diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index aab89a3c..090c9f8e 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -201,15 +201,15 @@ BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */; }; BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; }; BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; }; - BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */; }; - BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6325D22A8497000F30809 /* SelectTeamViewController.swift */; }; BFE6326622A857C200F30809 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326522A857C100F30809 /* Team.swift */; }; BFE6326822A858F300F30809 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326722A858F300F30809 /* Account.swift */; }; - BFE6326A22A85DAF00F30809 /* ReplaceCertificateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */; }; BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; }; BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68D23219520007A79E1 /* PatreonViewController.swift */; }; BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */; }; BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */; }; + BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */; }; + BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */; }; + BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */; }; DBAC68F8EC03F4A41D62EDE1 /* Pods_AltStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1039C07E517311FC499A0B64 /* Pods_AltStore.framework */; }; /* End PBXBuildFile section */ @@ -479,15 +479,15 @@ BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetGroupTableViewCell.swift; sourceTree = ""; }; BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderFooterView.swift; sourceTree = ""; }; BFE6325922A83BEB00F30809 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = ""; }; - BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; - BFE6325D22A8497000F30809 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = ""; }; BFE6326522A857C100F30809 /* Team.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; BFE6326722A858F300F30809 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; - BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaceCertificateViewController.swift; sourceTree = ""; }; BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationOperation.swift; sourceTree = ""; }; BFF0B68D23219520007A79E1 /* PatreonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonViewController.swift; sourceTree = ""; }; BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonComponents.swift; sourceTree = ""; }; BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutPatreonHeaderView.xib; sourceTree = ""; }; + BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; + BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsViewController.swift; sourceTree = ""; }; + BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+CompactHeight.swift"; sourceTree = ""; }; EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = ""; }; FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -941,6 +941,7 @@ BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */, BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */, BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */, + BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */, ); path = Extensions; sourceTree = ""; @@ -1012,9 +1013,8 @@ isa = PBXGroup; children = ( BFE6325922A83BEB00F30809 /* Authentication.storyboard */, - BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */, - BFE6325D22A8497000F30809 /* SelectTeamViewController.swift */, - BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */, + BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */, + BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */, ); path = Authentication; sourceTree = ""; @@ -1400,10 +1400,8 @@ BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */, BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */, BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */, - BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */, BFD2478F2284C8F900981D42 /* Button.swift in Sources */, BFD5D6F6230DDB12007955AB /* Tier.swift in Sources */, - BFE6326A22A85DAF00F30809 /* ReplaceCertificateViewController.swift in Sources */, BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */, BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */, BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */, @@ -1415,6 +1413,7 @@ BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */, BFD5D6F4230DDB0A007955AB /* Campaign.swift in Sources */, BFB6B21B23186D640022A802 /* NewsItem.swift in Sources */, + BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */, BFD5D6E8230CC961007955AB /* PatreonAPI.swift in Sources */, BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */, BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */, @@ -1434,11 +1433,11 @@ BF02419422F2156E00129732 /* RefreshAttempt.swift in Sources */, BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */, BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */, - BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */, BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */, BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */, BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */, BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */, + BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */, BFD5D6EE230D8A86007955AB /* Patron.swift in Sources */, BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */, BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */, @@ -1458,6 +1457,7 @@ BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */, BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */, BFD5D6F2230DD974007955AB /* Benefit.swift in Sources */, + BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */, BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */, BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */, BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */, diff --git a/AltStore/Authentication/Authentication.storyboard b/AltStore/Authentication/Authentication.storyboard index a5bc8197..aaa18187 100644 --- a/AltStore/Authentication/Authentication.storyboard +++ b/AltStore/Authentication/Authentication.storyboard @@ -4,239 +4,440 @@ + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - Your email address and password are used only to sign in with Apple and is never stored. - -If you have two-factor authentication enabled, make sure to use an app-specific password. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + diff --git a/AltStore/Authentication/AuthenticationViewController.swift b/AltStore/Authentication/AuthenticationViewController.swift index 039a8b80..aad7e1c0 100644 --- a/AltStore/Authentication/AuthenticationViewController.swift +++ b/AltStore/Authentication/AuthenticationViewController.swift @@ -2,48 +2,57 @@ // AuthenticationViewController.swift // AltStore // -// Created by Riley Testut on 6/5/19. +// Created by Riley Testut on 9/5/19. // Copyright © 2019 Riley Testut. All rights reserved. // import UIKit import AltSign -import Roxas -class AuthenticationViewController: UITableViewController +class AuthenticationViewController: UIViewController { var authenticationHandler: (((ALTAccount, String)?) -> Void)? - private var _didLayoutSubviews = false + private weak var toastView: ToastView? - @IBOutlet private var emailAddressTextField: UITextField! + @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() - self.update() - } - - override func viewDidLayoutSubviews() - { - super.viewDidLayoutSubviews() - - if !_didLayoutSubviews + for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!] { - self.emailAddressTextField.becomeFirstResponder() + view.clipsToBounds = true + view.layer.cornerRadius = 16 + } + + if UIScreen.main.isExtraCompactHeight + { + self.contentStackView.spacing = 20 } - _didLayoutSubviews = true + NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationViewController.textFieldDidChangeText(_:)), name: UITextField.textDidChangeNotification, object: self.appleIDTextField) + NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationViewController.textFieldDidChangeText(_:)), name: UITextField.textDidChangeNotification, object: self.passwordTextField) + + self.update() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - self.navigationItem.rightBarButtonItem?.isIndicatingActivity = false + self.signInButton.isIndicatingActivity = false + self.toastView?.dismiss() } } @@ -53,39 +62,25 @@ private extension AuthenticationViewController { if let _ = self.validate() { - self.navigationItem.rightBarButtonItem?.isEnabled = true + self.signInButton.isEnabled = true + self.signInButton.alpha = 1.0 } else { - self.navigationItem.rightBarButtonItem?.isEnabled = false + self.signInButton.isEnabled = false + self.signInButton.alpha = 0.6 } } func validate() -> (String, String)? { guard - let emailAddress = self.emailAddressTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !emailAddress.isEmpty, + let emailAddress = self.appleIDTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !emailAddress.isEmpty, let password = self.passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty else { return nil } return (emailAddress, password) } - - func authenticate(emailAddress: String, password: String, completionHandler: @escaping (Result<(ALTAccount, [ALTTeam]), Error>) -> Void) - { - ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in - switch Result(account, error) - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let account): - - ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in - let result = Result(teams, error).map { (account, $0) } - completionHandler(result) - } - } - } - } } private extension AuthenticationViewController @@ -94,10 +89,10 @@ private extension AuthenticationViewController { guard let (emailAddress, password) = self.validate() else { return } - self.emailAddressTextField.resignFirstResponder() + self.appleIDTextField.resignFirstResponder() self.passwordTextField.resignFirstResponder() - self.navigationItem.rightBarButtonItem?.isIndicatingActivity = true + self.signInButton.isIndicatingActivity = true ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in do @@ -108,17 +103,22 @@ private extension AuthenticationViewController catch { DispatchQueue.main.async { - let toastView = RSTToastView(text: NSLocalizedString("Failed to Log In", comment: ""), detailText: error.localizedDescription) - toastView.tintColor = .altPurple - toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) + let toastView = ToastView(text: NSLocalizedString("Failed to Log In", comment: ""), detailText: error.localizedDescription) + toastView.tintColor = .altGreen + toastView.show(in: self.navigationController?.view ?? self.view) + self.toastView = toastView - self.navigationItem.rightBarButtonItem?.isIndicatingActivity = false + self.signInButton.isIndicatingActivity = false } } + + DispatchQueue.main.async { + self.scrollView.setContentOffset(CGPoint(x: 0, y: -self.view.safeAreaInsets.top), animated: true) + } } } - @IBAction func cancel() + @IBAction func cancel(_ sender: UIBarButtonItem) { self.authenticationHandler?(nil) } @@ -130,7 +130,7 @@ extension AuthenticationViewController: UITextFieldDelegate { switch textField { - case self.emailAddressTextField: self.passwordTextField.becomeFirstResponder() + case self.appleIDTextField: self.passwordTextField.becomeFirstResponder() case self.passwordTextField: self.authenticate() default: break } @@ -140,12 +140,21 @@ extension AuthenticationViewController: UITextFieldDelegate return false } - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool + func textFieldDidBeginEditing(_ textField: UITextField) { - DispatchQueue.main.async { - self.update() - } + guard UIScreen.main.isExtraCompactHeight else { return } - return true + // Position all the controls within visible frame. + var contentOffset = self.scrollView.contentOffset + contentOffset.y = 44 + self.scrollView.setContentOffset(contentOffset, animated: true) + } +} + +extension AuthenticationViewController +{ + @objc func textFieldDidChangeText(_ notification: Notification) + { + self.update() } } diff --git a/AltStore/Authentication/InstructionsViewController.swift b/AltStore/Authentication/InstructionsViewController.swift new file mode 100644 index 00000000..744da7af --- /dev/null +++ b/AltStore/Authentication/InstructionsViewController.swift @@ -0,0 +1,50 @@ +// +// InstructionsViewController.swift +// AltStore +// +// Created by Riley Testut on 9/6/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +import UIKit + +class InstructionsViewController: UIViewController +{ + var completionHandler: (() -> Void)? + + var showsBottomButton: Bool = false + + @IBOutlet private var contentStackView: UIStackView! + @IBOutlet private var dismissButton: UIButton! + + override func viewDidLoad() + { + super.viewDidLoad() + + if UIScreen.main.isExtraCompactHeight + { + self.contentStackView.layoutMargins.top = 0 + self.contentStackView.layoutMargins.bottom = self.contentStackView.layoutMargins.left + } + + self.dismissButton.clipsToBounds = true + self.dismissButton.layer.cornerRadius = 16 + + if self.showsBottomButton + { + self.navigationItem.hidesBackButton = true + } + else + { + self.dismissButton.isHidden = true + } + } +} + +private extension InstructionsViewController +{ + @IBAction func dismiss() + { + self.completionHandler?() + } +} diff --git a/AltStore/Authentication/ReplaceCertificateViewController.swift b/AltStore/Authentication/ReplaceCertificateViewController.swift deleted file mode 100644 index d140291d..00000000 --- a/AltStore/Authentication/ReplaceCertificateViewController.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// ReplaceCertificateViewController.swift -// AltStore -// -// Created by Riley Testut on 6/5/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import UIKit - -import AltSign -import Roxas - -extension ReplaceCertificateViewController -{ - private enum Error: LocalizedError - { - case missingPrivateKey - case missingCertificate - - var errorDescription: String? { - switch self - { - case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "") - case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "") - } - } - } -} - -class ReplaceCertificateViewController: UITableViewController -{ - var replacementHandler: ((ALTCertificate?) -> Void)? - - var team: ALTTeam! - - var certificates: [ALTCertificate] { - get { - return self.dataSource.items - } - set { - self.dataSource.items = newValue - } - } - - private var selectedCertificate: ALTCertificate? { - didSet { - self.update() - } - } - - private lazy var dataSource = self.makeDataSource() - - override func viewDidLoad() - { - super.viewDidLoad() - - self.tableView.dataSource = self.dataSource - - self.update() - } -} - -private extension ReplaceCertificateViewController -{ - func makeDataSource() -> RSTArrayTableViewDataSource - { - let dataSource = RSTArrayTableViewDataSource(items: []) - dataSource.proxy = self - dataSource.cellConfigurationHandler = { [weak self] (cell, certificate, indexPath) in - cell.textLabel?.text = certificate.name - cell.accessoryType = (self?.selectedCertificate == certificate) ? .checkmark : .none - } - - let placeholderView = RSTPlaceholderView(frame: .zero) - placeholderView.textLabel.text = NSLocalizedString("No Certificates", comment: "") - placeholderView.detailTextLabel.text = NSLocalizedString("There are no certificates associated with this team.", comment: "") - dataSource.placeholderView = placeholderView - - return dataSource - } - - func update() - { - self.navigationItem.rightBarButtonItem?.isEnabled = (self.selectedCertificate != nil) - - if self.isViewLoaded - { - self.tableView.reloadData() - } - } -} - -private extension ReplaceCertificateViewController -{ - @IBAction func replaceCertificate(_ sender: UIBarButtonItem) - { - guard let certificate = self.selectedCertificate else { return } - - func replace() - { - sender.isIndicatingActivity = true - - ALTAppleAPI.shared.revoke(certificate, for: self.team) { (success, error) in - let result = Result(success, error).map { certificate } - - do - { - let certificate = try result.get() - self.replacementHandler?(certificate) - } - catch - { - DispatchQueue.main.async { - let toastView = RSTToastView(text: NSLocalizedString("Error Replacing Certificate", comment: ""), detailText: error.localizedDescription) - toastView.tintColor = .altPurple - toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) - - sender.isIndicatingActivity = false - } - } - } - } - - let localizedTitle = String(format: NSLocalizedString("Are you sure you want to replace %@?", comment: ""), certificate.name) - let localizedMessage = NSLocalizedString("Any AltStore apps currently installed with this certificate will need to be refreshed.", comment: "") - let localizedReplaceActionTitle = String(format: NSLocalizedString("Replace %@", comment: ""), certificate.name) - - let alertController = UIAlertController(title: localizedTitle, message: localizedMessage, preferredStyle: .actionSheet) - alertController.addAction(UIAlertAction(title: localizedReplaceActionTitle, style: .destructive) { (action) in - replace() - }) - alertController.addAction(.cancel) - - self.present(alertController, animated: true, completion: nil) - } - - @IBAction func cancel() - { - self.replacementHandler?(nil) - } -} - -extension ReplaceCertificateViewController -{ - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? - { - return NSLocalizedString("You have reached the maximum number of development certificates. Please select a certificate to replace.", comment: "") - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) - { - let certificate = self.dataSource.item(at: indexPath) - self.selectedCertificate = certificate - } -} diff --git a/AltStore/Authentication/SelectTeamViewController.swift b/AltStore/Authentication/SelectTeamViewController.swift deleted file mode 100644 index 861c3510..00000000 --- a/AltStore/Authentication/SelectTeamViewController.swift +++ /dev/null @@ -1,141 +0,0 @@ -// -// SelectTeamViewController.swift -// AltStore -// -// Created by Riley Testut on 6/5/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import UIKit - -import AltSign -import Roxas - -class SelectTeamViewController: UITableViewController -{ - var selectionHandler: ((ALTTeam?) -> Void)? - - var teams: [ALTTeam] { - get { - return self.dataSource.items - } - set { - self.dataSource.items = newValue - } - } - - private var selectedTeam: ALTTeam? { - didSet { - self.update() - } - } - - private lazy var dataSource = self.makeDataSource() - - override func viewDidLoad() - { - super.viewDidLoad() - - self.tableView.dataSource = self.dataSource - - self.update() - } - - override func viewWillDisappear(_ animated: Bool) - { - super.viewDidDisappear(animated) - - self.navigationItem.rightBarButtonItem?.isIndicatingActivity = false - } -} - -private extension SelectTeamViewController -{ - func makeDataSource() -> RSTArrayTableViewDataSource - { - let dataSource = RSTArrayTableViewDataSource(items: []) - dataSource.proxy = self - dataSource.cellConfigurationHandler = { [weak self] (cell, team, indexPath) in - cell.textLabel?.text = team.name - cell.detailTextLabel?.text = team.type.localizedDescription - cell.accessoryType = (self?.selectedTeam == team) ? .checkmark : .none - } - - let placeholderView = RSTPlaceholderView(frame: .zero) - placeholderView.textLabel.text = NSLocalizedString("No Teams", comment: "") - placeholderView.detailTextLabel.text = NSLocalizedString("You are not a member of any development teams.", comment: "") - dataSource.placeholderView = placeholderView - - return dataSource - } - - func update() - { - self.navigationItem.rightBarButtonItem?.isEnabled = (self.selectedTeam != nil) - - if self.isViewLoaded - { - self.tableView.reloadData() - } - } - - func fetchCertificates(for team: ALTTeam, completionHandler: @escaping (Result<[ALTCertificate], Error>) -> Void) - { - ALTAppleAPI.shared.fetchCertificates(for: team) { (certificate, error) in - let result = Result(certificate, error) - completionHandler(result) - } - } -} - -private extension SelectTeamViewController -{ - @IBAction func chooseTeam(_ sender: UIBarButtonItem) - { - guard let team = self.selectedTeam else { return } - - func choose() - { - sender.isIndicatingActivity = true - - self.selectionHandler?(team) - } - - if team.type == .organization - { - let localizedActionTitle = String(format: NSLocalizedString("Use %@?", comment: ""), team.name) - - let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to use an Organization team?", comment: ""), - message: NSLocalizedString("Doing so may affect other members of this team.", comment: ""), preferredStyle: .actionSheet) - alertController.addAction(UIAlertAction(title: localizedActionTitle, style: .destructive, handler: { (action) in - choose() - })) - alertController.addAction(.cancel) - - self.present(alertController, animated: true, completion: nil) - } - else - { - choose() - } - } - - @IBAction func cancel() - { - self.selectionHandler?(nil) - } -} - -extension SelectTeamViewController -{ - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? - { - return NSLocalizedString("Select the team you would like to use to install apps.", comment: "") - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) - { - let team = self.dataSource.item(at: indexPath) - self.selectedTeam = team - } -} diff --git a/AltStore/Extensions/UIColor+AltStore.swift b/AltStore/Extensions/UIColor+AltStore.swift index 030a2949..8db64781 100644 --- a/AltStore/Extensions/UIColor+AltStore.swift +++ b/AltStore/Extensions/UIColor+AltStore.swift @@ -12,6 +12,8 @@ extension UIColor { static let altPurple = UIColor(named: "Purple")! static let altGreen = UIColor(named: "Green")! + static let altRed = UIColor(named: "Red")! + static let altPink = UIColor(named: "Pink")! static let refreshRed = UIColor(named: "RefreshRed")! static let refreshOrange = UIColor(named: "RefreshOrange")! diff --git a/AltStore/Extensions/UIScreen+CompactHeight.swift b/AltStore/Extensions/UIScreen+CompactHeight.swift new file mode 100644 index 00000000..8ca350ac --- /dev/null +++ b/AltStore/Extensions/UIScreen+CompactHeight.swift @@ -0,0 +1,16 @@ +// +// UIScreen+CompactHeight.swift +// AltStore +// +// Created by Riley Testut on 9/6/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +import UIKit + +extension UIScreen +{ + var isExtraCompactHeight: Bool { + return self.fixedCoordinateSpace.bounds.height < 600 + } +} diff --git a/AltStore/Operations/AuthenticationOperation.swift b/AltStore/Operations/AuthenticationOperation.swift index 2935095f..267940a9 100644 --- a/AltStore/Operations/AuthenticationOperation.swift +++ b/AltStore/Operations/AuthenticationOperation.swift @@ -34,17 +34,22 @@ class AuthenticationOperation: ResultOperation { private weak var presentingViewController: UIViewController? - private lazy var navigationController = UINavigationController() + private lazy var navigationController: UINavigationController = { + let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController + return navigationController + }() + private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil) private var appleIDPassword: String? + private var shouldShowInstructions = false init(presentingViewController: UIViewController?) { self.presentingViewController = presentingViewController super.init() - + self.progress.totalUnitCount = 3 } @@ -82,8 +87,10 @@ class AuthenticationOperation: ResultOperation case .success(let certificate): self.progress.completedUnitCount += 1 - let signer = ALTSigner(team: team, certificate: certificate) - self.finish(.success(signer)) + self.showInstructionsIfNecessary() { (didShowInstructions) in + let signer = ALTSigner(team: team, certificate: certificate) + self.finish(.success(signer)) + } } } } @@ -161,7 +168,7 @@ private extension AuthenticationOperation { guard let presentingViewController = self.presentingViewController else { return false } - self.navigationController.view.tintColor = .altPurple + self.navigationController.view.tintColor = .white if self.navigationController.viewControllers.isEmpty { @@ -191,8 +198,11 @@ private extension AuthenticationOperation authenticationViewController.authenticationHandler = { (result) in if let (account, password) = result { - self.appleIDPassword = password + // We presented the Auth UI and the user signed in. + // In this case, we'll assume we should show the instructions again. + self.shouldShowInstructions = true + self.appleIDPassword = password completionHandler(.success(account)) } else @@ -242,38 +252,29 @@ private extension AuthenticationOperation { func selectTeam(from teams: [ALTTeam]) { - if let team = teams.first, teams.count == 1 + if let team = teams.first(where: { $0.type == .free }) { return completionHandler(.success(team)) } - - DispatchQueue.main.async { - let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController - selectTeamViewController.teams = teams - selectTeamViewController.selectionHandler = { (team) in - if let team = team - { - completionHandler(.success(team)) - } - else - { - completionHandler(.failure(OperationError.cancelled)) - } - } - - if !self.present(selectTeamViewController) - { - completionHandler(.failure(AuthenticationError.noTeam)) - } + else if let team = teams.first(where: { $0.type == .individual }) + { + return completionHandler(.success(team)) + } + else if let team = teams.first + { + return completionHandler(.success(team)) + } + else + { + return completionHandler(.failure(AuthenticationError.noTeam)) } } - + ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in switch Result(teams, error) { case .failure(let error): completionHandler(.failure(error)) case .success(let teams): - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier }) { @@ -326,40 +327,16 @@ private extension AuthenticationOperation func replaceCertificate(from certificates: [ALTCertificate]) { - if let certificate = certificates.first, certificates.count == 1 - { - ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in - if let error = error, !success - { - completionHandler(.failure(error)) - } - else - { - requestCertificate() - } - } - - return - } + guard let certificate = certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) } - DispatchQueue.main.async { - let replaceCertificateViewController = self.storyboard.instantiateViewController(withIdentifier: "replaceCertificateViewController") as! ReplaceCertificateViewController - replaceCertificateViewController.team = team - replaceCertificateViewController.certificates = certificates - replaceCertificateViewController.replacementHandler = { (certificate) in - if certificate != nil - { - requestCertificate() - } - else - { - completionHandler(.failure(OperationError.cancelled)) - } - } - - if !self.present(replaceCertificateViewController) + ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in + if let error = error, !success { - completionHandler(.failure(AuthenticationError.noCertificate)) + completionHandler(.failure(error)) + } + else + { + requestCertificate() } } } @@ -393,4 +370,21 @@ private extension AuthenticationOperation } } + func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void) + { + guard self.shouldShowInstructions else { return completionHandler(false) } + + DispatchQueue.main.async { + let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController + instructionsViewController.showsBottomButton = true + instructionsViewController.completionHandler = { + completionHandler(true) + } + + if !self.present(instructionsViewController) + { + completionHandler(false) + } + } + } } diff --git a/AltStore/Resources/Assets.xcassets/Colors/Pink.colorset/Contents.json b/AltStore/Resources/Assets.xcassets/Colors/Pink.colorset/Contents.json new file mode 100644 index 00000000..5ba8c958 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Colors/Pink.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "236", + "alpha" : "1.000", + "blue" : "178", + "green" : "65" + } + } + } + ] +} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Colors/Red.colorset/Contents.json b/AltStore/Resources/Assets.xcassets/Colors/Red.colorset/Contents.json new file mode 100644 index 00000000..c17b85fd --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Colors/Red.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "235", + "alpha" : "1.000", + "blue" : "59", + "green" : "70" + } + } + } + ] +} \ No newline at end of file