mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-13 16:53:29 +01:00
XCode project for app, moved app project to folder
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// AuthenticationViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/5/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import AltSign
|
||||
|
||||
final class AuthenticationViewController: UIViewController {
|
||||
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
||||
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
||||
|
||||
private weak var toastView: ToastView?
|
||||
|
||||
@IBOutlet private var appleIDTextField: UITextField!
|
||||
@IBOutlet private var passwordTextField: UITextField!
|
||||
@IBOutlet private var signInButton: UIButton!
|
||||
|
||||
@IBOutlet private var appleIDBackgroundView: UIView!
|
||||
@IBOutlet private var passwordBackgroundView: UIView!
|
||||
|
||||
@IBOutlet private var scrollView: UIScrollView!
|
||||
@IBOutlet private var contentStackView: UIStackView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
signInButton.activityIndicatorView.style = .medium
|
||||
|
||||
for view in [appleIDBackgroundView!, passwordBackgroundView!, signInButton!] {
|
||||
view.clipsToBounds = true
|
||||
view.layer.cornerRadius = 16
|
||||
}
|
||||
|
||||
if UIScreen.main.isExtraCompactHeight {
|
||||
contentStackView.spacing = 20
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationViewController.textFieldDidChangeText(_:)), name: UITextField.textDidChangeNotification, object: appleIDTextField)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationViewController.textFieldDidChangeText(_:)), name: UITextField.textDidChangeNotification, object: passwordTextField)
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
signInButton.isIndicatingActivity = false
|
||||
toastView?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private extension AuthenticationViewController {
|
||||
func update() {
|
||||
if let _ = validate() {
|
||||
signInButton.isEnabled = true
|
||||
signInButton.alpha = 1.0
|
||||
} else {
|
||||
signInButton.isEnabled = false
|
||||
signInButton.alpha = 0.6
|
||||
}
|
||||
}
|
||||
|
||||
func validate() -> (String, String)? {
|
||||
guard
|
||||
let emailAddress = appleIDTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !emailAddress.isEmpty,
|
||||
let password = passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty
|
||||
else { return nil }
|
||||
|
||||
return (emailAddress, password)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AuthenticationViewController {
|
||||
@IBAction func authenticate() {
|
||||
guard let (emailAddress, password) = validate() else { return }
|
||||
|
||||
appleIDTextField.resignFirstResponder()
|
||||
passwordTextField.resignFirstResponder()
|
||||
|
||||
signInButton.isIndicatingActivity = true
|
||||
|
||||
authenticationHandler?(emailAddress, password) { result in
|
||||
switch result {
|
||||
case .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
|
||||
// Ignore
|
||||
DispatchQueue.main.async {
|
||||
self.signInButton.isIndicatingActivity = false
|
||||
}
|
||||
|
||||
case let .failure(error as NSError):
|
||||
DispatchQueue.main.async {
|
||||
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Log In", comment: ""))
|
||||
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.textLabel.textColor = .altPink
|
||||
toastView.detailTextLabel.textColor = .altPink
|
||||
toastView.show(in: self)
|
||||
self.toastView = toastView
|
||||
|
||||
self.signInButton.isIndicatingActivity = false
|
||||
}
|
||||
|
||||
case let .success((account, session)):
|
||||
self.completionHandler?((account, session, password))
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.scrollView.setContentOffset(CGPoint(x: 0, y: -self.view.safeAreaInsets.top), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func cancel(_: UIBarButtonItem) {
|
||||
completionHandler?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension AuthenticationViewController: UITextFieldDelegate {
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
switch textField {
|
||||
case appleIDTextField: passwordTextField.becomeFirstResponder()
|
||||
case passwordTextField: authenticate()
|
||||
default: break
|
||||
}
|
||||
|
||||
update()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func textFieldDidBeginEditing(_: UITextField) {
|
||||
guard UIScreen.main.isExtraCompactHeight else { return }
|
||||
|
||||
// Position all the controls within visible frame.
|
||||
var contentOffset = scrollView.contentOffset
|
||||
contentOffset.y = 44
|
||||
scrollView.setContentOffset(contentOffset, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension AuthenticationViewController {
|
||||
@objc func textFieldDidChangeText(_: Notification) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// InstructionsViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/6/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
final class InstructionsViewController: UIViewController {
|
||||
var completionHandler: (() -> Void)?
|
||||
|
||||
var showsBottomButton: Bool = false
|
||||
|
||||
@IBOutlet private var contentStackView: UIStackView!
|
||||
@IBOutlet private var dismissButton: UIButton!
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
.lightContent
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
if UIScreen.main.isExtraCompactHeight {
|
||||
contentStackView.layoutMargins.top = 0
|
||||
contentStackView.layoutMargins.bottom = contentStackView.layoutMargins.left
|
||||
}
|
||||
|
||||
dismissButton.clipsToBounds = true
|
||||
dismissButton.layer.cornerRadius = 16
|
||||
|
||||
if showsBottomButton {
|
||||
navigationItem.hidesBackButton = true
|
||||
} else {
|
||||
dismissButton.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension InstructionsViewController {
|
||||
@IBAction func dismiss() {
|
||||
completionHandler?()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// IntentHandler.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 7/6/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SideStoreCore
|
||||
import Intents
|
||||
|
||||
@available(iOS 14, *)
|
||||
public final class IntentHandler: NSObject, RefreshAllIntentHandling {
|
||||
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
|
||||
|
||||
private var completionHandlers = [RefreshAllIntent: (RefreshAllIntentResponse) -> Void]()
|
||||
private var queuedResponses = [RefreshAllIntent: RefreshAllIntentResponse]()
|
||||
|
||||
private var operations = [RefreshAllIntent: BackgroundRefreshAppsOperation]()
|
||||
|
||||
public 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.
|
||||
|
||||
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.
|
||||
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 {
|
||||
refreshApps(intent: intent)
|
||||
}
|
||||
}
|
||||
|
||||
public func handle(intent: RefreshAllIntent, completion: @escaping (RefreshAllIntentResponse) -> Void) {
|
||||
completionHandlers[intent] = { response in
|
||||
// Ignore .ready response from confirm() timeout.
|
||||
guard response.code != .ready else { return }
|
||||
completion(response)
|
||||
}
|
||||
|
||||
if let response = queuedResponses[intent] {
|
||||
queuedResponses[intent] = nil
|
||||
finish(intent, response: response)
|
||||
} else {
|
||||
queue.asyncAfter(deadline: .now() + 7.0) {
|
||||
if let operation = self.operations[intent] {
|
||||
// We took too long to finish and return the final result,
|
||||
// so we'll now present a normal notification when finished.
|
||||
operation.presentsFinishedNotification = true
|
||||
}
|
||||
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .inProgress, userActivity: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
private extension IntentHandler {
|
||||
func finish(_ intent: RefreshAllIntent, response: RefreshAllIntentResponse) {
|
||||
queue.async {
|
||||
if let completionHandler = self.completionHandlers[intent] {
|
||||
self.completionHandlers[intent] = nil
|
||||
completionHandler(response)
|
||||
} else if response.code != .ready && response.code != .inProgress {
|
||||
// Queue response in case refreshing finishes after confirm() but before handle().
|
||||
self.queuedResponses[intent] = response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshApps(intent: RefreshAllIntent) {
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let installedApps = InstalledApp.fetchActiveApps(in: context)
|
||||
let operation = 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))
|
||||
}
|
||||
|
||||
self.operations[intent] = nil
|
||||
}
|
||||
|
||||
self.operations[intent] = operation
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,179 @@
|
||||
<?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>TPucbY</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>information</string>
|
||||
<key>INIntentConfigurable</key>
|
||||
<true/>
|
||||
<key>INIntentDescription</key>
|
||||
<string>Select App</string>
|
||||
<key>INIntentDescriptionID</key>
|
||||
<string>sb9c7F</string>
|
||||
<key>INIntentEligibleForWidgets</key>
|
||||
<true/>
|
||||
<key>INIntentIneligibleForSuggestions</key>
|
||||
<true/>
|
||||
<key>INIntentLastParameterTag</key>
|
||||
<integer>2</integer>
|
||||
<key>INIntentManagedParameterCombinations</key>
|
||||
<dict>
|
||||
<key>app</key>
|
||||
<dict>
|
||||
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
|
||||
<true/>
|
||||
<key>INIntentParameterCombinationUpdatesLinked</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>INIntentName</key>
|
||||
<string>ViewApp</string>
|
||||
<key>INIntentParameters</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentParameterConfigurable</key>
|
||||
<true/>
|
||||
<key>INIntentParameterDisplayName</key>
|
||||
<string>App</string>
|
||||
<key>INIntentParameterDisplayNameID</key>
|
||||
<string>QwLCXY</string>
|
||||
<key>INIntentParameterDisplayPriority</key>
|
||||
<integer>1</integer>
|
||||
<key>INIntentParameterName</key>
|
||||
<string>app</string>
|
||||
<key>INIntentParameterObjectType</key>
|
||||
<string>App</string>
|
||||
<key>INIntentParameterObjectTypeNamespace</key>
|
||||
<string>TPucbY</string>
|
||||
<key>INIntentParameterPromptDialogs</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>Configuration</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>Primary</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INIntentParameterSupportsDynamicEnumeration</key>
|
||||
<true/>
|
||||
<key>INIntentParameterTag</key>
|
||||
<integer>2</integer>
|
||||
<key>INIntentParameterType</key>
|
||||
<string>Object</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INIntentResponse</key>
|
||||
<dict>
|
||||
<key>INIntentResponseCodes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>success</string>
|
||||
<key>INIntentResponseCodeSuccess</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>failure</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>INIntentTitle</key>
|
||||
<string>View App</string>
|
||||
<key>INIntentTitleID</key>
|
||||
<string>7aGoWn</string>
|
||||
<key>INIntentType</key>
|
||||
<string>Custom</string>
|
||||
<key>INIntentVerb</key>
|
||||
<string>View</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INTypeDisplayName</key>
|
||||
<string>App</string>
|
||||
<key>INTypeDisplayNameID</key>
|
||||
<string>cUl1NZ</string>
|
||||
<key>INTypeLastPropertyTag</key>
|
||||
<integer>99</integer>
|
||||
<key>INTypeName</key>
|
||||
<string>App</string>
|
||||
<key>INTypeProperties</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INTypePropertyDefault</key>
|
||||
<true/>
|
||||
<key>INTypePropertyDisplayPriority</key>
|
||||
<integer>1</integer>
|
||||
<key>INTypePropertyName</key>
|
||||
<string>identifier</string>
|
||||
<key>INTypePropertyTag</key>
|
||||
<integer>1</integer>
|
||||
<key>INTypePropertyType</key>
|
||||
<string>String</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INTypePropertyDefault</key>
|
||||
<true/>
|
||||
<key>INTypePropertyDisplayPriority</key>
|
||||
<integer>2</integer>
|
||||
<key>INTypePropertyName</key>
|
||||
<string>displayString</string>
|
||||
<key>INTypePropertyTag</key>
|
||||
<integer>2</integer>
|
||||
<key>INTypePropertyType</key>
|
||||
<string>String</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INTypePropertyDefault</key>
|
||||
<true/>
|
||||
<key>INTypePropertyDisplayPriority</key>
|
||||
<integer>3</integer>
|
||||
<key>INTypePropertyName</key>
|
||||
<string>pronunciationHint</string>
|
||||
<key>INTypePropertyTag</key>
|
||||
<integer>3</integer>
|
||||
<key>INTypePropertyType</key>
|
||||
<string>String</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INTypePropertyDefault</key>
|
||||
<true/>
|
||||
<key>INTypePropertyDisplayPriority</key>
|
||||
<integer>4</integer>
|
||||
<key>INTypePropertyName</key>
|
||||
<string>alternativeSpeakableMatches</string>
|
||||
<key>INTypePropertySupportsMultipleValues</key>
|
||||
<true/>
|
||||
<key>INTypePropertyTag</key>
|
||||
<integer>4</integer>
|
||||
<key>INTypePropertyType</key>
|
||||
<string>SpeakableString</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ViewAppIntentHandler.swift
|
||||
// ViewAppIntentHandler
|
||||
//
|
||||
// Created by Riley Testut on 7/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Intents
|
||||
import Shared
|
||||
import SideStoreCore
|
||||
|
||||
@available(iOS 14, *)
|
||||
public class ViewAppIntentHandler: NSObject, ViewAppIntentHandling {
|
||||
public func provideAppOptionsCollection(for _: ViewAppIntent, with completion: @escaping (INObjectCollection<App>?, Error?) -> Void) {
|
||||
DatabaseManager.shared.start { error in
|
||||
if let error = error {
|
||||
print("Error starting extension:", error)
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let apps = InstalledApp.all(in: context).map { installedApp in
|
||||
App(identifier: installedApp.bundleIdentifier, display: installedApp.name)
|
||||
}
|
||||
|
||||
let collection = INObjectCollection(items: apps)
|
||||
completion(collection, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// RefreshAltStoreViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/26/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import AltSign
|
||||
import SideStoreCore
|
||||
import RoxasUIKit
|
||||
|
||||
final class RefreshAltStoreViewController: UIViewController {
|
||||
var context: AuthenticatedOperationContext!
|
||||
|
||||
var completionHandler: ((Result<Void, Error>) -> Void)?
|
||||
|
||||
@IBOutlet private var placeholderView: RSTPlaceholderView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
placeholderView.textLabel.isHidden = true
|
||||
|
||||
placeholderView.detailTextLabel.textAlignment = .left
|
||||
placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||
placeholderView.detailTextLabel.text = NSLocalizedString("SideStore was unable to use an existing signing certificate, so it had to create a new one. This will cause any apps installed with an existing certificate to expire — including SideStore.\n\nTo prevent SideStore from expiring early, please refresh the app now. SideStore will quit once refreshing is complete.", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
private extension RefreshAltStoreViewController {
|
||||
@IBAction func refreshAltStore(_ sender: PillButton) {
|
||||
guard let altStore = InstalledApp.fetchAltStore(in: DatabaseManager.shared.viewContext) else { return }
|
||||
|
||||
func refresh() {
|
||||
sender.isIndicatingActivity = true
|
||||
|
||||
if let progress = AppManager.shared.installationProgress(for: altStore) {
|
||||
// Cancel pending AltStore installation so we can start a new one.
|
||||
progress.cancel()
|
||||
}
|
||||
|
||||
// Install, _not_ refresh, to ensure we are installing with a non-revoked certificate.
|
||||
let group = AppManager.shared.install(altStore, presentingViewController: self, context: context) { result in
|
||||
switch result {
|
||||
case .success: self.completionHandler?(.success(()))
|
||||
case let .failure(error as NSError):
|
||||
DispatchQueue.main.async {
|
||||
sender.progress = nil
|
||||
sender.isIndicatingActivity = false
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Failed to Refresh SideStore", comment: ""), message: error.localizedFailureReason ?? error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Try Again", comment: ""), style: .default, handler: { _ in
|
||||
refresh()
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Refresh Later", comment: ""), style: .cancel, handler: { _ in
|
||||
self.completionHandler?(.failure(error))
|
||||
}))
|
||||
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.progress = group.progress
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
@IBAction func cancel(_: UIButton) {
|
||||
completionHandler?(.failure(OperationError.cancelled))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// SelectTeamViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Megarushing on 4/26/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Intents
|
||||
import IntentsUI
|
||||
import MessageUI
|
||||
import SafariServices
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
import AltSign
|
||||
|
||||
final class SelectTeamViewController: UITableViewController {
|
||||
public var teams: [ALTTeam]?
|
||||
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?
|
||||
|
||||
private var prototypeHeaderFooterView: SettingsHeaderFooterView!
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
.lightContent
|
||||
}
|
||||
|
||||
override func numberOfSections(in _: UITableView) -> Int {
|
||||
1
|
||||
}
|
||||
|
||||
override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
teams?.count ?? 0
|
||||
}
|
||||
|
||||
override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
precondition(completionHandler != nil)
|
||||
precondition(teams != nil)
|
||||
precondition(teams!.count <= indexPath.row)
|
||||
|
||||
guard let completionHandler = completionHandler else {
|
||||
os_log("completionHandler was nil", type: .error)
|
||||
return
|
||||
}
|
||||
guard let teams = teams, teams.count <= indexPath.row else {
|
||||
os_log("teams nil or out of bounds", type: .error)
|
||||
return
|
||||
}
|
||||
completionHandler(.success(teams[indexPath.row]))
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "TeamCell", for: indexPath) as! InsetGroupTableViewCell
|
||||
|
||||
cell.textLabel?.text = teams?[indexPath.row].name
|
||||
cell.detailTextLabel?.text = teams?[indexPath.row].type.localizedDescription
|
||||
if indexPath.row == 0 {
|
||||
cell.style = InsetGroupTableViewCell.Style.top
|
||||
} else if indexPath.row == self.tableView(self.tableView, numberOfRowsInSection: indexPath.section) - 1 {
|
||||
cell.style = InsetGroupTableViewCell.Style.bottom
|
||||
} else {
|
||||
cell.style = InsetGroupTableViewCell.Style.middle
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_: UITableView, titleForHeaderInSection _: Int) -> String? {
|
||||
"Teams"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user