mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-10 23:33:29 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7f554909 | ||
|
|
2b0e629dd1 | ||
|
|
7a1f402c5d | ||
|
|
ab56ce6004 | ||
|
|
53e948c0a9 | ||
|
|
b4f8ae00db | ||
|
|
9e610ddb73 | ||
|
|
7fc822948c |
@@ -194,6 +194,50 @@ extension ALTDeviceManager
|
|||||||
|
|
||||||
func fetchTeam(for account: ALTAccount, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
|
func fetchTeam(for account: ALTAccount, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
|
||||||
{
|
{
|
||||||
|
func finish(_ result: Result<ALTTeam, Error>)
|
||||||
|
{
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error):
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
|
||||||
|
case .success(let team):
|
||||||
|
|
||||||
|
var isCancelled = false
|
||||||
|
|
||||||
|
if team.type != .free
|
||||||
|
{
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = NSLocalizedString("Installing AltStore will revoke your iOS development certificate.", comment: "")
|
||||||
|
alert.informativeText = NSLocalizedString("""
|
||||||
|
This will not affect apps you've submitted to the App Store, but may cause apps you've installed to your devices with Xcode to stop working until you reinstall them.
|
||||||
|
|
||||||
|
To prevent this from happening, feel free to try again with another Apple ID to install AltStore.
|
||||||
|
""", comment: "")
|
||||||
|
|
||||||
|
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
||||||
|
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
||||||
|
|
||||||
|
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
||||||
|
|
||||||
|
let buttonIndex = alert.runModal()
|
||||||
|
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
|
||||||
|
{
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCancelled
|
||||||
|
{
|
||||||
|
return completionHandler(.failure(InstallError.cancelled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(team))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in
|
ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -201,15 +245,15 @@ extension ALTDeviceManager
|
|||||||
|
|
||||||
if let team = teams.first(where: { $0.type == .free })
|
if let team = teams.first(where: { $0.type == .free })
|
||||||
{
|
{
|
||||||
return completionHandler(.success(team))
|
return finish(.success(team))
|
||||||
}
|
}
|
||||||
else if let team = teams.first(where: { $0.type == .individual })
|
else if let team = teams.first(where: { $0.type == .individual })
|
||||||
{
|
{
|
||||||
return completionHandler(.success(team))
|
return finish(.success(team))
|
||||||
}
|
}
|
||||||
else if let team = teams.first
|
else if let team = teams.first
|
||||||
{
|
{
|
||||||
return completionHandler(.success(team))
|
return finish(.success(team))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -218,7 +262,7 @@ extension ALTDeviceManager
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error))
|
finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
|||||||
@@ -131,7 +131,6 @@
|
|||||||
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */; };
|
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */; };
|
||||||
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; };
|
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; };
|
||||||
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */; };
|
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */; };
|
||||||
BFB1169D22932DB100BB457C /* apps.json in Resources */ = {isa = PBXBuildFile; fileRef = BFB1169C22932DB100BB457C /* apps.json */; };
|
|
||||||
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB364592325985F00CD0EB1 /* FindServerOperation.swift */; };
|
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB364592325985F00CD0EB1 /* FindServerOperation.swift */; };
|
||||||
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */; };
|
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */; };
|
||||||
BFB6B21B23186D640022A802 /* NewsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21A23186D640022A802 /* NewsItem.swift */; };
|
BFB6B21B23186D640022A802 /* NewsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21A23186D640022A802 /* NewsItem.swift */; };
|
||||||
@@ -1274,7 +1273,6 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
BFB1169D22932DB100BB457C /* apps.json in Resources */,
|
|
||||||
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */,
|
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */,
|
||||||
BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */,
|
BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */,
|
||||||
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Glz-dw-2Eg">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Glz-dw-2Eg">
|
||||||
<rect key="frame" x="0.0" y="76" width="343" height="33.5"/>
|
<rect key="frame" x="0.0" y="76" width="343" height="33.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="If you used an app-specific password to install AltStore, please use that same password again." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a51-OQ-f3j">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="If you used an app-specific password to install AltStore, please use that same password again." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="a51-OQ-f3j">
|
||||||
<rect key="frame" x="14" y="0.0" width="315" height="33.5"/>
|
<rect key="frame" x="14" y="0.0" width="315" height="33.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -19,7 +36,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0.1</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import AuthenticationServices
|
|||||||
|
|
||||||
private let clientID = "ZMx0EGUWe4TVWYXNZZwK_fbIK5jHFVWoUf1Qb-sqNXmT-YzAGwDPxxq7ak3_W5Q2"
|
private let clientID = "ZMx0EGUWe4TVWYXNZZwK_fbIK5jHFVWoUf1Qb-sqNXmT-YzAGwDPxxq7ak3_W5Q2"
|
||||||
private let clientSecret = "1hktsZB89QyN69cB4R0tu55R4TCPQGXxvebYUUh7Y-5TLSnRswuxs6OUjdJ74IJt"
|
private let clientSecret = "1hktsZB89QyN69cB4R0tu55R4TCPQGXxvebYUUh7Y-5TLSnRswuxs6OUjdJ74IJt"
|
||||||
private let creatorAccessToken = "mBh0yyK40Ibjzwb_cYeKIuzq8nNFBdEIlNPfgAQlhcU"
|
private let creatorAccessToken = "NSX1ts9Rf9IzKRCu8GjbwsZ6wll8bDtoJxNbPbp2eZo"
|
||||||
|
|
||||||
private let campaignID = "2863968"
|
private let campaignID = "2863968"
|
||||||
|
|
||||||
@@ -21,12 +21,14 @@ extension PatreonAPI
|
|||||||
{
|
{
|
||||||
case unknown
|
case unknown
|
||||||
case notAuthenticated
|
case notAuthenticated
|
||||||
|
case invalidAccessToken
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .unknown: return NSLocalizedString("An unknown error occurred.", comment: "")
|
case .unknown: return NSLocalizedString("An unknown error occurred.", comment: "")
|
||||||
case .notAuthenticated: return NSLocalizedString("No connected Patreon account.", comment: "")
|
case .notAuthenticated: return NSLocalizedString("No connected Patreon account.", comment: "")
|
||||||
|
case .invalidAccessToken: return NSLocalizedString("Invalid access token.", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,7 +367,7 @@ private extension PatreonAPI
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
completion(.failure(Error.notAuthenticated))
|
completion(.failure(Error.invalidAccessToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
"developerName": "Riley Testut",
|
"developerName": "Riley Testut",
|
||||||
"subtitle": "Classic games in your pocket.",
|
"subtitle": "Classic games in your pocket.",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"versionDate": "2019-09-28T14:30:00-07:00",
|
"versionDate": "2019-09-28T12:00:00-07:00",
|
||||||
"versionDescription": "Initial version.",
|
"versionDescription": "Initial version.",
|
||||||
"downloadURL": "https://f000.backblazeb2.com/file/altstore/delta.ipa",
|
"downloadURL": "https://f000.backblazeb2.com/file/altstore/delta.ipa",
|
||||||
"localizedDescription": "Delta is an all-in-one emulator for iOS. Delta builds upon the strengths of its predecessor, GBA4iOS, while expanding to include support for more game systems such as NES, SNES, and N64.\n\nFEATURES\n\nSupported Game Systems\n• Nintendo Entertainment System\n• Super Nintendo Entertainment System\n• Nintendo 64\n• Game Boy (Color)\n• Game Boy Advance\n• And plenty more to come!\n\nController Support\n• Supports PS4, Xbox One S, and MFi game controllers.\n• Supports bluetooth (and wired) keyboards, as well as the Apple Smart Keyboard.\n• Completely customize button mappings on a per-system, per-controller basis.\n• Map buttons to special “Quick Save”, “Quick Load,” and “Fast Forward” actions.\n\nSave States\n• Save and load save states for any game from the pause menu.\n• Lock save states to prevent them from being accidentally overwritten.\n• Automatically makes backup save states to ensure you never lose your progress.\n• Support for “Quick Saves,” save states that can be quickly saved/loaded with a single button press (requires external controller).\n\nCheats\n• Supports various types of cheat codes for each supported system:\n• NES: Game Genie\n• SNES: Game Genie, Pro Action Replay\n• N64: GameShark\n• GBC: Game Genie, GameShark\n• GBA: Action Replay, Code Breaker, GameShark\n\nDelta Sync\n• Sync your games, game saves, save states, cheats, controller skins, and controller mappings between devices.\n• View version histories of everything you sync and optionally restore them to earlier versions.\n• Supports both Google Drive and Dropbox.\n\nCustom Controller Skins\n• Beautiful built-in controller skins for all systems.\n• Import controller skins made by others, or even make your own to share with the world!\n\nHold Button\n• Choose buttons for Delta to hold down on your behalf, freeing up your thumbs to press other buttons instead.\n• Perfect for games that typically require one button be held down constantly (ex: run button in Mario games, or the A button in Mario Kart).\n\nFast Forward\n• Speed through slower parts of games by running the game much faster than normal.\n• Easily enable or disable from the pause menu, or optionally with a mapped button on an external controller.\n\n3D/Haptic Touch\n• Use 3D or Haptic Touch to “peek” at games, save states, and cheat codes.\n• App icon shortcuts allow quick access to your most recently played games, or optionally customize the shortcuts to always include certain games.\n\nGame Artwork\n• Automatically displays appropriate box art for imported games.\n• Change a game’s artwork to anything you want, or select from the built-in game artwork database.\n\nMisc.\n• Gyroscope support for WarioWare: Twisted!\n• Support for delta:// URL scheme to jump directly into a specific game.\n\n**Delta and AltStore LLC are in no way affiliated with Nintendo. The name \"Nintendo\" and all associated game console names are registered trademarks of Nintendo Co., Ltd.**",
|
"localizedDescription": "Delta is an all-in-one emulator for iOS. Delta builds upon the strengths of its predecessor, GBA4iOS, while expanding to include support for more game systems such as NES, SNES, and N64.\n\nFEATURES\n\nSupported Game Systems\n• Nintendo Entertainment System\n• Super Nintendo Entertainment System\n• Nintendo 64\n• Game Boy (Color)\n• Game Boy Advance\n• And plenty more to come!\n\nController Support\n• Supports PS4, Xbox One S, and MFi game controllers.\n• Supports bluetooth (and wired) keyboards, as well as the Apple Smart Keyboard.\n• Completely customize button mappings on a per-system, per-controller basis.\n• Map buttons to special “Quick Save”, “Quick Load,” and “Fast Forward” actions.\n\nSave States\n• Save and load save states for any game from the pause menu.\n• Lock save states to prevent them from being accidentally overwritten.\n• Automatically makes backup save states to ensure you never lose your progress.\n• Support for “Quick Saves,” save states that can be quickly saved/loaded with a single button press (requires external controller).\n\nCheats\n• Supports various types of cheat codes for each supported system:\n• NES: Game Genie\n• SNES: Game Genie, Pro Action Replay\n• N64: GameShark\n• GBC: Game Genie, GameShark\n• GBA: Action Replay, Code Breaker, GameShark\n\nDelta Sync\n• Sync your games, game saves, save states, cheats, controller skins, and controller mappings between devices.\n• View version histories of everything you sync and optionally restore them to earlier versions.\n• Supports both Google Drive and Dropbox.\n\nCustom Controller Skins\n• Beautiful built-in controller skins for all systems.\n• Import controller skins made by others, or even make your own to share with the world!\n\nHold Button\n• Choose buttons for Delta to hold down on your behalf, freeing up your thumbs to press other buttons instead.\n• Perfect for games that typically require one button be held down constantly (ex: run button in Mario games, or the A button in Mario Kart).\n\nFast Forward\n• Speed through slower parts of games by running the game much faster than normal.\n• Easily enable or disable from the pause menu, or optionally with a mapped button on an external controller.\n\n3D/Haptic Touch\n• Use 3D or Haptic Touch to “peek” at games, save states, and cheat codes.\n• App icon shortcuts allow quick access to your most recently played games, or optionally customize the shortcuts to always include certain games.\n\nGame Artwork\n• Automatically displays appropriate box art for imported games.\n• Change a game’s artwork to anything you want, or select from the built-in game artwork database.\n\nMisc.\n• Gyroscope support for WarioWare: Twisted!\n• Support for delta:// URL scheme to jump directly into a specific game.\n\n**Delta and AltStore LLC are in no way affiliated with Nintendo. The name \"Nintendo\" and all associated game console names are registered trademarks of Nintendo Co., Ltd.**",
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
"developerName": "Riley Testut",
|
"developerName": "Riley Testut",
|
||||||
"subtitle": "Classic games in your pocket.",
|
"subtitle": "Classic games in your pocket.",
|
||||||
"version": "1.0b",
|
"version": "1.0b",
|
||||||
"versionDate": "2019-09-28T02:30:00-07:00",
|
"versionDate": "2019-09-28T12:00:00-07:00",
|
||||||
"versionDescription": "Includes initial support for DS games.",
|
"versionDescription": "Includes initial support for DS games.",
|
||||||
"downloadURL": "https://f000.backblazeb2.com/file/altstore/delta-beta.ipa",
|
"downloadURL": "https://f000.backblazeb2.com/file/altstore/delta-beta.ipa",
|
||||||
"localizedDescription": "The next console for Delta is coming: this beta version of Delta brings support for playing DS games!\n\nDS support currently includes:\n• Playing DS games\n• Save States\n• Hold Button\n\nFeatures I'm still working on:\n• Fast Forward\n• Cheats\n• Controller skin (using placeholder controller skin for now)\n\nPlease report any issues you find to support@altstore.io. Thanks!",
|
"localizedDescription": "The next console for Delta is coming: this beta version of Delta brings support for playing DS games!\n\nDS support currently includes:\n• Playing DS games\n• Save States\n• Hold Button\n\nFeatures I'm still working on:\n• Fast Forward\n• Cheats\n• Controller skin (using placeholder controller skin for now)\n\nPlease report any issues you find to support@altstore.io. Thanks!",
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
"subtitle": "Manage your clipboard history with ease.",
|
"subtitle": "Manage your clipboard history with ease.",
|
||||||
"developerName": "Riley Testut",
|
"developerName": "Riley Testut",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"versionDate": "2019-09-28T02:30:00-07:00",
|
"versionDate": "2019-09-28T12:00:00-07:00",
|
||||||
"versionDescription": "Initial version.",
|
"versionDescription": "Initial version.",
|
||||||
"downloadURL": "https://f000.backblazeb2.com/file/altstore/clip.ipa",
|
"downloadURL": "https://f000.backblazeb2.com/file/altstore/clip.ipa",
|
||||||
"localizedDescription": "Clip is a simple clipboard manager for iOS. \n\nUnlike other clipboard managers, Clip can continue monitoring your clipboard while in the background. No longer do you need to remember to manually open or share to an app to save your clipboard; just copy and paste as you would normally do, and Clip will have your back.\n\nIn addition to background monitoring, Clip also has these features:\n\n• Automatically track text, URLs, and images copied to the clipboard.\n• Shows notification upon saving a new item to the clipboard with a preview of the copied text, URL, or image.\n• Copy, delete, or share any clippings saved to Clip.\n• Customizable history limit.\n\nDownload Clip today, and never worry about losing your clipboard again!",
|
"localizedDescription": "Clip is a simple clipboard manager for iOS. \n\nUnlike other clipboard managers, Clip can continue monitoring your clipboard while in the background. No longer do you need to remember to manually open or share to an app to save your clipboard; just copy and paste as you would normally do, and Clip will have your back.\n\nIn addition to background monitoring, Clip also has these features:\n\n• Automatically track text, URLs, and images copied to the clipboard.\n• Shows notification upon saving a new item to the clipboard with a preview of the copied text, URL, or image.\n• Copy, delete, or share any clippings saved to Clip.\n• Customizable history limit.\n\nDownload Clip today, and never worry about losing your clipboard again!",
|
||||||
|
|||||||
@@ -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