mirror of
https://github.com/SideStore/SideStore.git
synced 2026-03-27 12:55:40 +01:00
Merge branch 'module_refactoring' into develop
This commit is contained in:
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
// Shared
|
||||||
|
#import "ALTConstants.h"
|
||||||
|
#import "ALTConnection.h"
|
||||||
|
#import "NSError+ALTServerError.h"
|
||||||
|
#import "CFNotificationName+AltStore.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface AKDevice : NSObject
|
@interface AKDevice : NSObject
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
|
||||||
|
|
||||||
private let ReceivedLocalServerConnectionRequest: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
private let ReceivedLocalServerConnectionRequest: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||||
{ (center, observer, name, object, userInfo) in
|
{ (center, observer, name, object, userInfo) in
|
||||||
guard let name = name, let observer = observer else { return }
|
guard let name = name, let observer = observer else { return }
|
||||||
|
|||||||
@@ -7,21 +7,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltKit
|
|
||||||
|
|
||||||
typealias ConnectionManager = AltKit.ConnectionManager<RequestHandler>
|
typealias DaemonConnectionManager = ConnectionManager<DaemonRequestHandler>
|
||||||
|
|
||||||
private let connectionManager = ConnectionManager(requestHandler: RequestHandler(),
|
private let connectionManager = ConnectionManager(requestHandler: DaemonRequestHandler(),
|
||||||
connectionHandlers: [LocalConnectionHandler()])
|
connectionHandlers: [LocalConnectionHandler()])
|
||||||
|
|
||||||
extension ConnectionManager
|
extension DaemonConnectionManager
|
||||||
{
|
{
|
||||||
static var shared: ConnectionManager {
|
static var shared: ConnectionManager {
|
||||||
return connectionManager
|
return connectionManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RequestHandler: AltKit.RequestHandler
|
struct DaemonRequestHandler: RequestHandler
|
||||||
{
|
{
|
||||||
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
|
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
autoreleasepool {
|
autoreleasepool {
|
||||||
ConnectionManager.shared.start()
|
DaemonConnectionManager.shared.start()
|
||||||
RunLoop.current.run()
|
RunLoop.current.run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,9 @@
|
|||||||
#import "ALTDeviceManager.h"
|
#import "ALTDeviceManager.h"
|
||||||
#import "ALTWiredConnection.h"
|
#import "ALTWiredConnection.h"
|
||||||
#import "ALTNotificationConnection.h"
|
#import "ALTNotificationConnection.h"
|
||||||
#import "AltKit.h"
|
|
||||||
|
// Shared
|
||||||
|
#import "ALTConstants.h"
|
||||||
|
#import "ALTConnection.h"
|
||||||
|
#import "NSError+ALTServerError.h"
|
||||||
|
#import "CFNotificationName+AltStore.h"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltKit
|
|
||||||
|
|
||||||
class AnisetteDataManager: NSObject
|
class AnisetteDataManager: NSObject
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
UNUserNotificationCenter.current().delegate = self
|
UNUserNotificationCenter.current().delegate = self
|
||||||
|
|
||||||
ConnectionManager.shared.start()
|
ServerConnectionManager.shared.start()
|
||||||
ALTDeviceManager.shared.start()
|
ALTDeviceManager.shared.start()
|
||||||
|
|
||||||
let item = NSStatusBar.system.statusItem(withLength: -1)
|
let item = NSStatusBar.system.statusItem(withLength: -1)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <AltSign/AltSign.h>
|
#import "AltSign.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "ALTNotificationConnection+Private.h"
|
#import "ALTNotificationConnection+Private.h"
|
||||||
#import "AltKit.h"
|
|
||||||
|
#import "NSError+ALTServerError.h"
|
||||||
|
|
||||||
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
|
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <AltSign/AltSign.h>
|
#import "AltSign.h"
|
||||||
|
|
||||||
#import "AltKit.h"
|
#import "ALTConnection.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "ALTWiredConnection+Private.h"
|
#import "ALTWiredConnection+Private.h"
|
||||||
#import "AltKit.h"
|
|
||||||
|
#import "ALTConnection.h"
|
||||||
|
#import "NSError+ALTServerError.h"
|
||||||
|
|
||||||
@implementation ALTWiredConnection
|
@implementation ALTWiredConnection
|
||||||
|
|
||||||
@@ -7,21 +7,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltKit
|
|
||||||
|
|
||||||
typealias ConnectionManager = AltKit.ConnectionManager<RequestHandler>
|
typealias ServerConnectionManager = ConnectionManager<ServerRequestHandler>
|
||||||
|
|
||||||
private let connectionManager = ConnectionManager(requestHandler: RequestHandler(),
|
private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(),
|
||||||
connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()])
|
connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()])
|
||||||
|
|
||||||
extension ConnectionManager
|
extension ServerConnectionManager
|
||||||
{
|
{
|
||||||
static var shared: ConnectionManager {
|
static var shared: ConnectionManager {
|
||||||
return connectionManager
|
return connectionManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RequestHandler: AltKit.RequestHandler
|
struct ServerRequestHandler: RequestHandler
|
||||||
{
|
{
|
||||||
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
|
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
|
||||||
{
|
{
|
||||||
@@ -187,7 +186,7 @@ private extension RequestHandler
|
|||||||
let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
|
let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
|
||||||
print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
|
print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
|
||||||
|
|
||||||
if let error = error.map { ALTServerError($0) }
|
if let error = error.map({ ALTServerError($0) })
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error))
|
completionHandler(.failure(error))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltKit
|
|
||||||
|
|
||||||
class WiredConnectionHandler: ConnectionHandler
|
class WiredConnectionHandler: ConnectionHandler
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
|
||||||
|
|
||||||
extension WirelessConnectionHandler
|
extension WirelessConnectionHandler
|
||||||
{
|
{
|
||||||
public enum State
|
public enum State
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <AltSign/AltSign.h>
|
#import "AltSign.h"
|
||||||
|
|
||||||
@class ALTWiredConnection;
|
@class ALTWiredConnection;
|
||||||
@class ALTNotificationConnection;
|
@class ALTNotificationConnection;
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
|
|
||||||
#import "ALTDeviceManager.h"
|
#import "ALTDeviceManager.h"
|
||||||
|
|
||||||
#import "AltKit.h"
|
|
||||||
#import "ALTWiredConnection+Private.h"
|
#import "ALTWiredConnection+Private.h"
|
||||||
#import "ALTNotificationConnection+Private.h"
|
#import "ALTNotificationConnection+Private.h"
|
||||||
|
|
||||||
|
#import "ALTConstants.h"
|
||||||
|
#import "NSError+ALTServerError.h"
|
||||||
|
|
||||||
#include <libimobiledevice/libimobiledevice.h>
|
#include <libimobiledevice/libimobiledevice.h>
|
||||||
#include <libimobiledevice/lockdown.h>
|
#include <libimobiledevice/lockdown.h>
|
||||||
#include <libimobiledevice/installation_proxy.h>
|
#include <libimobiledevice/installation_proxy.h>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1130"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "BF1E314F22A0616100370A3C"
|
|
||||||
BuildableName = "libAltKit.a"
|
|
||||||
BlueprintName = "AltKit"
|
|
||||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
</Testables>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "BF1E314F22A0616100370A3C"
|
|
||||||
BuildableName = "libAltKit.a"
|
|
||||||
BlueprintName = "AltKit"
|
|
||||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
||||||
2
AltStore.xcworkspace/contents.xcworkspacedata
generated
2
AltStore.xcworkspace/contents.xcworkspacedata
generated
@@ -5,7 +5,7 @@
|
|||||||
location = "container:AltStore.xcodeproj">
|
location = "container:AltStore.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Dependencies/AltSign/AltSign.xcodeproj">
|
location = "group:Dependencies/AltSign">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
|
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
|
||||||
|
|||||||
@@ -2,10 +2,4 @@
|
|||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "AltKit.h"
|
|
||||||
|
|
||||||
#import "ALTAppPermission.h"
|
|
||||||
#import "ALTPatreonBenefitType.h"
|
|
||||||
#import "ALTSourceUserInfoKey.h"
|
|
||||||
|
|
||||||
#import "NSAttributedString+Markdown.h"
|
#import "NSAttributedString+Markdown.h"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.siri</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.rileytestut.AltStore</string>
|
<string>group.com.rileytestut.AltStore</string>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
import AppCenter
|
import AppCenter
|
||||||
import AppCenterAnalytics
|
import AppCenterAnalytics
|
||||||
import AppCenterCrashes
|
import AppCenterCrashes
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
class PermissionPopoverViewController: UIViewController
|
class PermissionPopoverViewController: UIViewController
|
||||||
{
|
{
|
||||||
var permission: AppPermission!
|
var permission: AppPermission!
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class AppIDsViewController: UICollectionViewController
|
class AppIDsViewController: UICollectionViewController
|
||||||
|
|||||||
@@ -9,47 +9,12 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import Intents
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
private enum RefreshError: LocalizedError
|
|
||||||
{
|
|
||||||
case noInstalledApps
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
switch self
|
|
||||||
{
|
|
||||||
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension CFNotificationName
|
|
||||||
{
|
|
||||||
static let requestAppState = CFNotificationName("com.altstore.RequestAppState" as CFString)
|
|
||||||
static let appIsRunning = CFNotificationName("com.altstore.AppState.Running" as CFString)
|
|
||||||
|
|
||||||
static func requestAppState(for appID: String) -> CFNotificationName
|
|
||||||
{
|
|
||||||
let name = String(CFNotificationName.requestAppState.rawValue) + "." + appID
|
|
||||||
return CFNotificationName(name as CFString)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func appIsRunning(for appID: String) -> CFNotificationName
|
|
||||||
{
|
|
||||||
let name = String(CFNotificationName.appIsRunning.rawValue) + "." + appID
|
|
||||||
return CFNotificationName(name as CFString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
|
||||||
{ (center, observer, name, object, userInfo) in
|
|
||||||
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let name = name else { return }
|
|
||||||
appDelegate.receivedApplicationState(notification: name)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppDelegate
|
extension AppDelegate
|
||||||
{
|
{
|
||||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
||||||
@@ -68,11 +33,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
private var runningApplications: Set<String>?
|
private lazy var intentHandler = IntentHandler()
|
||||||
private var backgroundRefreshContext: NSManagedObjectContext? // Keep context alive until finished refreshing.
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||||
{
|
{
|
||||||
|
DatabaseManager.shared.start { (error) in
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
print("Failed to start DatabaseManager. Error:", error as Any)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print("Started DatabaseManager.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AnalyticsManager.shared.start()
|
AnalyticsManager.shared.start()
|
||||||
|
|
||||||
self.setTintColor()
|
self.setTintColor()
|
||||||
@@ -117,6 +92,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
{
|
{
|
||||||
return self.open(url)
|
return self.open(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any?
|
||||||
|
{
|
||||||
|
guard intent is RefreshAllIntent else { return nil }
|
||||||
|
return self.intentHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
extension AppDelegate
|
||||||
|
{
|
||||||
|
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
|
||||||
|
{
|
||||||
|
// Called when a new scene session is being created.
|
||||||
|
// Use this method to select a configuration to create the new scene with.
|
||||||
|
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
|
||||||
|
{
|
||||||
|
// Called when the user discards a scene session.
|
||||||
|
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||||
|
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AppDelegate
|
private extension AppDelegate
|
||||||
@@ -234,93 +233,92 @@ extension AppDelegate
|
|||||||
|
|
||||||
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||||||
{
|
{
|
||||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
if UserDefaults.standard.isBackgroundRefreshEnabled && !UserDefaults.standard.presentedLaunchReminderNotification
|
||||||
{
|
{
|
||||||
ServerManager.shared.startDiscovering()
|
let threeHours: TimeInterval = 3 * 60 * 60
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
||||||
|
|
||||||
if !UserDefaults.standard.presentedLaunchReminderNotification
|
let content = UNMutableNotificationContent()
|
||||||
{
|
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
||||||
let threeHours: TimeInterval = 3 * 60 * 60
|
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
|
||||||
|
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
||||||
let content = UNMutableNotificationContent()
|
UNUserNotificationCenter.current().add(request)
|
||||||
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
|
||||||
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
|
UserDefaults.standard.presentedLaunchReminderNotification = true
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
|
|
||||||
UserDefaults.standard.presentedLaunchReminderNotification = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let refreshIdentifier = UUID().uuidString
|
|
||||||
|
|
||||||
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
||||||
|
|
||||||
func finish(_ result: Result<[String: Result<InstalledApp, Error>], Error>)
|
|
||||||
{
|
|
||||||
// If finish is actually called, that means an error occured during installation.
|
|
||||||
|
|
||||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
|
||||||
{
|
|
||||||
ServerManager.shared.stopDiscovering()
|
|
||||||
self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
taskCompletionHandler()
|
|
||||||
|
|
||||||
self.backgroundRefreshContext = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let error = taskResult.error
|
if let error = taskResult.error
|
||||||
{
|
{
|
||||||
print("Error starting extended background task. Aborting.", error)
|
print("Error starting extended background task. Aborting.", error)
|
||||||
backgroundFetchCompletionHandler(.failed)
|
backgroundFetchCompletionHandler(.failed)
|
||||||
finish(.failure(error))
|
taskCompletionHandler()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !DatabaseManager.shared.isStarted
|
if !DatabaseManager.shared.isStarted
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.start() { (error) in
|
DatabaseManager.shared.start() { (error) in
|
||||||
if let error = error
|
if error != nil
|
||||||
{
|
{
|
||||||
backgroundFetchCompletionHandler(.failed)
|
backgroundFetchCompletionHandler(.failed)
|
||||||
finish(.failure(error))
|
taskCompletionHandler()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
|
self.performBackgroundFetch { (backgroundFetchResult) in
|
||||||
|
backgroundFetchCompletionHandler(backgroundFetchResult)
|
||||||
|
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
||||||
|
taskCompletionHandler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
|
self.performBackgroundFetch { (backgroundFetchResult) in
|
||||||
|
backgroundFetchCompletionHandler(backgroundFetchResult)
|
||||||
|
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
||||||
|
taskCompletionHandler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func performBackgroundFetch(backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||||
|
refreshAppsCompletionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||||
|
{
|
||||||
|
self.fetchSources { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure: backgroundFetchCompletionHandler(.failed)
|
||||||
|
case .success: backgroundFetchCompletionHandler(.newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
{
|
||||||
|
refreshAppsCompletionHandler(.success([:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard UserDefaults.standard.isBackgroundRefreshEnabled else { return }
|
||||||
|
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
||||||
|
AppManager.shared.backgroundRefresh(installedApps, completionHandler: refreshAppsCompletionHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AppDelegate
|
private extension AppDelegate
|
||||||
{
|
{
|
||||||
func refreshApps(identifier: String,
|
func fetchSources(completionHandler: @escaping (Result<Set<Source>, Error>) -> Void)
|
||||||
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
|
||||||
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
|
||||||
{
|
{
|
||||||
var fetchSourcesResult: Result<Set<Source>, Error>?
|
|
||||||
var serversResult: Result<Void, Error>?
|
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
AppManager.shared.fetchSources() { (result) in
|
AppManager.shared.fetchSources() { (result) in
|
||||||
fetchSourcesResult = result.map { $0.0 }.mapError { $0 as Error }
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (_, context) = try result.get()
|
let (sources, context) = try result.get()
|
||||||
|
|
||||||
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
||||||
previousUpdatesFetchRequest.includesPendingChanges = false
|
previousUpdatesFetchRequest.includesPendingChanges = false
|
||||||
@@ -383,223 +381,14 @@ private extension AppDelegate
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(sources))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Error fetching apps:", error)
|
print("Error fetching apps:", error)
|
||||||
|
completionHandler(.failure(error))
|
||||||
fetchSourcesResult = .failure(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
|
||||||
{
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
|
||||||
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
|
||||||
guard !installedApps.isEmpty else {
|
|
||||||
serversResult = .success(())
|
|
||||||
dispatchGroup.leave()
|
|
||||||
|
|
||||||
completionHandler(.failure(RefreshError.noInstalledApps))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.runningApplications = []
|
|
||||||
self.backgroundRefreshContext = context
|
|
||||||
|
|
||||||
let identifiers = installedApps.compactMap { $0.bundleIdentifier }
|
|
||||||
print("Apps to refresh:", identifiers)
|
|
||||||
|
|
||||||
DispatchQueue.global().async {
|
|
||||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
||||||
|
|
||||||
for identifier in identifiers
|
|
||||||
{
|
|
||||||
let appIsRunningNotification = CFNotificationName.appIsRunning(for: identifier)
|
|
||||||
CFNotificationCenterAddObserver(notificationCenter, nil, ReceivedApplicationState, appIsRunningNotification.rawValue, nil, .deliverImmediately)
|
|
||||||
|
|
||||||
let requestAppStateNotification = CFNotificationName.requestAppState(for: identifier)
|
|
||||||
CFNotificationCenterPostNotification(notificationCenter, requestAppStateNotification, nil, nil, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for three seconds to:
|
|
||||||
// a) give us time to discover AltServers
|
|
||||||
// b) give other processes a chance to respond to requestAppState notification
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
|
|
||||||
context.perform {
|
|
||||||
if ServerManager.shared.discoveredServers.isEmpty
|
|
||||||
{
|
|
||||||
serversResult = .failure(ConnectionError.serverNotFound)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
serversResult = .success(())
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.leave()
|
|
||||||
|
|
||||||
let filteredApps = installedApps.filter { !(self.runningApplications?.contains($0.bundleIdentifier) ?? false) }
|
|
||||||
print("Filtered Apps to Refresh:", filteredApps.map { $0.bundleIdentifier })
|
|
||||||
|
|
||||||
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
|
|
||||||
group.beginInstallationHandler = { (installedApp) in
|
|
||||||
guard installedApp.bundleIdentifier == StoreApp.altstoreAppID else { return }
|
|
||||||
|
|
||||||
// We're starting to install AltStore, which means the app is about to quit.
|
|
||||||
// So, we schedule a "refresh successful" local notification to be displayed after a delay,
|
|
||||||
// but if the app is still running, we cancel the notification.
|
|
||||||
// Then, we schedule another notification and repeat the process.
|
|
||||||
|
|
||||||
// Also since AltServer has already received the app, it can finish installing even if we're no longer running in background.
|
|
||||||
|
|
||||||
if let error = group.context.error
|
|
||||||
{
|
|
||||||
self.scheduleFinishedRefreshingNotification(for: .failure(error), identifier: identifier)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var results = group.results
|
|
||||||
results[installedApp.bundleIdentifier] = .success(installedApp)
|
|
||||||
|
|
||||||
self.scheduleFinishedRefreshingNotification(for: .success(results), identifier: identifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.completionHandler = { (results) in
|
|
||||||
completionHandler(.success(results))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .main) {
|
|
||||||
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
|
||||||
{
|
|
||||||
guard let fetchSourcesResult = fetchSourcesResult else {
|
|
||||||
backgroundFetchCompletionHandler(.failed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fetchSourcesResult
|
|
||||||
{
|
|
||||||
case .failure: backgroundFetchCompletionHandler(.failed)
|
|
||||||
case .success: backgroundFetchCompletionHandler(.newData)
|
|
||||||
}
|
|
||||||
|
|
||||||
completionHandler(.success([:]))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
guard let fetchSourcesResult = fetchSourcesResult, let serversResult = serversResult else {
|
|
||||||
backgroundFetchCompletionHandler(.failed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call completionHandler early to improve chances of refreshing in the background again.
|
|
||||||
switch (fetchSourcesResult, serversResult)
|
|
||||||
{
|
|
||||||
case (.success, .success): backgroundFetchCompletionHandler(.newData)
|
|
||||||
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
|
|
||||||
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func receivedApplicationState(notification: CFNotificationName)
|
|
||||||
{
|
|
||||||
let baseName = String(CFNotificationName.appIsRunning.rawValue)
|
|
||||||
|
|
||||||
let appID = String(notification.rawValue).replacingOccurrences(of: baseName + ".", with: "")
|
|
||||||
self.runningApplications?.insert(appID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func scheduleFinishedRefreshingNotification(for result: Result<[String: Result<InstalledApp, Error>], Error>, identifier: String, delay: TimeInterval = 5)
|
|
||||||
{
|
|
||||||
func scheduleFinishedRefreshingNotification()
|
|
||||||
{
|
|
||||||
self.cancelFinishedRefreshingNotification(identifier: identifier)
|
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
|
|
||||||
var shouldPresentAlert = true
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let results = try result.get()
|
|
||||||
shouldPresentAlert = !results.isEmpty
|
|
||||||
|
|
||||||
for (_, result) in results
|
|
||||||
{
|
|
||||||
guard case let .failure(error) = result else { continue }
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
|
||||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
|
||||||
}
|
|
||||||
catch ConnectionError.serverNotFound
|
|
||||||
{
|
|
||||||
shouldPresentAlert = false
|
|
||||||
}
|
|
||||||
catch RefreshError.noInstalledApps
|
|
||||||
{
|
|
||||||
shouldPresentAlert = false
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to refresh apps in background.", error)
|
|
||||||
|
|
||||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
|
||||||
content.body = error.localizedDescription
|
|
||||||
|
|
||||||
shouldPresentAlert = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if shouldPresentAlert
|
|
||||||
{
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
|
|
||||||
if delay > 0
|
|
||||||
{
|
|
||||||
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
|
|
||||||
UNUserNotificationCenter.current().getPendingNotificationRequests() { (requests) in
|
|
||||||
// If app is still running at this point, we schedule another notification with same identifier.
|
|
||||||
// This prevents the currently scheduled notification from displaying, and starts another countdown timer.
|
|
||||||
// First though, make sure there _is_ still a pending request, otherwise it's been cancelled
|
|
||||||
// and we should stop polling.
|
|
||||||
guard requests.contains(where: { $0.identifier == identifier }) else { return }
|
|
||||||
|
|
||||||
scheduleFinishedRefreshingNotification()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleFinishedRefreshingNotification()
|
|
||||||
|
|
||||||
// Perform synchronously to ensure app doesn't quit before we've finishing saving to disk.
|
|
||||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
|
||||||
context.performAndWait {
|
|
||||||
_ = RefreshAttempt(identifier: identifier, result: result, context: context)
|
|
||||||
|
|
||||||
do { try context.save() }
|
|
||||||
catch { print("Failed to save refresh attempt.", error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cancelFinishedRefreshingNotification(identifier: String)
|
|
||||||
{
|
|
||||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import AltSign
|
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class RefreshAltStoreViewController: UIViewController
|
class RefreshAltStoreViewController: UIViewController
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
@@ -83,6 +84,7 @@ private extension BrowseViewController
|
|||||||
cell.layoutMargins.left = self.view.layoutMargins.left
|
cell.layoutMargins.left = self.view.layoutMargins.left
|
||||||
cell.layoutMargins.right = self.view.layoutMargins.right
|
cell.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
|
cell.subtitleLabel.text = app.subtitle
|
||||||
cell.imageURLs = Array(app.screenshotURLs.prefix(2))
|
cell.imageURLs = Array(app.screenshotURLs.prefix(2))
|
||||||
|
|
||||||
cell.bannerView.configure(for: app)
|
cell.bannerView.configure(for: app)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class AppBannerView: RSTNibView
|
class AppBannerView: RSTNibView
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
extension TimeInterval
|
extension TimeInterval
|
||||||
{
|
{
|
||||||
static let shortToastViewDuration = 4.0
|
static let shortToastViewDuration = 4.0
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
|
|
||||||
extension FileManager
|
extension FileManager
|
||||||
{
|
{
|
||||||
|
|||||||
23
AltStore/Extensions/INInteraction+AltStore.swift
Normal file
23
AltStore/Extensions/INInteraction+AltStore.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// INInteraction+AltStore.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/4/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Intents
|
||||||
|
|
||||||
|
// Requires iOS 14 in-app intent handling.
|
||||||
|
@available(iOS 14, *)
|
||||||
|
extension INInteraction
|
||||||
|
{
|
||||||
|
static func refreshAllApps() -> INInteraction
|
||||||
|
{
|
||||||
|
let refreshAllIntent = RefreshAllIntent()
|
||||||
|
refreshAllIntent.suggestedInvocationPhrase = NSString.deferredLocalizedIntentsString(with: "Refresh my apps") as String
|
||||||
|
|
||||||
|
let interaction = INInteraction(intent: refreshAllIntent, response: nil)
|
||||||
|
return interaction
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,10 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
|
<key>INIntentsSupported</key>
|
||||||
|
<array>
|
||||||
|
<string>RefreshAllIntent</string>
|
||||||
|
</array>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>altstore-com.rileytestut.AltStore</string>
|
<string>altstore-com.rileytestut.AltStore</string>
|
||||||
@@ -85,6 +89,31 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
<string>AltStore uses the local network to find and communicate with AltServer.</string>
|
<string>AltStore uses the local network to find and communicate with AltServer.</string>
|
||||||
|
<key>NSUserActivityTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>RefreshAllIntent</string>
|
||||||
|
</array>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<true/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>Default Configuration</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
|
|||||||
119
AltStore/Intents/IntentHandler.swift
Normal file
119
AltStore/Intents/IntentHandler.swift
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
//
|
||||||
|
// IntentHandler.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/6/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||||
|
{
|
||||||
|
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
|
||||||
|
|
||||||
|
private var completionHandlers = [RefreshAllIntent: (RefreshAllIntentResponse) -> Void]()
|
||||||
|
private var queuedResponses = [RefreshAllIntent: RefreshAllIntentResponse]()
|
||||||
|
|
||||||
|
func confirm(intent: RefreshAllIntent, completion: @escaping (RefreshAllIntentResponse) -> Void)
|
||||||
|
{
|
||||||
|
// Refreshing apps usually, but not always, completes within alotted time.
|
||||||
|
// As a workaround, we'll start refreshing apps in confirm() so we can
|
||||||
|
// take advantage of some extra time before starting handle() timeout timer.
|
||||||
|
|
||||||
|
self.completionHandlers[intent] = { (response) in
|
||||||
|
if response.code != .ready
|
||||||
|
{
|
||||||
|
// Operation finished before confirmation "timeout".
|
||||||
|
// Cache response to return it when handle() is called.
|
||||||
|
self.queuedResponses[intent] = response
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give ourselves 9 extra seconds before starting handle() timeout timer.
|
||||||
|
// 10 seconds or longer results in timeout regardless.
|
||||||
|
self.queue.asyncAfter(deadline: .now() + 9.0) {
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !DatabaseManager.shared.isStarted
|
||||||
|
{
|
||||||
|
DatabaseManager.shared.start() { (error) in
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse.failure(localizedDescription: error.localizedDescription))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.refreshApps(intent: intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.refreshApps(intent: intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(intent: RefreshAllIntent, completion: @escaping (RefreshAllIntentResponse) -> Void)
|
||||||
|
{
|
||||||
|
self.completionHandlers[intent] = { (response) in
|
||||||
|
// Ignore .ready response from confirm() timeout.
|
||||||
|
guard response.code != .ready else { return }
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let response = self.queuedResponses[intent]
|
||||||
|
{
|
||||||
|
self.queuedResponses[intent] = nil
|
||||||
|
self.finish(intent, response: response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension IntentHandler
|
||||||
|
{
|
||||||
|
func finish(_ intent: RefreshAllIntent, response: RefreshAllIntentResponse)
|
||||||
|
{
|
||||||
|
self.queue.async {
|
||||||
|
guard let completionHandler = self.completionHandlers[intent] else { return }
|
||||||
|
self.completionHandlers[intent] = nil
|
||||||
|
|
||||||
|
completionHandler(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshApps(intent: RefreshAllIntent)
|
||||||
|
{
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
let installedApps = InstalledApp.fetchActiveApps(in: context)
|
||||||
|
AppManager.shared.backgroundRefresh(installedApps, presentsNotifications: false) { (result) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let results = try result.get()
|
||||||
|
|
||||||
|
for (_, result) in results
|
||||||
|
{
|
||||||
|
guard case let .failure(error) = result else { continue }
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
|
}
|
||||||
|
catch RefreshError.noInstalledApps
|
||||||
|
{
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
|
}
|
||||||
|
catch let error as NSError
|
||||||
|
{
|
||||||
|
print("Failed to refresh apps in background.", error)
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse.failure(localizedDescription: error.localizedFailureReason ?? error.localizedDescription))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
AltStore/Intents/Intents.intentdefinition
Normal file
120
AltStore/Intents/Intents.intentdefinition
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>INEnums</key>
|
||||||
|
<array/>
|
||||||
|
<key>INIntentDefinitionModelVersion</key>
|
||||||
|
<string>1.2</string>
|
||||||
|
<key>INIntentDefinitionNamespace</key>
|
||||||
|
<string>KyhEWE</string>
|
||||||
|
<key>INIntentDefinitionSystemVersion</key>
|
||||||
|
<string>20A5354i</string>
|
||||||
|
<key>INIntentDefinitionToolsBuildVersion</key>
|
||||||
|
<string>12A8189n</string>
|
||||||
|
<key>INIntentDefinitionToolsVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
|
<key>INIntents</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentCategory</key>
|
||||||
|
<string>generic</string>
|
||||||
|
<key>INIntentConfigurable</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentDescriptionID</key>
|
||||||
|
<string>62S1rm</string>
|
||||||
|
<key>INIntentLastParameterTag</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
<key>INIntentManagedParameterCombinations</key>
|
||||||
|
<dict>
|
||||||
|
<key></key>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentParameterCombinationTitle</key>
|
||||||
|
<string>Refresh All Apps</string>
|
||||||
|
<key>INIntentParameterCombinationTitleID</key>
|
||||||
|
<string>cJxa2I</string>
|
||||||
|
<key>INIntentParameterCombinationUpdatesLinked</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>INIntentName</key>
|
||||||
|
<string>RefreshAll</string>
|
||||||
|
<key>INIntentParameterCombinations</key>
|
||||||
|
<dict>
|
||||||
|
<key></key>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentParameterCombinationTitle</key>
|
||||||
|
<string>Refresh All Apps</string>
|
||||||
|
<key>INIntentParameterCombinationTitleID</key>
|
||||||
|
<string>DKTGdO</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>INIntentResponse</key>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseCodes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseCodeConciseFormatString</key>
|
||||||
|
<string>All apps have been refreshed.</string>
|
||||||
|
<key>INIntentResponseCodeConciseFormatStringID</key>
|
||||||
|
<string>3WMWsJ</string>
|
||||||
|
<key>INIntentResponseCodeFormatString</key>
|
||||||
|
<string>All apps have been refreshed.</string>
|
||||||
|
<key>INIntentResponseCodeFormatStringID</key>
|
||||||
|
<string>BjInD3</string>
|
||||||
|
<key>INIntentResponseCodeName</key>
|
||||||
|
<string>success</string>
|
||||||
|
<key>INIntentResponseCodeSuccess</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseCodeConciseFormatString</key>
|
||||||
|
<string>${localizedDescription}</string>
|
||||||
|
<key>INIntentResponseCodeConciseFormatStringID</key>
|
||||||
|
<string>GJdShK</string>
|
||||||
|
<key>INIntentResponseCodeFormatString</key>
|
||||||
|
<string>${localizedDescription}</string>
|
||||||
|
<key>INIntentResponseCodeFormatStringID</key>
|
||||||
|
<string>oXAiOU</string>
|
||||||
|
<key>INIntentResponseCodeName</key>
|
||||||
|
<string>failure</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>INIntentResponseLastParameterTag</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
<key>INIntentResponseParameters</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseParameterDisplayName</key>
|
||||||
|
<string>Localized Description</string>
|
||||||
|
<key>INIntentResponseParameterDisplayNameID</key>
|
||||||
|
<string>wdy22v</string>
|
||||||
|
<key>INIntentResponseParameterDisplayPriority</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>INIntentResponseParameterName</key>
|
||||||
|
<string>localizedDescription</string>
|
||||||
|
<key>INIntentResponseParameterTag</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
<key>INIntentResponseParameterType</key>
|
||||||
|
<string>String</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>INIntentTitle</key>
|
||||||
|
<string>Refresh All Apps</string>
|
||||||
|
<key>INIntentTitleID</key>
|
||||||
|
<string>2b6Xto</string>
|
||||||
|
<key>INIntentType</key>
|
||||||
|
<string>Custom</string>
|
||||||
|
<key>INIntentVerb</key>
|
||||||
|
<string>Do</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>INTypes</key>
|
||||||
|
<array/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
class LaunchViewController: RSTLaunchViewController
|
class LaunchViewController: RSTLaunchViewController
|
||||||
{
|
{
|
||||||
private var didFinishLaunching = false
|
private var didFinishLaunching = false
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
|
import Intents
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -23,15 +24,41 @@ extension AppManager
|
|||||||
static let expirationWarningNotificationID = "altstore-expiration-warning"
|
static let expirationWarningNotificationID = "altstore-expiration-warning"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
class AppManagerPublisher: ObservableObject
|
||||||
|
{
|
||||||
|
@Published
|
||||||
|
fileprivate(set) var installationProgress = [String: Progress]()
|
||||||
|
|
||||||
|
@Published
|
||||||
|
fileprivate(set) var refreshProgress = [String: Progress]()
|
||||||
|
}
|
||||||
|
|
||||||
class AppManager
|
class AppManager
|
||||||
{
|
{
|
||||||
static let shared = AppManager()
|
static let shared = AppManager()
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
private(set) lazy var publisher: AppManagerPublisher = AppManagerPublisher()
|
||||||
|
|
||||||
private let operationQueue = OperationQueue()
|
private let operationQueue = OperationQueue()
|
||||||
private let serialOperationQueue = OperationQueue()
|
private let serialOperationQueue = OperationQueue()
|
||||||
|
|
||||||
|
private var installationProgress = [String: Progress]() {
|
||||||
|
didSet {
|
||||||
|
guard #available(iOS 13, *) else { return }
|
||||||
|
self.publisher.installationProgress = self.installationProgress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var refreshProgress = [String: Progress]() {
|
||||||
|
didSet {
|
||||||
|
guard #available(iOS 13, *) else { return }
|
||||||
|
self.publisher.refreshProgress = self.refreshProgress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var installationProgress = [String: Progress]()
|
@available(iOS 13.0, *)
|
||||||
private var refreshProgress = [String: Progress]()
|
private lazy var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
@@ -39,6 +66,28 @@ class AppManager
|
|||||||
|
|
||||||
self.serialOperationQueue.name = "com.altstore.AppManager.serialOperationQueue"
|
self.serialOperationQueue.name = "com.altstore.AppManager.serialOperationQueue"
|
||||||
self.serialOperationQueue.maxConcurrentOperationCount = 1
|
self.serialOperationQueue.maxConcurrentOperationCount = 1
|
||||||
|
|
||||||
|
if #available(iOS 13, *)
|
||||||
|
{
|
||||||
|
self.prepareSubscriptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
func prepareSubscriptions()
|
||||||
|
{
|
||||||
|
self.publisher.$refreshProgress
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.map(\.keys)
|
||||||
|
.flatMap { (bundleIDs) in
|
||||||
|
DatabaseManager.shared.viewContext.registeredObjects.publisher
|
||||||
|
.compactMap { $0 as? InstalledApp }
|
||||||
|
.map { ($0, bundleIDs) }
|
||||||
|
}
|
||||||
|
.sink { (installedApp, bundleIDs) in
|
||||||
|
installedApp.isRefreshing = bundleIDs.contains(installedApp.bundleIdentifier)
|
||||||
|
}
|
||||||
|
.store(in: &self.cancellables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +549,17 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AppManager
|
||||||
|
{
|
||||||
|
func backgroundRefresh(_ installedApps: [InstalledApp], presentsNotifications: Bool = true, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||||
|
{
|
||||||
|
let backgroundRefreshAppsOperation = BackgroundRefreshAppsOperation(installedApps: installedApps)
|
||||||
|
backgroundRefreshAppsOperation.resultHandler = completionHandler
|
||||||
|
backgroundRefreshAppsOperation.presentsFinishedNotification = presentsNotifications
|
||||||
|
self.run([backgroundRefreshAppsOperation], context: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extension AppManager
|
private extension AppManager
|
||||||
{
|
{
|
||||||
enum AppOperation
|
enum AppOperation
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
{
|
{
|
||||||
struct FetchSourcesError: LocalizedError, CustomNSError
|
struct FetchSourcesError: LocalizedError, CustomNSError
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
|
import Intents
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
import Roxas
|
|
||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import Roxas
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
@@ -654,6 +654,15 @@ private extension MyAppsViewController
|
|||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
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)
|
@IBAction func updateApp(_ sender: UIButton)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
import Roxas
|
import Roxas
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
enum AuthenticationError: LocalizedError
|
||||||
|
|||||||
272
AltStore/Operations/BackgroundRefreshAppsOperation.swift
Normal file
272
AltStore/Operations/BackgroundRefreshAppsOperation.swift
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
//
|
||||||
|
// BackgroundRefreshAppsOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/6/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
enum RefreshError: LocalizedError
|
||||||
|
{
|
||||||
|
case noInstalledApps
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension CFNotificationName
|
||||||
|
{
|
||||||
|
static let requestAppState = CFNotificationName("com.altstore.RequestAppState" as CFString)
|
||||||
|
static let appIsRunning = CFNotificationName("com.altstore.AppState.Running" as CFString)
|
||||||
|
|
||||||
|
static func requestAppState(for appID: String) -> CFNotificationName
|
||||||
|
{
|
||||||
|
let name = String(CFNotificationName.requestAppState.rawValue) + "." + appID
|
||||||
|
return CFNotificationName(name as CFString)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func appIsRunning(for appID: String) -> CFNotificationName
|
||||||
|
{
|
||||||
|
let name = String(CFNotificationName.appIsRunning.rawValue) + "." + appID
|
||||||
|
return CFNotificationName(name as CFString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||||
|
{ (center, observer, name, object, userInfo) in
|
||||||
|
guard let name = name, let observer = observer else { return }
|
||||||
|
|
||||||
|
let operation = unsafeBitCast(observer, to: BackgroundRefreshAppsOperation.self)
|
||||||
|
operation.receivedApplicationState(notification: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(BackgroundRefreshAppsOperation)
|
||||||
|
class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
|
||||||
|
{
|
||||||
|
let installedApps: [InstalledApp]
|
||||||
|
private let managedObjectContext: NSManagedObjectContext
|
||||||
|
|
||||||
|
var presentsFinishedNotification: Bool = true
|
||||||
|
|
||||||
|
private let refreshIdentifier: String = UUID().uuidString
|
||||||
|
private var runningApplications: Set<String> = []
|
||||||
|
|
||||||
|
init(installedApps: [InstalledApp])
|
||||||
|
{
|
||||||
|
self.installedApps = installedApps
|
||||||
|
self.managedObjectContext = installedApps.compactMap({ $0.managedObjectContext }).first ?? DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func finish(_ result: Result<[String: Result<InstalledApp, Error>], Error>)
|
||||||
|
{
|
||||||
|
super.finish(result)
|
||||||
|
|
||||||
|
self.scheduleFinishedRefreshingNotification(for: result, delay: 0)
|
||||||
|
|
||||||
|
self.managedObjectContext.perform {
|
||||||
|
self.stopListeningForRunningApps()
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if UIApplication.shared.applicationState == .background
|
||||||
|
{
|
||||||
|
ServerManager.shared.stopDiscovering()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
guard !self.installedApps.isEmpty else {
|
||||||
|
self.finish(.failure(RefreshError.noInstalledApps))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ServerManager.shared.isDiscovering
|
||||||
|
{
|
||||||
|
ServerManager.shared.startDiscovering()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.managedObjectContext.perform {
|
||||||
|
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||||
|
|
||||||
|
self.startListeningForRunningApps()
|
||||||
|
|
||||||
|
// Wait for three seconds to:
|
||||||
|
// a) give us time to discover AltServers
|
||||||
|
// b) give other processes a chance to respond to requestAppState notification
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
||||||
|
self.managedObjectContext.perform {
|
||||||
|
guard !ServerManager.shared.discoveredServers.isEmpty else { return self.finish(.failure(ConnectionError.serverNotFound)) }
|
||||||
|
|
||||||
|
let filteredApps = self.installedApps.filter { !self.runningApplications.contains($0.bundleIdentifier) }
|
||||||
|
print("Filtered Apps to Refresh:", filteredApps.map { $0.bundleIdentifier })
|
||||||
|
|
||||||
|
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
|
||||||
|
group.beginInstallationHandler = { (installedApp) in
|
||||||
|
guard installedApp.bundleIdentifier == StoreApp.altstoreAppID else { return }
|
||||||
|
|
||||||
|
// We're starting to install AltStore, which means the app is about to quit.
|
||||||
|
// So, we schedule a "refresh successful" local notification to be displayed after a delay,
|
||||||
|
// but if the app is still running, we cancel the notification.
|
||||||
|
// Then, we schedule another notification and repeat the process.
|
||||||
|
|
||||||
|
// Also since AltServer has already received the app, it can finish installing even if we're no longer running in background.
|
||||||
|
|
||||||
|
if let error = group.context.error
|
||||||
|
{
|
||||||
|
self.scheduleFinishedRefreshingNotification(for: .failure(error))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var results = group.results
|
||||||
|
results[installedApp.bundleIdentifier] = .success(installedApp)
|
||||||
|
|
||||||
|
self.scheduleFinishedRefreshingNotification(for: .success(results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.completionHandler = { (results) in
|
||||||
|
self.finish(.success(results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension BackgroundRefreshAppsOperation
|
||||||
|
{
|
||||||
|
func startListeningForRunningApps()
|
||||||
|
{
|
||||||
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
|
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||||
|
|
||||||
|
for installedApp in self.installedApps
|
||||||
|
{
|
||||||
|
let appIsRunningNotification = CFNotificationName.appIsRunning(for: installedApp.bundleIdentifier)
|
||||||
|
CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedApplicationState, appIsRunningNotification.rawValue, nil, .deliverImmediately)
|
||||||
|
|
||||||
|
let requestAppStateNotification = CFNotificationName.requestAppState(for: installedApp.bundleIdentifier)
|
||||||
|
CFNotificationCenterPostNotification(notificationCenter, requestAppStateNotification, nil, nil, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopListeningForRunningApps()
|
||||||
|
{
|
||||||
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
|
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||||
|
|
||||||
|
for installedApp in self.installedApps
|
||||||
|
{
|
||||||
|
let appIsRunningNotification = CFNotificationName.appIsRunning(for: installedApp.bundleIdentifier)
|
||||||
|
CFNotificationCenterRemoveObserver(notificationCenter, observer, appIsRunningNotification, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func receivedApplicationState(notification: CFNotificationName)
|
||||||
|
{
|
||||||
|
let baseName = String(CFNotificationName.appIsRunning.rawValue)
|
||||||
|
|
||||||
|
let appID = String(notification.rawValue).replacingOccurrences(of: baseName + ".", with: "")
|
||||||
|
self.runningApplications.insert(appID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleFinishedRefreshingNotification(for result: Result<[String: Result<InstalledApp, Error>], Error>, delay: TimeInterval = 5)
|
||||||
|
{
|
||||||
|
func scheduleFinishedRefreshingNotification()
|
||||||
|
{
|
||||||
|
self.cancelFinishedRefreshingNotification()
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
|
var shouldPresentAlert = true
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let results = try result.get()
|
||||||
|
shouldPresentAlert = !results.isEmpty
|
||||||
|
|
||||||
|
for (_, result) in results
|
||||||
|
{
|
||||||
|
guard case let .failure(error) = result else { continue }
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||||
|
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||||
|
}
|
||||||
|
catch ConnectionError.serverNotFound
|
||||||
|
{
|
||||||
|
shouldPresentAlert = false
|
||||||
|
}
|
||||||
|
catch RefreshError.noInstalledApps
|
||||||
|
{
|
||||||
|
shouldPresentAlert = false
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to refresh apps in background.", error)
|
||||||
|
|
||||||
|
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||||
|
content.body = error.localizedDescription
|
||||||
|
|
||||||
|
shouldPresentAlert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldPresentAlert
|
||||||
|
{
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: self.refreshIdentifier, content: content, trigger: trigger)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
|
||||||
|
if delay > 0
|
||||||
|
{
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
|
||||||
|
UNUserNotificationCenter.current().getPendingNotificationRequests() { (requests) in
|
||||||
|
// If app is still running at this point, we schedule another notification with same identifier.
|
||||||
|
// This prevents the currently scheduled notification from displaying, and starts another countdown timer.
|
||||||
|
// First though, make sure there _is_ still a pending request, otherwise it's been cancelled
|
||||||
|
// and we should stop polling.
|
||||||
|
guard requests.contains(where: { $0.identifier == self.refreshIdentifier }) else { return }
|
||||||
|
|
||||||
|
scheduleFinishedRefreshingNotification()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.presentsFinishedNotification
|
||||||
|
{
|
||||||
|
scheduleFinishedRefreshingNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform synchronously to ensure app doesn't quit before we've finishing saving to disk.
|
||||||
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
|
context.performAndWait {
|
||||||
|
_ = RefreshAttempt(identifier: self.refreshIdentifier, result: result, context: context)
|
||||||
|
|
||||||
|
do { try context.save() }
|
||||||
|
catch { print("Failed to save refresh attempt.", error) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelFinishedRefreshingNotification()
|
||||||
|
{
|
||||||
|
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [self.refreshIdentifier])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
extension BackupAppOperation
|
extension BackupAppOperation
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(DeactivateAppOperation)
|
@objc(DeactivateAppOperation)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
@objc(DownloadAppOperation)
|
@objc(DownloadAppOperation)
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchAnisetteDataOperation)
|
@objc(FetchAnisetteDataOperation)
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchAppIDsOperation)
|
@objc(FetchAppIDsOperation)
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Roxas
|
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchProvisioningProfilesOperation)
|
@objc(FetchProvisioningProfilesOperation)
|
||||||
class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
|
class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchSourceOperation)
|
@objc(FetchSourceOperation)
|
||||||
@@ -49,7 +50,7 @@ class FetchSourceOperation: ResultOperation<Source>
|
|||||||
{
|
{
|
||||||
let (data, _) = try Result((data, response), error).get()
|
let (data, _) = try Result((data, response), error).get()
|
||||||
|
|
||||||
let decoder = JSONDecoder()
|
let decoder = AltStoreCore.JSONDecoder()
|
||||||
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
|
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
let text = try container.decode(String.self)
|
let text = try container.decode(String.self)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltKit
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
private let ReceivedServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
private let ReceivedServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Foundation
|
|||||||
import CoreData
|
import CoreData
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
class OperationContext
|
class OperationContext
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(RefreshAppOperation)
|
@objc(RefreshAppOperation)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
class RefreshGroup: NSObject
|
class RefreshGroup: NSObject
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import AltKit
|
|
||||||
|
|
||||||
@objc(RemoveAppBackupOperation)
|
@objc(RemoveAppBackupOperation)
|
||||||
class RemoveAppBackupOperation: ResultOperation<Void>
|
class RemoveAppBackupOperation: ResultOperation<Void>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
|
|
||||||
@objc(RemoveAppOperation)
|
@objc(RemoveAppOperation)
|
||||||
class RemoveAppOperation: ResultOperation<InstalledApp>
|
class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
@objc(ResignAppOperation)
|
@objc(ResignAppOperation)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
|
|
||||||
@objc(SendAppOperation)
|
@objc(SendAppOperation)
|
||||||
class SendAppOperation: ResultOperation<ServerConnection>
|
class SendAppOperation: ResultOperation<ServerConnection>
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
enum VerificationError: ALTLocalizedError
|
enum VerificationError: ALTLocalizedError
|
||||||
|
|||||||
134
AltStore/SceneDelegate.swift
Normal file
134
AltStore/SceneDelegate.swift
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
//
|
||||||
|
// SceneDelegate.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/6/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||||
|
{
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
|
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
|
||||||
|
{
|
||||||
|
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||||
|
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||||
|
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||||
|
guard let _ = (scene as? UIWindowScene) else { return }
|
||||||
|
|
||||||
|
if let context = connectionOptions.urlContexts.first
|
||||||
|
{
|
||||||
|
self.open(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneWillEnterForeground(_ scene: UIScene)
|
||||||
|
{
|
||||||
|
// Called as the scene transitions from the background to the foreground.
|
||||||
|
// Use this method to undo the changes made on entering the background.
|
||||||
|
|
||||||
|
// applicationWillEnterForeground is _not_ called when launching app,
|
||||||
|
// whereas sceneWillEnterForeground _is_ called when launching.
|
||||||
|
// As a result, DatabaseManager might not be started yet, so just return if it isn't
|
||||||
|
// (since all these methods are called separately during app startup).
|
||||||
|
guard DatabaseManager.shared.isStarted else { return }
|
||||||
|
|
||||||
|
AppManager.shared.update()
|
||||||
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
|
PatreonAPI.shared.refreshPatreonAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneDidEnterBackground(_ scene: UIScene)
|
||||||
|
{
|
||||||
|
// Called as the scene transitions from the foreground to the background.
|
||||||
|
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||||
|
// to restore the scene back to its current state.
|
||||||
|
|
||||||
|
guard UIApplication.shared.applicationState == .background else { return }
|
||||||
|
|
||||||
|
ServerManager.shared.stopDiscovering()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
|
||||||
|
{
|
||||||
|
guard let context = URLContexts.first else { return }
|
||||||
|
self.open(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
private extension SceneDelegate
|
||||||
|
{
|
||||||
|
func open(_ context: UIOpenURLContext)
|
||||||
|
{
|
||||||
|
if context.url.isFileURL
|
||||||
|
{
|
||||||
|
guard context.url.pathExtension.lowercased() == "ipa" else { return }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: context.url])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
guard let components = URLComponents(url: context.url, resolvingAgainstBaseURL: false) else { return }
|
||||||
|
guard let host = components.host?.lowercased() else { return }
|
||||||
|
|
||||||
|
switch host
|
||||||
|
{
|
||||||
|
case "patreon":
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "appbackupresponse":
|
||||||
|
let result: Result<Void, Error>
|
||||||
|
|
||||||
|
switch context.url.path.lowercased()
|
||||||
|
{
|
||||||
|
case "/success": result = .success(())
|
||||||
|
case "/failure":
|
||||||
|
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name] = $1.value } ?? [:]
|
||||||
|
guard
|
||||||
|
let errorDomain = queryItems["errorDomain"],
|
||||||
|
let errorCodeString = queryItems["errorCode"], let errorCode = Int(errorCodeString),
|
||||||
|
let errorDescription = queryItems["errorDescription"]
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let error = NSError(domain: errorDomain, code: errorCode, userInfo: [NSLocalizedDescriptionKey: errorDescription])
|
||||||
|
result = .failure(error)
|
||||||
|
|
||||||
|
default: return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.appBackupDidFinish, object: nil, userInfo: [AppDelegate.appBackupResultKey: result])
|
||||||
|
}
|
||||||
|
|
||||||
|
case "install":
|
||||||
|
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||||
|
guard let downloadURLString = queryItems["url"], let downloadURL = URL(string: downloadURLString) else { return }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: downloadURL])
|
||||||
|
}
|
||||||
|
|
||||||
|
case "source":
|
||||||
|
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||||
|
guard let sourceURLString = queryItems["url"], let sourceURL = URL(string: sourceURLString) else { return }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL])
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
|
||||||
|
|
||||||
enum ConnectionError: LocalizedError
|
enum ConnectionError: LocalizedError
|
||||||
{
|
{
|
||||||
case serverNotFound
|
case serverNotFound
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
|
|
||||||
class ServerConnection
|
class ServerConnection
|
||||||
{
|
{
|
||||||
@@ -95,7 +95,7 @@ class ServerConnection
|
|||||||
{
|
{
|
||||||
let data = try self.process(data: data, error: error)
|
let data = try self.process(data: data, error: error)
|
||||||
|
|
||||||
let response = try JSONDecoder().decode(ServerResponse.self, from: data)
|
let response = try AltStoreCore.JSONDecoder().decode(ServerResponse.self, from: data)
|
||||||
completionHandler(.success(response))
|
completionHandler(.success(response))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltKit
|
import AltStoreCore
|
||||||
|
|
||||||
class ServerManager: NSObject
|
class ServerManager: NSObject
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import UIKit
|
|||||||
import SafariServices
|
import SafariServices
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
extension PatreonViewController
|
extension PatreonViewController
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(RefreshAttemptTableViewCell)
|
@objc(RefreshAttemptTableViewCell)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17124"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="AltStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="AltStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||||
<rect key="frame" x="0.0" y="996.5" width="375" height="25"/>
|
<rect key="frame" x="0.0" y="1047.5" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Riley Testut" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="CnN-M1-AYK">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Riley Testut" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="CnN-M1-AYK">
|
||||||
<rect key="frame" x="251.5" y="16" width="93.5" height="20.5"/>
|
<rect key="frame" x="252.5" y="16" width="92.5" height="20.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="riley@altstore.io" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="0uP-Cd-tNX">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="riley@altstore.io" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="0uP-Cd-tNX">
|
||||||
<rect key="frame" x="215" y="16" width="130" height="20.5"/>
|
<rect key="frame" x="215.5" y="16" width="129.5" height="20.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Developer" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="434-MW-Den">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Developer" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="434-MW-Den">
|
||||||
<rect key="frame" x="263.5" y="16" width="81.5" height="20.5"/>
|
<rect key="frame" x="264" y="16" width="81" height="20.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||||
<rect key="frame" x="30" y="15" width="106.5" height="21"/>
|
<rect key="frame" x="30" y="15" width="106" height="21"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Background Refresh" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbG-HB-IOn">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Background Refresh" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbG-HB-IOn">
|
||||||
<rect key="frame" x="30" y="15" width="167" height="21"/>
|
<rect key="frame" x="30" y="15" width="166" height="21"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -230,17 +230,45 @@
|
|||||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
<userDefinedRuntimeAttributes>
|
<userDefinedRuntimeAttributes>
|
||||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
<integer key="value" value="0"/>
|
<integer key="value" value="1"/>
|
||||||
</userDefinedRuntimeAttribute>
|
</userDefinedRuntimeAttribute>
|
||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="461.5" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||||
|
<rect key="frame" x="30" y="15" width="101" height="21"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="c6K-fI-CVr" firstAttribute="centerY" secondItem="GEO-2e-E4k" secondAttribute="centerY" id="IGB-ox-RAM"/>
|
||||||
|
<constraint firstItem="c6K-fI-CVr" firstAttribute="leading" secondItem="GEO-2e-E4k" secondAttribute="leadingMargin" id="xoI-eB-1TH"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="3"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
</tableViewSection>
|
</tableViewSection>
|
||||||
<tableViewSection headerTitle="" id="9ht-ML-85l">
|
<tableViewSection headerTitle="" id="9ht-ML-85l">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="wYS-qW-u8S" rowHeight="51" style="IBUITableViewCellStyleDefault" id="ndZ-OC-MWv" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="wYS-qW-u8S" rowHeight="51" style="IBUITableViewCellStyleDefault" id="ndZ-OC-MWv" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="501.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="552.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ndZ-OC-MWv" id="Tuq-2M-9df">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ndZ-OC-MWv" id="Tuq-2M-9df">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -270,14 +298,14 @@
|
|||||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="592.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="643.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
|
||||||
<rect key="frame" x="30" y="15" width="106" height="21"/>
|
<rect key="frame" x="30" y="15" width="105" height="21"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -310,7 +338,7 @@
|
|||||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="683.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="734.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -323,16 +351,16 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||||
<rect key="frame" x="219.5" y="15.5" width="125.5" height="20.5"/>
|
<rect key="frame" x="220.5" y="15.5" width="124.5" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Riley Testut" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Riley Testut" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="93.5" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="92.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||||
<rect key="frame" x="107.5" y="0.0" width="18" height="20.5"/>
|
<rect key="frame" x="106.5" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
@@ -354,7 +382,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="734.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="785.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -367,16 +395,16 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||||
<rect key="frame" x="191.5" y="15.5" width="153.5" height="20.5"/>
|
<rect key="frame" x="192.5" y="15.5" width="152.5" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Caroline Moore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Caroline Moore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="121.5" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="120.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||||
<rect key="frame" x="135.5" y="0.0" width="18" height="20.5"/>
|
<rect key="frame" x="134.5" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
@@ -398,7 +426,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="785.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="836.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -438,7 +466,7 @@
|
|||||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="876.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="927.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -471,14 +499,14 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="927.5" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="978.5" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||||
<rect key="frame" x="30" y="15" width="189" height="21"/>
|
<rect key="frame" x="30" y="15" width="187.5" height="21"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -581,9 +609,9 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Success" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SWb-Of-t97">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Success" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SWb-Of-t97">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="67.5" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="67" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" systemColor="darkTextColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Date" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4MX-Qv-H8V">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Date" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4MX-Qv-H8V">
|
||||||
@@ -721,6 +749,7 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="o3f-Lj-IHF"/>
|
||||||
<color key="backgroundColor" name="SettingsBackground"/>
|
<color key="backgroundColor" name="SettingsBackground"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="oQQ-pR-oKc" firstAttribute="top" secondItem="o3f-Lj-IHF" secondAttribute="top" id="3gx-wh-Lol"/>
|
<constraint firstItem="oQQ-pR-oKc" firstAttribute="top" secondItem="o3f-Lj-IHF" secondAttribute="top" id="3gx-wh-Lol"/>
|
||||||
@@ -728,7 +757,6 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<constraint firstItem="oQQ-pR-oKc" firstAttribute="leading" secondItem="o3f-Lj-IHF" secondAttribute="leading" id="PIZ-YA-eVd"/>
|
<constraint firstItem="oQQ-pR-oKc" firstAttribute="leading" secondItem="o3f-Lj-IHF" secondAttribute="leading" id="PIZ-YA-eVd"/>
|
||||||
<constraint firstItem="oQQ-pR-oKc" firstAttribute="trailing" secondItem="o3f-Lj-IHF" secondAttribute="trailing" id="oUR-b9-ajN"/>
|
<constraint firstItem="oQQ-pR-oKc" firstAttribute="trailing" secondItem="o3f-Lj-IHF" secondAttribute="trailing" id="oUR-b9-ajN"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<viewLayoutGuide key="safeArea" id="o3f-Lj-IHF"/>
|
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" title="Software Licenses" largeTitleDisplayMode="never" id="JcT-wX-zay"/>
|
<navigationItem key="navigationItem" title="Software Licenses" largeTitleDisplayMode="never" id="JcT-wX-zay"/>
|
||||||
<connections>
|
<connections>
|
||||||
@@ -807,5 +835,8 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<namedColor name="SettingsBackground">
|
<namedColor name="SettingsBackground">
|
||||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
|
<systemColor name="darkTextColor">
|
||||||
|
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import MessageUI
|
import MessageUI
|
||||||
|
import Intents
|
||||||
|
import IntentsUI
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
extension SettingsViewController
|
extension SettingsViewController
|
||||||
{
|
{
|
||||||
@@ -17,13 +21,26 @@ extension SettingsViewController
|
|||||||
case signIn
|
case signIn
|
||||||
case account
|
case account
|
||||||
case patreon
|
case patreon
|
||||||
case backgroundRefresh
|
case appRefresh
|
||||||
case jailbreak
|
case jailbreak
|
||||||
case instructions
|
case instructions
|
||||||
case credits
|
case credits
|
||||||
case debug
|
case debug
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||||
|
{
|
||||||
|
case backgroundRefresh
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
case addToSiri
|
||||||
|
|
||||||
|
static var allCases: [AppRefreshRow] {
|
||||||
|
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
||||||
|
return [.backgroundRefresh, .addToSiri]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate enum CreditsRow: Int, CaseIterable
|
fileprivate enum CreditsRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case developer
|
case developer
|
||||||
@@ -165,8 +182,15 @@ private extension SettingsViewController
|
|||||||
settingsHeaderFooterView.button.addTarget(self, action: #selector(SettingsViewController.signOut(_:)), for: .primaryActionTriggered)
|
settingsHeaderFooterView.button.addTarget(self, action: #selector(SettingsViewController.signOut(_:)), for: .primaryActionTriggered)
|
||||||
settingsHeaderFooterView.button.isHidden = false
|
settingsHeaderFooterView.button.isHidden = false
|
||||||
|
|
||||||
case .backgroundRefresh:
|
case .appRefresh:
|
||||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Automatically refresh apps in the background when connected to the same WiFi as AltServer.", comment: "")
|
if isHeader
|
||||||
|
{
|
||||||
|
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("REFRESHING APPS", comment: "")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to the same WiFi as AltServer.", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
case .jailbreak:
|
case .jailbreak:
|
||||||
if isHeader
|
if isHeader
|
||||||
@@ -254,6 +278,17 @@ private extension SettingsViewController
|
|||||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
@IBAction func addRefreshAppsShortcut()
|
||||||
|
{
|
||||||
|
guard let shortcut = INShortcut(intent: INInteraction.refreshAllApps().intent) else { return }
|
||||||
|
|
||||||
|
let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcut)
|
||||||
|
viewController.delegate = self
|
||||||
|
viewController.modalPresentationStyle = .formSheet
|
||||||
|
self.present(viewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||||
{
|
{
|
||||||
self.debugGestureCounter += 1
|
self.debugGestureCounter += 1
|
||||||
@@ -331,11 +366,28 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
case .signIn: return (self.activeTeam == nil) ? 1 : 0
|
case .signIn: return (self.activeTeam == nil) ? 1 : 0
|
||||||
case .account: return (self.activeTeam == nil) ? 0 : 3
|
case .account: return (self.activeTeam == nil) ? 0 : 3
|
||||||
|
case .appRefresh: return AppRefreshRow.allCases.count
|
||||||
case .jailbreak: return UIDevice.current.isJailbroken ? 1 : 0
|
case .jailbreak: return UIDevice.current.isJailbroken ? 1 : 0
|
||||||
default: return super.tableView(tableView, numberOfRowsInSection: section.rawValue)
|
default: return super.tableView(tableView, numberOfRowsInSection: section.rawValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
|
||||||
|
{
|
||||||
|
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||||
|
|
||||||
|
if #available(iOS 14, *) {}
|
||||||
|
else if let cell = cell as? InsetGroupTableViewCell,
|
||||||
|
indexPath.section == Section.appRefresh.rawValue,
|
||||||
|
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||||
|
{
|
||||||
|
// Only one row is visible pre-iOS 14.
|
||||||
|
cell.style = .single
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
|
||||||
{
|
{
|
||||||
let section = Section.allCases[section]
|
let section = Section.allCases[section]
|
||||||
@@ -345,12 +397,12 @@ extension SettingsViewController
|
|||||||
case .account where self.activeTeam == nil: return nil
|
case .account where self.activeTeam == nil: return nil
|
||||||
case .jailbreak where !UIDevice.current.isJailbroken: return nil
|
case .jailbreak where !UIDevice.current.isJailbroken: return nil
|
||||||
|
|
||||||
case .signIn, .account, .patreon, .jailbreak, .credits, .debug:
|
case .signIn, .account, .patreon, .appRefresh, .jailbreak, .credits, .debug:
|
||||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||||
self.prepare(headerView, for: section, isHeader: true)
|
self.prepare(headerView, for: section, isHeader: true)
|
||||||
return headerView
|
return headerView
|
||||||
|
|
||||||
case .backgroundRefresh, .instructions: return nil
|
case .instructions: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +414,7 @@ extension SettingsViewController
|
|||||||
case .signIn where self.activeTeam != nil: return nil
|
case .signIn where self.activeTeam != nil: return nil
|
||||||
case .jailbreak where !UIDevice.current.isJailbroken: return nil
|
case .jailbreak where !UIDevice.current.isJailbroken: return nil
|
||||||
|
|
||||||
case .signIn, .patreon, .backgroundRefresh, .jailbreak:
|
case .signIn, .patreon, .appRefresh, .jailbreak:
|
||||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||||
self.prepare(footerView, for: section, isHeader: false)
|
self.prepare(footerView, for: section, isHeader: false)
|
||||||
return footerView
|
return footerView
|
||||||
@@ -380,11 +432,11 @@ extension SettingsViewController
|
|||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .jailbreak where !UIDevice.current.isJailbroken: return 1.0
|
case .jailbreak where !UIDevice.current.isJailbroken: return 1.0
|
||||||
|
|
||||||
case .signIn, .account, .patreon, .jailbreak, .credits, .debug:
|
case .signIn, .account, .patreon, .appRefresh, .jailbreak, .credits, .debug:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||||
return height
|
return height
|
||||||
|
|
||||||
case .backgroundRefresh, .instructions: return 0.0
|
case .instructions: return 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +449,7 @@ extension SettingsViewController
|
|||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .jailbreak where !UIDevice.current.isJailbroken: return 1.0
|
case .jailbreak where !UIDevice.current.isJailbroken: return 1.0
|
||||||
|
|
||||||
case .signIn, .patreon, .backgroundRefresh, .jailbreak:
|
case .signIn, .patreon, .appRefresh, .jailbreak:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||||
return height
|
return height
|
||||||
|
|
||||||
@@ -415,6 +467,16 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
case .signIn: self.signIn()
|
case .signIn: self.signIn()
|
||||||
case .instructions: break
|
case .instructions: break
|
||||||
|
case .appRefresh:
|
||||||
|
let row = AppRefreshRow.allCases[indexPath.row]
|
||||||
|
switch row
|
||||||
|
{
|
||||||
|
case .backgroundRefresh: break
|
||||||
|
case .addToSiri:
|
||||||
|
guard #available(iOS 14, *) else { return }
|
||||||
|
self.addRefreshAppsShortcut()
|
||||||
|
}
|
||||||
|
|
||||||
case .jailbreak:
|
case .jailbreak:
|
||||||
let fileURL = Bundle.main.url(forResource: "AltDaemon", withExtension: "deb")!
|
let fileURL = Bundle.main.url(forResource: "AltDaemon", withExtension: "deb")!
|
||||||
|
|
||||||
@@ -489,3 +551,31 @@ extension SettingsViewController: UIGestureRecognizerDelegate
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SettingsViewController: INUIAddVoiceShortcutViewControllerDelegate
|
||||||
|
{
|
||||||
|
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?)
|
||||||
|
{
|
||||||
|
if let indexPath = self.tableView.indexPathForSelectedRow
|
||||||
|
{
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.dismiss(animated: true, completion: nil)
|
||||||
|
|
||||||
|
guard let error = error else { return }
|
||||||
|
|
||||||
|
let toastView = ToastView(error: error)
|
||||||
|
toastView.show(in: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController)
|
||||||
|
{
|
||||||
|
if let indexPath = self.tableView.indexPathForSelectedRow
|
||||||
|
{
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class SourcesViewController: UICollectionViewController
|
class SourcesViewController: UICollectionViewController
|
||||||
|
|||||||
27
AltStoreCore/AltStoreCore.h
Normal file
27
AltStoreCore/AltStoreCore.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AltStoreCore.h
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/3/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
//! Project version number for AltStoreCore.
|
||||||
|
FOUNDATION_EXPORT double AltStoreCoreVersionNumber;
|
||||||
|
|
||||||
|
//! Project version string for AltStoreCore.
|
||||||
|
FOUNDATION_EXPORT const unsigned char AltStoreCoreVersionString[];
|
||||||
|
|
||||||
|
// In this header, you should import all the public headers of your framework using statements like #import <AltStoreCore/PublicHeader.h>
|
||||||
|
|
||||||
|
#import <AltStoreCore/ALTAppPermission.h>
|
||||||
|
#import <AltStoreCore/ALTSourceUserInfoKey.h>
|
||||||
|
#import <AltStoreCore/ALTPatreonBenefitType.h>
|
||||||
|
|
||||||
|
// Shared
|
||||||
|
#import <AltStoreCore/ALTConstants.h>
|
||||||
|
#import <AltStoreCore/ALTConnection.h>
|
||||||
|
#import <AltStoreCore/NSError+ALTServerError.h>
|
||||||
|
#import <AltStoreCore/CFNotificationName+AltStore.h>
|
||||||
@@ -12,11 +12,11 @@ import KeychainAccess
|
|||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
struct KeychainItem<Value>
|
public struct KeychainItem<Value>
|
||||||
{
|
{
|
||||||
let key: String
|
public let key: String
|
||||||
|
|
||||||
var wrappedValue: Value? {
|
public var wrappedValue: Value? {
|
||||||
get {
|
get {
|
||||||
switch Value.self
|
switch Value.self
|
||||||
{
|
{
|
||||||
@@ -35,50 +35,50 @@ struct KeychainItem<Value>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(key: String)
|
public init(key: String)
|
||||||
{
|
{
|
||||||
self.key = key
|
self.key = key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Keychain
|
public class Keychain
|
||||||
{
|
{
|
||||||
static let shared = Keychain()
|
public static let shared = Keychain()
|
||||||
|
|
||||||
fileprivate let keychain = KeychainAccess.Keychain(service: "com.rileytestut.AltStore").accessibility(.afterFirstUnlock).synchronizable(true)
|
fileprivate let keychain = KeychainAccess.Keychain(service: "com.rileytestut.AltStore").accessibility(.afterFirstUnlock).synchronizable(true)
|
||||||
|
|
||||||
@KeychainItem(key: "appleIDEmailAddress")
|
@KeychainItem(key: "appleIDEmailAddress")
|
||||||
var appleIDEmailAddress: String?
|
public var appleIDEmailAddress: String?
|
||||||
|
|
||||||
@KeychainItem(key: "appleIDPassword")
|
@KeychainItem(key: "appleIDPassword")
|
||||||
var appleIDPassword: String?
|
public var appleIDPassword: String?
|
||||||
|
|
||||||
@KeychainItem(key: "signingCertificatePrivateKey")
|
@KeychainItem(key: "signingCertificatePrivateKey")
|
||||||
var signingCertificatePrivateKey: Data?
|
public var signingCertificatePrivateKey: Data?
|
||||||
|
|
||||||
@KeychainItem(key: "signingCertificateSerialNumber")
|
@KeychainItem(key: "signingCertificateSerialNumber")
|
||||||
var signingCertificateSerialNumber: String?
|
public var signingCertificateSerialNumber: String?
|
||||||
|
|
||||||
@KeychainItem(key: "signingCertificate")
|
@KeychainItem(key: "signingCertificate")
|
||||||
var signingCertificate: Data?
|
public var signingCertificate: Data?
|
||||||
|
|
||||||
@KeychainItem(key: "signingCertificatePassword")
|
@KeychainItem(key: "signingCertificatePassword")
|
||||||
var signingCertificatePassword: String?
|
public var signingCertificatePassword: String?
|
||||||
|
|
||||||
@KeychainItem(key: "patreonAccessToken")
|
@KeychainItem(key: "patreonAccessToken")
|
||||||
var patreonAccessToken: String?
|
public var patreonAccessToken: String?
|
||||||
|
|
||||||
@KeychainItem(key: "patreonRefreshToken")
|
@KeychainItem(key: "patreonRefreshToken")
|
||||||
var patreonRefreshToken: String?
|
public var patreonRefreshToken: String?
|
||||||
|
|
||||||
@KeychainItem(key: "patreonCreatorAccessToken")
|
@KeychainItem(key: "patreonCreatorAccessToken")
|
||||||
var patreonCreatorAccessToken: String?
|
public var patreonCreatorAccessToken: String?
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset()
|
public func reset()
|
||||||
{
|
{
|
||||||
self.appleIDEmailAddress = nil
|
self.appleIDEmailAddress = nil
|
||||||
self.appleIDPassword = nil
|
self.appleIDPassword = nil
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
extension CodingUserInfoKey
|
public extension CodingUserInfoKey
|
||||||
{
|
{
|
||||||
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")!
|
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")!
|
||||||
static let sourceURL = CodingUserInfoKey(rawValue: "sourceURL")!
|
static let sourceURL = CodingUserInfoKey(rawValue: "sourceURL")!
|
||||||
@@ -18,29 +18,29 @@ extension CodingUserInfoKey
|
|||||||
public final class JSONDecoder: Foundation.JSONDecoder
|
public final class JSONDecoder: Foundation.JSONDecoder
|
||||||
{
|
{
|
||||||
@DecoderItem(key: .managedObjectContext)
|
@DecoderItem(key: .managedObjectContext)
|
||||||
var managedObjectContext: NSManagedObjectContext?
|
public var managedObjectContext: NSManagedObjectContext?
|
||||||
|
|
||||||
@DecoderItem(key: .sourceURL)
|
@DecoderItem(key: .sourceURL)
|
||||||
var sourceURL: URL?
|
public var sourceURL: URL?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Decoder
|
public extension Decoder
|
||||||
{
|
{
|
||||||
var managedObjectContext: NSManagedObjectContext? { self.userInfo[.managedObjectContext] as? NSManagedObjectContext }
|
var managedObjectContext: NSManagedObjectContext? { self.userInfo[.managedObjectContext] as? NSManagedObjectContext }
|
||||||
var sourceURL: URL? { self.userInfo[.sourceURL] as? URL }
|
var sourceURL: URL? { self.userInfo[.sourceURL] as? URL }
|
||||||
}
|
}
|
||||||
|
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
struct DecoderItem<Value>
|
public struct DecoderItem<Value>
|
||||||
{
|
{
|
||||||
let key: CodingUserInfoKey
|
public let key: CodingUserInfoKey
|
||||||
|
|
||||||
var wrappedValue: Value? {
|
public var wrappedValue: Value? {
|
||||||
get { fatalError("only works on instance properties of classes") }
|
get { fatalError("only works on instance properties of classes") }
|
||||||
set { fatalError("only works on instance properties of classes") }
|
set { fatalError("only works on instance properties of classes") }
|
||||||
}
|
}
|
||||||
|
|
||||||
init(key: CodingUserInfoKey)
|
public init(key: CodingUserInfoKey)
|
||||||
{
|
{
|
||||||
self.key = key
|
self.key = key
|
||||||
}
|
}
|
||||||
17
AltStoreCore/Extensions/UIApplication+AppExtension.swift
Normal file
17
AltStoreCore/Extensions/UIApplication+AppExtension.swift
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// UIApplication+AppExtension.swift
|
||||||
|
// DeltaCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 6/14/18.
|
||||||
|
// Copyright © 2018 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public extension UIApplication
|
||||||
|
{
|
||||||
|
// Cannot normally use UIApplication.shared from extensions, so we get around this by calling value(forKey:).
|
||||||
|
class var alt_shared: UIApplication? {
|
||||||
|
return UIApplication.value(forKey: "sharedApplication") as? UIApplication
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIColor
|
public extension UIColor
|
||||||
{
|
{
|
||||||
// Borrowed from https://stackoverflow.com/a/26341062
|
// Borrowed from https://stackoverflow.com/a/26341062
|
||||||
var hexString: String {
|
var hexString: String {
|
||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
extension UserDefaults
|
public extension UserDefaults
|
||||||
{
|
{
|
||||||
@NSManaged var firstLaunch: Date?
|
@NSManaged var firstLaunch: Date?
|
||||||
|
|
||||||
22
AltStoreCore/Info.plist
Normal file
22
AltStoreCore/Info.plist
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -12,9 +12,9 @@ import CoreData
|
|||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
@objc(Account)
|
@objc(Account)
|
||||||
class Account: NSManagedObject, Fetchable
|
public class Account: NSManagedObject, Fetchable
|
||||||
{
|
{
|
||||||
var localizedName: String {
|
public var localizedName: String {
|
||||||
var components = PersonNameComponents()
|
var components = PersonNameComponents()
|
||||||
components.givenName = self.firstName
|
components.givenName = self.firstName
|
||||||
components.familyName = self.lastName
|
components.familyName = self.lastName
|
||||||
@@ -24,30 +24,30 @@ class Account: NSManagedObject, Fetchable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Properties */
|
/* Properties */
|
||||||
@NSManaged var appleID: String
|
@NSManaged public var appleID: String
|
||||||
@NSManaged var identifier: String
|
@NSManaged public var identifier: String
|
||||||
|
|
||||||
@NSManaged var firstName: String
|
@NSManaged public var firstName: String
|
||||||
@NSManaged var lastName: String
|
@NSManaged public var lastName: String
|
||||||
|
|
||||||
@NSManaged var isActiveAccount: Bool
|
@NSManaged public var isActiveAccount: Bool
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged var teams: Set<Team>
|
@NSManaged public var teams: Set<Team>
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ account: ALTAccount, context: NSManagedObjectContext)
|
public init(_ account: ALTAccount, context: NSManagedObjectContext)
|
||||||
{
|
{
|
||||||
super.init(entity: Account.entity(), insertInto: context)
|
super.init(entity: Account.entity(), insertInto: context)
|
||||||
|
|
||||||
self.update(account: account)
|
self.update(account: account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(account: ALTAccount)
|
public func update(account: ALTAccount)
|
||||||
{
|
{
|
||||||
self.appleID = account.appleID
|
self.appleID = account.appleID
|
||||||
self.identifier = account.identifier
|
self.identifier = account.identifier
|
||||||
@@ -57,7 +57,7 @@ class Account: NSManagedObject, Fetchable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Account
|
public extension Account
|
||||||
{
|
{
|
||||||
@nonobjc class func fetchRequest() -> NSFetchRequest<Account>
|
@nonobjc class func fetchRequest() -> NSFetchRequest<Account>
|
||||||
{
|
{
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>_XCCurrentVersionName</key>
|
<key>_XCCurrentVersionName</key>
|
||||||
<string>AltStore 7.xcdatamodel</string>
|
<string>AltStore 8.xcdatamodel</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -171,4 +171,4 @@
|
|||||||
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="343"/>
|
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="343"/>
|
||||||
<element name="Team" positionX="-45" positionY="81" width="128" height="148"/>
|
<element name="Team" positionX="-45" positionY="81" width="128" height="148"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17189" systemVersion="20A5354i" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
|
<entity name="Account" representedClassName="Account" syncable="YES">
|
||||||
|
<attribute name="appleID" attributeType="String"/>
|
||||||
|
<attribute name="firstName" attributeType="String"/>
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="isActiveAccount" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="lastName" attributeType="String"/>
|
||||||
|
<relationship name="teams" toMany="YES" deletionRule="Cascade" destinationEntity="Team" inverseName="account" inverseEntity="Team"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="AppID" representedClassName="AppID" syncable="YES">
|
||||||
|
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||||
|
<attribute name="expirationDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="features" attributeType="Transformable"/>
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="appIDs" inverseEntity="Team"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="AppPermission" representedClassName="AppPermission" syncable="YES">
|
||||||
|
<attribute name="type" attributeType="String"/>
|
||||||
|
<attribute name="usageDescription" attributeType="String"/>
|
||||||
|
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="permissions" inverseEntity="StoreApp"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
|
||||||
|
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||||
|
<attribute name="certificateSerialNumber" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="isActive" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="isRefreshing" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="resignedBundleIdentifier" attributeType="String"/>
|
||||||
|
<attribute name="version" attributeType="String"/>
|
||||||
|
<relationship name="appExtensions" toMany="YES" deletionRule="Cascade" destinationEntity="InstalledExtension" inverseName="parentApp" inverseEntity="InstalledExtension"/>
|
||||||
|
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="installedApp" inverseEntity="StoreApp"/>
|
||||||
|
<relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="installedApps" inverseEntity="Team"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="bundleIdentifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="InstalledExtension" representedClassName="InstalledExtension" syncable="YES">
|
||||||
|
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||||
|
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="resignedBundleIdentifier" attributeType="String"/>
|
||||||
|
<attribute name="version" attributeType="String"/>
|
||||||
|
<relationship name="parentApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="appExtensions" inverseEntity="InstalledApp"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="NewsItem" representedClassName="NewsItem" syncable="YES">
|
||||||
|
<attribute name="appID" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="caption" attributeType="String"/>
|
||||||
|
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="externalURL" optional="YES" attributeType="URI"/>
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="imageURL" optional="YES" attributeType="URI"/>
|
||||||
|
<attribute name="isSilent" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="sourceIdentifier" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="tintColor" optional="YES" attributeType="Transformable"/>
|
||||||
|
<attribute name="title" attributeType="String"/>
|
||||||
|
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="newsItems" inverseEntity="Source"/>
|
||||||
|
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="newsItems" inverseEntity="StoreApp"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
<constraint value="sourceIdentifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
|
||||||
|
<attribute name="firstName" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="isPatron" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="RefreshAttempt" representedClassName="RefreshAttempt" syncable="YES">
|
||||||
|
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="errorDescription" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="isSuccess" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="Source" representedClassName="Source" syncable="YES">
|
||||||
|
<attribute name="error" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<attribute name="sourceURL" attributeType="URI"/>
|
||||||
|
<relationship name="apps" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="StoreApp" inverseName="source" inverseEntity="StoreApp"/>
|
||||||
|
<relationship name="newsItems" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="NewsItem" inverseName="source" inverseEntity="NewsItem"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="StoreApp" representedClassName="StoreApp" syncable="YES">
|
||||||
|
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||||
|
<attribute name="developerName" attributeType="String"/>
|
||||||
|
<attribute name="downloadURL" attributeType="URI"/>
|
||||||
|
<attribute name="iconURL" attributeType="URI"/>
|
||||||
|
<attribute name="isBeta" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="localizedDescription" attributeType="String"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<attribute name="screenshotURLs" attributeType="Transformable"/>
|
||||||
|
<attribute name="size" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="sourceIdentifier" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="subtitle" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="tintColor" optional="YES" attributeType="Transformable"/>
|
||||||
|
<attribute name="version" attributeType="String"/>
|
||||||
|
<attribute name="versionDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="versionDescription" optional="YES" attributeType="String"/>
|
||||||
|
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp"/>
|
||||||
|
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem"/>
|
||||||
|
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission"/>
|
||||||
|
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="sourceIdentifier"/>
|
||||||
|
<constraint value="bundleIdentifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="Team" representedClassName="Team" syncable="YES">
|
||||||
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="isActiveTeam" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="teams" inverseEntity="Account"/>
|
||||||
|
<relationship name="appIDs" toMany="YES" deletionRule="Cascade" destinationEntity="AppID" inverseName="team" inverseEntity="AppID"/>
|
||||||
|
<relationship name="installedApps" toMany="YES" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="team" inverseEntity="InstalledApp"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<elements>
|
||||||
|
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
||||||
|
<element name="AppID" positionX="-27" positionY="153" width="128" height="133"/>
|
||||||
|
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
||||||
|
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="224"/>
|
||||||
|
<element name="InstalledExtension" positionX="-45" positionY="135" width="128" height="163"/>
|
||||||
|
<element name="NewsItem" positionX="-45" positionY="126" width="128" height="238"/>
|
||||||
|
<element name="PatreonAccount" positionX="-45" positionY="117" width="128" height="105"/>
|
||||||
|
<element name="RefreshAttempt" positionX="-45" positionY="117" width="128" height="105"/>
|
||||||
|
<element name="Source" positionX="-45" positionY="99" width="128" height="133"/>
|
||||||
|
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="343"/>
|
||||||
|
<element name="Team" positionX="-45" positionY="81" width="128" height="148"/>
|
||||||
|
</elements>
|
||||||
|
</model>
|
||||||
@@ -12,24 +12,24 @@ import CoreData
|
|||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
@objc(AppID)
|
@objc(AppID)
|
||||||
class AppID: NSManagedObject, Fetchable
|
public class AppID: NSManagedObject, Fetchable
|
||||||
{
|
{
|
||||||
/* Properties */
|
/* Properties */
|
||||||
@NSManaged var name: String
|
@NSManaged public var name: String
|
||||||
@NSManaged var identifier: String
|
@NSManaged public var identifier: String
|
||||||
@NSManaged var bundleIdentifier: String
|
@NSManaged public var bundleIdentifier: String
|
||||||
@NSManaged var features: [ALTFeature: Any]
|
@NSManaged public var features: [ALTFeature: Any]
|
||||||
@NSManaged var expirationDate: Date?
|
@NSManaged public var expirationDate: Date?
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged private(set) var team: Team?
|
@NSManaged public private(set) var team: Team?
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ appID: ALTAppID, team: Team, context: NSManagedObjectContext)
|
public init(_ appID: ALTAppID, team: Team, context: NSManagedObjectContext)
|
||||||
{
|
{
|
||||||
super.init(entity: AppID.entity(), insertInto: context)
|
super.init(entity: AppID.entity(), insertInto: context)
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class AppID: NSManagedObject, Fetchable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppID
|
public extension AppID
|
||||||
{
|
{
|
||||||
@nonobjc class func fetchRequest() -> NSFetchRequest<AppID>
|
@nonobjc class func fetchRequest() -> NSFetchRequest<AppID>
|
||||||
{
|
{
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import CoreData
|
import CoreData
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension ALTAppPermissionType
|
public extension ALTAppPermissionType
|
||||||
{
|
{
|
||||||
var localizedShortName: String? {
|
var localizedShortName: String? {
|
||||||
switch self
|
switch self
|
||||||
@@ -43,14 +43,14 @@ extension ALTAppPermissionType
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc(AppPermission)
|
@objc(AppPermission)
|
||||||
class AppPermission: NSManagedObject, Decodable, Fetchable
|
public class AppPermission: NSManagedObject, Decodable, Fetchable
|
||||||
{
|
{
|
||||||
/* Properties */
|
/* Properties */
|
||||||
@NSManaged var type: ALTAppPermissionType
|
@NSManaged public var type: ALTAppPermissionType
|
||||||
@NSManaged var usageDescription: String
|
@NSManaged public var usageDescription: String
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged private(set) var app: StoreApp!
|
@NSManaged public private(set) var app: StoreApp!
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ class AppPermission: NSManagedObject, Decodable, Fetchable
|
|||||||
case usageDescription
|
case usageDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws
|
public required init(from decoder: Decoder) throws
|
||||||
{
|
{
|
||||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class AppPermission: NSManagedObject, Decodable, Fetchable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppPermission
|
public extension AppPermission
|
||||||
{
|
{
|
||||||
@nonobjc class func fetchRequest() -> NSFetchRequest<AppPermission>
|
@nonobjc class func fetchRequest() -> NSFetchRequest<AppPermission>
|
||||||
{
|
{
|
||||||
@@ -20,10 +20,11 @@ public class DatabaseManager
|
|||||||
public private(set) var isStarted = false
|
public private(set) var isStarted = false
|
||||||
|
|
||||||
private var startCompletionHandlers = [(Error?) -> Void]()
|
private var startCompletionHandlers = [(Error?) -> Void]()
|
||||||
|
private let dispatchQueue = DispatchQueue(label: "io.altstore.DatabaseManager")
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
self.persistentContainer = RSTPersistentContainer(name: "AltStore")
|
self.persistentContainer = RSTPersistentContainer(name: "AltStore", bundle: Bundle(for: DatabaseManager.self))
|
||||||
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,30 +33,34 @@ public extension DatabaseManager
|
|||||||
{
|
{
|
||||||
func start(completionHandler: @escaping (Error?) -> Void)
|
func start(completionHandler: @escaping (Error?) -> Void)
|
||||||
{
|
{
|
||||||
self.startCompletionHandlers.append(completionHandler)
|
|
||||||
|
|
||||||
guard self.startCompletionHandlers.count == 1 else { return }
|
|
||||||
|
|
||||||
func finish(_ error: Error?)
|
func finish(_ error: Error?)
|
||||||
{
|
{
|
||||||
self.startCompletionHandlers.forEach { $0(error) }
|
self.dispatchQueue.async {
|
||||||
self.startCompletionHandlers.removeAll()
|
if error == nil
|
||||||
|
{
|
||||||
|
self.isStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.startCompletionHandlers.forEach { $0(error) }
|
||||||
|
self.startCompletionHandlers.removeAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !self.isStarted else { return finish(nil) }
|
self.dispatchQueue.async {
|
||||||
|
self.startCompletionHandlers.append(completionHandler)
|
||||||
self.persistentContainer.loadPersistentStores { (description, error) in
|
guard self.startCompletionHandlers.count == 1 else { return }
|
||||||
guard error == nil else { return finish(error!) }
|
|
||||||
|
|
||||||
self.prepareDatabase() { (result) in
|
guard !self.isStarted else { return finish(nil) }
|
||||||
switch result
|
|
||||||
{
|
self.persistentContainer.loadPersistentStores { (description, error) in
|
||||||
case .failure(let error):
|
guard error == nil else { return finish(error!) }
|
||||||
finish(error)
|
|
||||||
|
self.prepareDatabase() { (result) in
|
||||||
case .success:
|
switch result
|
||||||
self.isStarted = true
|
{
|
||||||
finish(nil)
|
case .failure(let error): finish(error)
|
||||||
|
case .success: finish(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +103,7 @@ public extension DatabaseManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseManager
|
public extension DatabaseManager
|
||||||
{
|
{
|
||||||
func activeAccount(in context: NSManagedObjectContext = DatabaseManager.shared.viewContext) -> Account?
|
func activeAccount(in context: NSManagedObjectContext = DatabaseManager.shared.viewContext) -> Account?
|
||||||
{
|
{
|
||||||
@@ -12,9 +12,9 @@ import CoreData
|
|||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||||
let ALTActiveAppsLimit = 3
|
public let ALTActiveAppsLimit = 3
|
||||||
|
|
||||||
protocol InstalledAppProtocol: Fetchable
|
public protocol InstalledAppProtocol: Fetchable
|
||||||
{
|
{
|
||||||
var name: String { get }
|
var name: String { get }
|
||||||
var bundleIdentifier: String { get }
|
var bundleIdentifier: String { get }
|
||||||
@@ -27,36 +27,39 @@ protocol InstalledAppProtocol: Fetchable
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc(InstalledApp)
|
@objc(InstalledApp)
|
||||||
class InstalledApp: NSManagedObject, InstalledAppProtocol
|
public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
||||||
{
|
{
|
||||||
/* Properties */
|
/* Properties */
|
||||||
@NSManaged var name: String
|
@NSManaged public var name: String
|
||||||
@NSManaged var bundleIdentifier: String
|
@NSManaged public var bundleIdentifier: String
|
||||||
@NSManaged var resignedBundleIdentifier: String
|
@NSManaged public var resignedBundleIdentifier: String
|
||||||
@NSManaged var version: String
|
@NSManaged public var version: String
|
||||||
|
|
||||||
@NSManaged var refreshedDate: Date
|
@NSManaged public var refreshedDate: Date
|
||||||
@NSManaged var expirationDate: Date
|
@NSManaged public var expirationDate: Date
|
||||||
@NSManaged var installedDate: Date
|
@NSManaged public var installedDate: Date
|
||||||
|
|
||||||
@NSManaged var isActive: Bool
|
@NSManaged public var isActive: Bool
|
||||||
|
|
||||||
@NSManaged var certificateSerialNumber: String?
|
@NSManaged public var certificateSerialNumber: String?
|
||||||
|
|
||||||
|
/* Transient */
|
||||||
|
@NSManaged public var isRefreshing: Bool
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged var storeApp: StoreApp?
|
@NSManaged public var storeApp: StoreApp?
|
||||||
@NSManaged var team: Team?
|
@NSManaged public var team: Team?
|
||||||
@NSManaged var appExtensions: Set<InstalledExtension>
|
@NSManaged public var appExtensions: Set<InstalledExtension>
|
||||||
|
|
||||||
var isSideloaded: Bool {
|
public var isSideloaded: Bool {
|
||||||
return self.storeApp == nil
|
return self.storeApp == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var appIDCount: Int {
|
public var appIDCount: Int {
|
||||||
return 1 + self.appExtensions.count
|
return 1 + self.appExtensions.count
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredActiveSlots: Int {
|
public var requiredActiveSlots: Int {
|
||||||
let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? self.appIDCount : 1
|
let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? self.appIDCount : 1
|
||||||
return requiredActiveSlots
|
return requiredActiveSlots
|
||||||
}
|
}
|
||||||
@@ -66,7 +69,7 @@ class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(resignedApp: ALTApplication, originalBundleIdentifier: String, certificateSerialNumber: String?, context: NSManagedObjectContext)
|
public init(resignedApp: ALTApplication, originalBundleIdentifier: String, certificateSerialNumber: String?, context: NSManagedObjectContext)
|
||||||
{
|
{
|
||||||
super.init(entity: InstalledApp.entity(), insertInto: context)
|
super.init(entity: InstalledApp.entity(), insertInto: context)
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
self.update(resignedApp: resignedApp, certificateSerialNumber: certificateSerialNumber)
|
self.update(resignedApp: resignedApp, certificateSerialNumber: certificateSerialNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(resignedApp: ALTApplication, certificateSerialNumber: String?)
|
public func update(resignedApp: ALTApplication, certificateSerialNumber: String?)
|
||||||
{
|
{
|
||||||
self.name = resignedApp.name
|
self.name = resignedApp.name
|
||||||
|
|
||||||
@@ -95,14 +98,14 @@ class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(provisioningProfile: ALTProvisioningProfile)
|
public func update(provisioningProfile: ALTProvisioningProfile)
|
||||||
{
|
{
|
||||||
self.refreshedDate = provisioningProfile.creationDate
|
self.refreshedDate = provisioningProfile.creationDate
|
||||||
self.expirationDate = provisioningProfile.expirationDate
|
self.expirationDate = provisioningProfile.expirationDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InstalledApp
|
public extension InstalledApp
|
||||||
{
|
{
|
||||||
@nonobjc class func fetchRequest() -> NSFetchRequest<InstalledApp>
|
@nonobjc class func fetchRequest() -> NSFetchRequest<InstalledApp>
|
||||||
{
|
{
|
||||||
@@ -199,7 +202,7 @@ extension InstalledApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InstalledApp
|
public extension InstalledApp
|
||||||
{
|
{
|
||||||
var openAppURL: URL {
|
var openAppURL: URL {
|
||||||
let openAppURL = URL(string: "altstore-" + self.bundleIdentifier + "://")!
|
let openAppURL = URL(string: "altstore-" + self.bundleIdentifier + "://")!
|
||||||
@@ -213,7 +216,7 @@ extension InstalledApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InstalledApp
|
public extension InstalledApp
|
||||||
{
|
{
|
||||||
class var appsDirectoryURL: URL {
|
class var appsDirectoryURL: URL {
|
||||||
let appsDirectoryURL = FileManager.default.applicationSupportDirectory.appendingPathComponent("Apps")
|
let appsDirectoryURL = FileManager.default.applicationSupportDirectory.appendingPathComponent("Apps")
|
||||||
@@ -12,27 +12,27 @@ import CoreData
|
|||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
@objc(InstalledExtension)
|
@objc(InstalledExtension)
|
||||||
class InstalledExtension: NSManagedObject, InstalledAppProtocol
|
public class InstalledExtension: NSManagedObject, InstalledAppProtocol
|
||||||
{
|
{
|
||||||
/* Properties */
|
/* Properties */
|
||||||
@NSManaged var name: String
|
@NSManaged public var name: String
|
||||||
@NSManaged var bundleIdentifier: String
|
@NSManaged public var bundleIdentifier: String
|
||||||
@NSManaged var resignedBundleIdentifier: String
|
@NSManaged public var resignedBundleIdentifier: String
|
||||||
@NSManaged var version: String
|
@NSManaged public var version: String
|
||||||
|
|
||||||
@NSManaged var refreshedDate: Date
|
@NSManaged public var refreshedDate: Date
|
||||||
@NSManaged var expirationDate: Date
|
@NSManaged public var expirationDate: Date
|
||||||
@NSManaged var installedDate: Date
|
@NSManaged public var installedDate: Date
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged var parentApp: InstalledApp?
|
@NSManaged public var parentApp: InstalledApp?
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(resignedAppExtension: ALTApplication, originalBundleIdentifier: String, context: NSManagedObjectContext)
|
public init(resignedAppExtension: ALTApplication, originalBundleIdentifier: String, context: NSManagedObjectContext)
|
||||||
{
|
{
|
||||||
super.init(entity: InstalledExtension.entity(), insertInto: context)
|
super.init(entity: InstalledExtension.entity(), insertInto: context)
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class InstalledExtension: NSManagedObject, InstalledAppProtocol
|
|||||||
self.update(resignedAppExtension: resignedAppExtension)
|
self.update(resignedAppExtension: resignedAppExtension)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(resignedAppExtension: ALTApplication)
|
public func update(resignedAppExtension: ALTApplication)
|
||||||
{
|
{
|
||||||
self.name = resignedAppExtension.name
|
self.name = resignedAppExtension.name
|
||||||
|
|
||||||
@@ -59,14 +59,14 @@ class InstalledExtension: NSManagedObject, InstalledAppProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(provisioningProfile: ALTProvisioningProfile)
|
public func update(provisioningProfile: ALTProvisioningProfile)
|
||||||
{
|
{
|
||||||
self.refreshedDate = provisioningProfile.creationDate
|
self.refreshedDate = provisioningProfile.creationDate
|
||||||
self.expirationDate = provisioningProfile.expirationDate
|
self.expirationDate = provisioningProfile.expirationDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InstalledExtension
|
public extension InstalledExtension
|
||||||
{
|
{
|
||||||
@nonobjc class func fetchRequest() -> NSFetchRequest<InstalledExtension>
|
@nonobjc class func fetchRequest() -> NSFetchRequest<InstalledExtension>
|
||||||
{
|
{
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user