[AltStore] Basic Account tab

This commit is contained in:
Riley Testut
2019-06-06 14:46:23 -07:00
parent b98ab3e852
commit c4542373c5
12 changed files with 465 additions and 41 deletions

View File

@@ -144,6 +144,8 @@
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1D22A1A9EC000B7ED1 /* node.c */; };
BFD52C2122A1A9EC000B7ED1 /* node_list.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1E22A1A9EC000B7ED1 /* node_list.c */; };
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; };
BFDB69FD22A9A7B7007EA6D6 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB69FC22A9A7B7007EA6D6 /* AccountViewController.swift */; };
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0422A9AFB2007EA6D6 /* Fetchable.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 */; };
@@ -361,6 +363,8 @@
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; };
BFDB69FC22A9A7B7007EA6D6 /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = "<group>"; };
BFE6325922A83BEB00F30809 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = "<group>"; };
BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = "<group>"; };
BFE6325D22A8497000F30809 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
@@ -675,9 +679,11 @@
BFD2478A2284C49000981D42 /* Apps */,
BFBBE2E2229320A2002097FA /* My Apps */,
BFB1169E22933DDC00BB457C /* Updates */,
BFDB69FB22A9A7A6007EA6D6 /* Account */,
BFC51D7922972F1F00388324 /* Server */,
BFD247982284D7FC00981D42 /* Model */,
BFD2478D2284C4C700981D42 /* Components */,
BFDB6A0622A9B114007EA6D6 /* Protocols */,
BFD2479D2284FBBD00981D42 /* Extensions */,
BFD247962284D7C100981D42 /* Resources */,
BFD247972284D7D800981D42 /* Supporting Files */,
@@ -771,6 +777,22 @@
path = Connections;
sourceTree = "<group>";
};
BFDB69FB22A9A7A6007EA6D6 /* Account */ = {
isa = PBXGroup;
children = (
BFDB69FC22A9A7B7007EA6D6 /* AccountViewController.swift */,
);
path = Account;
sourceTree = "<group>";
};
BFDB6A0622A9B114007EA6D6 /* Protocols */ = {
isa = PBXGroup;
children = (
BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
BFE6325822A83BA800F30809 /* Authentication */ = {
isa = PBXGroup;
children = (
@@ -1128,6 +1150,7 @@
files = (
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
BFDB69FD22A9A7B7007EA6D6 /* AccountViewController.swift in Sources */,
BFE6326A22A85DAF00F30809 /* ReplaceCertificateViewController.swift in Sources */,
BFD247722284B9A500981D42 /* MyAppsViewController.swift in Sources */,
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */,
@@ -1149,6 +1172,7 @@
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
BFD52BD622A08A85000B7ED1 /* Server.swift in Sources */,
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */,
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */,
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */,
);

View File

@@ -0,0 +1,148 @@
//
// AccountViewController.swift
// AltStore
//
// Created by Riley Testut on 6/6/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
class AccountViewController: UITableViewController
{
private var team: Team?
private lazy var placeholderView = self.makePlaceholderView()
@IBOutlet var accountNameLabel: UILabel!
@IBOutlet var accountEmailLabel: UILabel!
@IBOutlet var teamNameLabel: UILabel!
@IBOutlet var teamTypeLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
self.update()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.update()
}
}
private extension AccountViewController
{
func makePlaceholderView() -> RSTPlaceholderView
{
let placeholderView = RSTPlaceholderView()
placeholderView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
placeholderView.textLabel.text = NSLocalizedString("Not Signed In", comment: "")
placeholderView.detailTextLabel.text = NSLocalizedString("Please sign in with your Apple ID to download and refresh apps.", comment: "")
let signInButton = UIButton(type: .system)
signInButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
signInButton.setTitle(NSLocalizedString("Sign In", comment: ""), for: .normal)
signInButton.addTarget(self, action: #selector(AccountViewController.signIn(_:)), for: .primaryActionTriggered)
placeholderView.stackView.addArrangedSubview(signInButton)
return placeholderView
}
func update()
{
if let team = DatabaseManager.shared.activeTeam()
{
self.tableView.separatorStyle = .singleLine
self.tableView.isScrollEnabled = true
self.tableView.backgroundView = nil
self.navigationItem.rightBarButtonItem?.isEnabled = true
self.accountNameLabel.text = team.account.localizedName
self.accountEmailLabel.text = team.account.appleID
self.teamNameLabel.text = team.name
self.teamTypeLabel.text = team.type.localizedDescription
self.team = team
}
else
{
self.tableView.separatorStyle = .none
self.tableView.isScrollEnabled = false
self.tableView.backgroundView = self.placeholderView
self.navigationItem.rightBarButtonItem?.isEnabled = false
self.team = nil
}
if self.isViewLoaded
{
self.tableView.reloadData()
}
}
}
private extension AccountViewController
{
@objc func signIn(_ sender: UIButton)
{
sender.isIndicatingActivity = true
AppManager.shared.authenticate(presentingViewController: self) { (result) in
DispatchQueue.main.async {
sender.isIndicatingActivity = false
self.update()
}
}
}
@IBAction func signOut(_ sender: UIBarButtonItem)
{
func signOut()
{
DatabaseManager.shared.signOut { (error) in
DispatchQueue.main.async {
if let error = error
{
let toastView = RSTToastView(text: error.localizedDescription, detailText: nil)
toastView.tintColor = .red
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
}
else
{
let toastView = RSTToastView(text: NSLocalizedString("Successfully Signed Out!", comment: ""), detailText: nil)
toastView.tintColor = .altPurple
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
}
self.update()
}
}
}
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer be able to install or refresh apps once you sign out.", comment: ""), preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() })
alertController.addAction(.cancel)
self.present(alertController, animated: true, completion: nil)
}
}
extension AccountViewController
{
override func numberOfSections(in tableView: UITableView) -> Int
{
let count = (self.team == nil) ? 0 : super.numberOfSections(in: tableView)
return count
}
}

View File

@@ -36,11 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if UserDefaults.standard.firstLaunch == nil
{
Keychain.shared.appleIDEmailAddress = nil
Keychain.shared.appleIDPassword = nil
Keychain.shared.signingCertificatePrivateKey = nil
Keychain.shared.signingCertificateIdentifier = nil
Keychain.shared.reset()
UserDefaults.standard.firstLaunch = Date()
}

View File

@@ -77,21 +77,29 @@ class AuthenticationOperation: RSTOperation
// Account
let account = Account(altAccount, context: context)
account.isActiveAccount = true
let otherAccountsFetchRequest = Account.fetchRequest() as NSFetchRequest<Account>
otherAccountsFetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(Account.identifier), account.identifier)
let otherAccounts = try context.fetch(otherAccountsFetchRequest)
otherAccounts.forEach(context.delete(_:))
for account in otherAccounts
{
account.isActiveAccount = false
}
// Team
let team = Team(altTeam, account: account, context: context)
team.isActiveTeam = true
let otherTeamsFetchRequest = Team.fetchRequest() as NSFetchRequest<Team>
otherTeamsFetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(Team.identifier), team.identifier)
let otherTeams = try context.fetch(otherTeamsFetchRequest)
otherTeams.forEach(context.delete(_:))
for team in otherTeams
{
team.isActiveTeam = false
}
// Save
try context.save()
@@ -264,27 +272,12 @@ private extension AuthenticationOperation
case .success(let teams):
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
do
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
{
let fetchRequest = Team.fetchRequest() as NSFetchRequest<Team>
fetchRequest.fetchLimit = 1
fetchRequest.returnsObjectsAsFaults = false
let fetchedTeams = try context.fetch(fetchRequest)
if let fetchedTeam = fetchedTeams.first, let altTeam = teams.first(where: { $0.identifier == fetchedTeam.identifier })
{
completionHandler(.success(altTeam))
}
else
{
selectTeam(from: teams)
}
completionHandler(.success(altTeam))
}
catch
else
{
print("Error fetching Teams.", error)
selectTeam(from: teams)
}
}

View File

@@ -57,16 +57,7 @@ private extension SelectTeamViewController
dataSource.proxy = self
dataSource.cellConfigurationHandler = { [weak self] (cell, team, indexPath) in
cell.textLabel?.text = team.name
switch team.type
{
case .unknown: cell.detailTextLabel?.text = NSLocalizedString("Unknown", comment: "")
case .free: cell.detailTextLabel?.text = NSLocalizedString("Free Developer Account", comment: "")
case .individual: cell.detailTextLabel?.text = NSLocalizedString("Individual", comment: "")
case .organization: cell.detailTextLabel?.text = NSLocalizedString("Organization", comment: "")
@unknown default: cell.detailTextLabel?.text = nil
}
cell.detailTextLabel?.text = team.type.localizedDescription
cell.accessoryType = (self?.selectedTeam == team) ? .checkmark : .none
}

View File

@@ -21,6 +21,7 @@
<segue destination="XF0-gk-CxQ" kind="relationship" relationship="viewControllers" id="uf8-vX-M3B"/>
<segue destination="v8c-d9-T9x" kind="relationship" relationship="viewControllers" id="fqx-5p-YCD"/>
<segue destination="gJ2-NJ-8DO" kind="relationship" relationship="viewControllers" id="J4s-tm-3YV"/>
<segue destination="MGm-Zy-ffn" kind="relationship" relationship="viewControllers" id="quv-RY-0rM"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
@@ -468,11 +469,145 @@
</objects>
<point key="canvasLocation" x="1518" y="1124"/>
</scene>
<!--My Apps-->
<!--Account-->
<scene sceneID="GaO-Ug-BdZ">
<objects>
<navigationController id="MGm-Zy-ffn" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Account" image="second" id="8Ic-ki-txH"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="rzJ-pZ-611">
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="VBC-qD-V1a" kind="relationship" relationship="rootViewController" id="tgI-RK-57z"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="cM4-hZ-uHG" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="750" y="1846"/>
</scene>
<!--Account-->
<scene sceneID="Xdi-2V-rwM">
<objects>
<tableViewController id="VBC-qD-V1a" customClass="AccountViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="dOC-Gz-Ieu">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<sections>
<tableViewSection headerTitle="Account" id="nOs-a4-lBS">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="4Qf-b3-Kd1" detailTextLabel="zvb-TJ-uGW" style="IBUITableViewCellStyleValue1" id="HgQ-vv-9nH">
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="HgQ-vv-9nH" id="SSv-nz-f4V">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4Qf-b3-Kd1">
<rect key="frame" x="16" y="12" width="45" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Riley Testut (iOS Developer)" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="zvb-TJ-uGW">
<rect key="frame" x="144.5" y="12" width="214.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="1UL-2f-ayi" detailTextLabel="2YH-D5-AnU" style="IBUITableViewCellStyleValue1" id="7cR-Qb-5GU">
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7cR-Qb-5GU" id="iPP-TB-jnD">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Email" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="1UL-2f-ayi">
<rect key="frame" x="16" y="12" width="41" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="riley@rileytestut.com" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2YH-D5-AnU">
<rect key="frame" x="198" y="12" width="161" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Team" id="xqO-qN-967">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="ktC-F0-e9P" detailTextLabel="GtD-Jo-ONK" style="IBUITableViewCellStyleSubtitle" id="itp-Ya-UBR">
<rect key="frame" x="0.0" y="199.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="itp-Ya-UBR" id="w1A-z5-P4W">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ktC-F0-e9P">
<rect key="frame" x="16" y="5" width="33.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="GtD-Jo-ONK">
<rect key="frame" x="16" y="25.5" width="44" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection footerTitle="" id="Yg2-vc-vLQ">
<cells/>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="VBC-qD-V1a" id="1Xd-SN-tww"/>
<outlet property="delegate" destination="VBC-qD-V1a" id="KEk-wr-hab"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Account" id="Mtw-26-mVI">
<barButtonItem key="rightBarButtonItem" title="Sign Out" id="0wM-zj-gVA">
<color key="tintColor" red="1" green="0.14901960780000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="signOut:" destination="VBC-qD-V1a" id="X7d-Qp-VWw"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="accountEmailLabel" destination="2YH-D5-AnU" id="fyD-e7-ygs"/>
<outlet property="accountNameLabel" destination="zvb-TJ-uGW" id="mCh-p8-qCs"/>
<outlet property="teamNameLabel" destination="ktC-F0-e9P" id="2Zg-sh-2mY"/>
<outlet property="teamTypeLabel" destination="GtD-Jo-ONK" id="Jzp-2K-Bjk"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="BE4-68-0PU" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1518" y="1847"/>
</scene>
<!--Updates-->
<scene sceneID="H4l-3g-QrV">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="gJ2-NJ-8DO" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="My Apps" image="second" id="n2m-w3-Ltw"/>
<tabBarItem key="tabBarItem" title="Updates" image="first" id="n2m-w3-Ltw"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="mb8-TA-qKp">
<rect key="frame" x="0.0" y="20" width="375" height="96"/>

View File

@@ -20,6 +20,14 @@ class Keychain
private init()
{
}
func reset()
{
self.appleIDEmailAddress = nil
self.appleIDPassword = nil
self.signingCertificatePrivateKey = nil
self.signingCertificateIdentifier = nil
}
}
extension Keychain

View File

@@ -12,7 +12,7 @@ import CoreData
import AltSign
@objc(Account)
class Account: NSManagedObject
class Account: NSManagedObject, Fetchable
{
var localizedName: String {
var components = PersonNameComponents()
@@ -30,6 +30,8 @@ class Account: NSManagedObject
@NSManaged var firstName: String
@NSManaged var lastName: String
@NSManaged var isActiveAccount: Bool
/* Relationships */
@NSManaged var teams: Set<Team>

View File

@@ -4,6 +4,7 @@
<attribute name="appleID" attributeType="String" syncable="YES"/>
<attribute name="firstName" attributeType="String" syncable="YES"/>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isActiveAccount" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="lastName" attributeType="String" syncable="YES"/>
<relationship name="teams" toMany="YES" deletionRule="Cascade" destinationEntity="Team" inverseName="account" inverseEntity="Team" syncable="YES"/>
<uniquenessConstraints>
@@ -39,6 +40,7 @@
</entity>
<entity name="Team" representedClassName="Team" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isActiveTeam" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="teams" inverseEntity="Account" syncable="YES"/>
@@ -49,9 +51,9 @@
</uniquenessConstraints>
</entity>
<elements>
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
<element name="App" positionX="-63" positionY="-18" width="128" height="210"/>
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="120"/>
<element name="Team" positionX="-45" positionY="81" width="128" height="105"/>
<element name="Account" positionX="-36" positionY="90" width="128" height="120"/>
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
</elements>
</model>

View File

@@ -38,6 +38,35 @@ public extension DatabaseManager
completionHandler(error)
}
}
func signOut(completionHandler: @escaping (Error?) -> Void)
{
self.persistentContainer.performBackgroundTask { (context) in
if let account = self.activeAccount(in: context)
{
account.isActiveAccount = false
}
if let team = self.activeTeam(in: context)
{
team.isActiveTeam = false
}
do
{
try context.save()
Keychain.shared.reset()
completionHandler(nil)
}
catch
{
print("Failed to save when signing out.", error)
completionHandler(error)
}
}
}
}
public extension DatabaseManager
@@ -46,3 +75,22 @@ public extension DatabaseManager
return self.persistentContainer.viewContext
}
}
extension DatabaseManager
{
func activeAccount(in context: NSManagedObjectContext = DatabaseManager.shared.viewContext) -> Account?
{
let predicate = NSPredicate(format: "%K == YES", #keyPath(Account.isActiveAccount))
let activeAccount = Account.first(satisfying: predicate, in: context)
return activeAccount
}
func activeTeam(in context: NSManagedObjectContext = DatabaseManager.shared.viewContext) -> Team?
{
let predicate = NSPredicate(format: "%K == YES", #keyPath(Team.isActiveTeam))
let activeTeam = Team.first(satisfying: predicate, in: context)
return activeTeam
}
}

View File

@@ -11,14 +11,30 @@ import CoreData
import AltSign
extension ALTTeamType
{
var localizedDescription: String {
switch self
{
case .free: return NSLocalizedString("Free Developer Account", comment: "")
case .individual: return NSLocalizedString("Individual", comment: "")
case .organization: return NSLocalizedString("Organization", comment: "")
case .unknown: fallthrough
@unknown default: return NSLocalizedString("Unknown", comment: "")
}
}
}
@objc(Team)
class Team: NSManagedObject
class Team: NSManagedObject, Fetchable
{
/* Properties */
@NSManaged var name: String
@NSManaged var identifier: String
@NSManaged var type: ALTTeamType
@NSManaged var isActiveTeam: Bool
/* Relationships */
@NSManaged private(set) var account: Account!

View File

@@ -0,0 +1,61 @@
//
// NSManagedObject+Conveniences.swift
// AltStore
//
// Created by Riley Testut on 6/6/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import CoreData
protocol Fetchable: NSManagedObject
{
}
extension Fetchable
{
static func first(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext) -> Self?
{
let managedObjects = Self.all(satisfying: predicate, sortedBy: sortDescriptors, in: context, returnFirstResult: true)
return managedObjects.first
}
static func all(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext) -> [Self]
{
let managedObjects = Self.all(satisfying: predicate, sortedBy: sortDescriptors, in: context, returnFirstResult: false)
return managedObjects
}
private static func all(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext, returnFirstResult: Bool) -> [Self]
{
let registeredObjects = context.registeredObjects.lazy.compactMap({ $0 as? Self }).filter({ predicate?.evaluate(with: $0) != false })
if let managedObject = registeredObjects.first, returnFirstResult
{
return [managedObject]
}
let fetchRequest = self.fetchRequest() as! NSFetchRequest<Self>
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = sortDescriptors
do
{
let managedObjects = try context.fetch(fetchRequest)
if let managedObject = managedObjects.first, returnFirstResult
{
return [managedObject]
}
else
{
return managedObjects
}
}
catch
{
print("Failed to fetch managed objects.", error)
return []
}
}
}