Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d279775fe | ||
|
|
820b1fb718 | ||
|
|
f6a797975f | ||
|
|
2977b79dcb | ||
|
|
0ce078a675 | ||
|
|
de74aed83e | ||
|
|
01e2f635f8 | ||
|
|
7b3f78082e | ||
|
|
046b36f4c4 | ||
|
|
1504a277d5 | ||
|
|
865e3778b8 | ||
|
|
4c9480e6de | ||
|
|
14b2a10b4e | ||
|
|
caac63c93b | ||
|
|
32b4611c1e | ||
|
|
993fa3eebb | ||
|
|
3195a3f65d | ||
|
|
b60d693056 | ||
|
|
3faed8cf5c | ||
|
|
6c91db1dcd | ||
|
|
f506988296 | ||
|
|
883e8cfbed | ||
|
|
997376938a | ||
|
|
f51e41efab | ||
|
|
1117c05349 | ||
|
|
26f799de72 | ||
|
|
9ea584c1fb | ||
|
|
73c44c5e29 | ||
|
|
00a7886941 | ||
|
|
c5b0072443 | ||
|
|
94a22da471 | ||
|
|
8bfa5c6ff3 | ||
|
|
3a190afa3b | ||
|
|
d03d7eae42 | ||
|
|
cb25e44636 | ||
|
|
405e894768 | ||
|
|
f03ae815d7 | ||
|
|
9f9710c31d | ||
|
|
ad69b9989c | ||
|
|
e6fc491f6a | ||
|
|
f5d29cd2c1 | ||
|
|
f47212000b | ||
|
|
5c3b129c7f | ||
|
|
8110c12272 | ||
|
|
7536b09c4a | ||
|
|
deff48f9c3 | ||
|
|
07746174d4 | ||
|
|
e3cf7b203c | ||
|
|
ee20ac9a03 | ||
|
|
ff5e805b81 | ||
|
|
6214f1044b | ||
|
|
502a5488b0 | ||
|
|
e3bf6d6239 | ||
|
|
e510e9d992 | ||
|
|
f01e4ec753 | ||
|
|
225bbbe7af | ||
|
|
839b0b95fc | ||
|
|
f6768b2d72 | ||
|
|
6955f57063 | ||
|
|
5b59ccc6a0 | ||
|
|
936474cd1c | ||
|
|
2192a756b2 | ||
|
|
c8336d6199 | ||
|
|
8881ebb0f2 | ||
|
|
939d7c5f35 | ||
|
|
cf3977e7f3 | ||
|
|
ab8d51c000 | ||
|
|
f5ea5a140a | ||
|
|
e6bfdfdaee | ||
|
|
6635565a1c | ||
|
|
859f8a255c | ||
|
|
88ab3f0c37 | ||
|
|
66c9f547c1 | ||
|
|
a37d02d5d1 | ||
|
|
0c1f469dfa | ||
|
|
d03f963d9b | ||
|
|
22fcb940f2 | ||
|
|
82b4d28698 | ||
|
|
c2a8b59e36 | ||
|
|
eb5b1a616a | ||
|
|
8df4c97a74 | ||
|
|
d45f052f16 | ||
|
|
7d48b831ed |
@@ -13,19 +13,20 @@ extern NSErrorDomain const AltServerInstallationErrorDomain;
|
|||||||
|
|
||||||
typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError)
|
typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError)
|
||||||
{
|
{
|
||||||
ALTServerErrorUnknown,
|
ALTServerErrorUnknown = 0,
|
||||||
ALTServerErrorConnectionFailed,
|
ALTServerErrorConnectionFailed = 1,
|
||||||
ALTServerErrorLostConnection,
|
ALTServerErrorLostConnection = 2,
|
||||||
|
|
||||||
ALTServerErrorDeviceNotFound,
|
ALTServerErrorDeviceNotFound = 3,
|
||||||
ALTServerErrorDeviceWriteFailed,
|
ALTServerErrorDeviceWriteFailed = 4,
|
||||||
|
|
||||||
ALTServerErrorInvalidRequest,
|
ALTServerErrorInvalidRequest = 5,
|
||||||
ALTServerErrorInvalidResponse,
|
ALTServerErrorInvalidResponse = 6,
|
||||||
|
|
||||||
ALTServerErrorInvalidApp,
|
ALTServerErrorInvalidApp = 7,
|
||||||
ALTServerErrorInstallationFailed,
|
ALTServerErrorInstallationFailed = 8,
|
||||||
ALTServerErrorMaximumFreeAppLimitReached,
|
ALTServerErrorMaximumFreeAppLimitReached = 9,
|
||||||
|
ALTServerErrorUnsupportediOSVersion = 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServ
|
|||||||
return NSLocalizedString(@"Lost connection to AltServer.", @"");
|
return NSLocalizedString(@"Lost connection to AltServer.", @"");
|
||||||
|
|
||||||
case ALTServerErrorDeviceNotFound:
|
case ALTServerErrorDeviceNotFound:
|
||||||
return NSLocalizedString(@"AltServer could not locate this device.", @"");
|
return NSLocalizedString(@"AltServer could not find this device.", @"");
|
||||||
|
|
||||||
case ALTServerErrorDeviceWriteFailed:
|
case ALTServerErrorDeviceWriteFailed:
|
||||||
return NSLocalizedString(@"Failed to write app data to phone.", @"");
|
return NSLocalizedString(@"Failed to write app data to device.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidRequest:
|
case ALTServerErrorInvalidRequest:
|
||||||
return NSLocalizedString(@"AltServer received an invalid request.", @"");
|
return NSLocalizedString(@"AltServer received an invalid request.", @"");
|
||||||
@@ -58,6 +58,9 @@ NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServ
|
|||||||
|
|
||||||
case ALTServerErrorMaximumFreeAppLimitReached:
|
case ALTServerErrorMaximumFreeAppLimitReached:
|
||||||
return NSLocalizedString(@"You have reached the limit of 3 apps per device.", @"");
|
return NSLocalizedString(@"You have reached the limit of 3 apps per device.", @"");
|
||||||
|
|
||||||
|
case ALTServerErrorUnsupportediOSVersion:
|
||||||
|
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install AltStore.", @"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import UserNotifications
|
|||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
|
import LaunchAtLogin
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
@IBOutlet private var appMenu: NSMenu!
|
@IBOutlet private var appMenu: NSMenu!
|
||||||
@IBOutlet private var connectedDevicesMenu: NSMenu!
|
@IBOutlet private var connectedDevicesMenu: NSMenu!
|
||||||
|
@IBOutlet private var launchAtLoginMenuItem: NSMenuItem!
|
||||||
|
|
||||||
private weak var authenticationAppleIDTextField: NSTextField?
|
private weak var authenticationAppleIDTextField: NSTextField?
|
||||||
private weak var authenticationPasswordTextField: NSSecureTextField?
|
private weak var authenticationPasswordTextField: NSSecureTextField?
|
||||||
@@ -43,6 +46,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
self.statusItem = item
|
self.statusItem = item
|
||||||
|
|
||||||
self.connectedDevicesMenu.delegate = self
|
self.connectedDevicesMenu.delegate = self
|
||||||
|
|
||||||
|
if !UserDefaults.standard.didPresentInitialNotification
|
||||||
|
{
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = NSLocalizedString("AltServer Running", comment: "")
|
||||||
|
content.body = NSLocalizedString("AltServer runs in the background as a menu bar app listening for AltStore.", comment: "")
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
|
||||||
|
UserDefaults.standard.didPresentInitialNotification = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification)
|
func applicationWillTerminate(_ aNotification: Notification)
|
||||||
@@ -59,6 +74,9 @@ private extension AppDelegate
|
|||||||
|
|
||||||
self.connectedDevices = ALTDeviceManager.shared.connectedDevices
|
self.connectedDevices = ALTDeviceManager.shared.connectedDevices
|
||||||
|
|
||||||
|
self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
|
||||||
|
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
|
||||||
|
|
||||||
let x = button.frame.origin.x
|
let x = button.frame.origin.x
|
||||||
let y = button.frame.origin.y - 5
|
let y = button.frame.origin.y - 5
|
||||||
|
|
||||||
@@ -78,7 +96,11 @@ private extension AppDelegate
|
|||||||
|
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
|
alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
|
||||||
alert.informativeText = NSLocalizedString("Your Apple ID and password are not saved and are only sent to Apple for authentication.", comment: "")
|
alert.informativeText = NSLocalizedString("""
|
||||||
|
Your Apple ID and password are not saved and are only sent to Apple for authentication.
|
||||||
|
|
||||||
|
If you have two-factor authentication enabled, please create an app-specific password for use with AltStore at https://appleid.apple.com.
|
||||||
|
""", comment: "")
|
||||||
|
|
||||||
let textFieldSize = NSSize(width: 300, height: 22)
|
let textFieldSize = NSSize(width: 300, height: 22)
|
||||||
|
|
||||||
@@ -121,22 +143,54 @@ private extension AppDelegate
|
|||||||
|
|
||||||
let device = self.connectedDevices[index]
|
let device = self.connectedDevices[index]
|
||||||
ALTDeviceManager.shared.installAltStore(to: device, appleID: username, password: password) { (result) in
|
ALTDeviceManager.shared.installAltStore(to: device, appleID: username, password: password) { (result) in
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success:
|
case .success:
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
content.title = NSLocalizedString("Installation Succeeded", comment: "")
|
content.title = NSLocalizedString("Installation Succeeded", comment: "")
|
||||||
content.body = String(format: NSLocalizedString("AltStore was successfully installed on %@.", comment: ""), device.name)
|
content.body = String(format: NSLocalizedString("AltStore was successfully installed on %@.", comment: ""), device.name)
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
content.title = NSLocalizedString("Installation Failed", comment: "")
|
|
||||||
content.body = error.localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||||
UNUserNotificationCenter.current().add(request)
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
|
||||||
|
case .failure(InstallError.cancelled):
|
||||||
|
// Ignore
|
||||||
|
break
|
||||||
|
|
||||||
|
case .failure(let error as NSError):
|
||||||
|
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.alertStyle = .critical
|
||||||
|
alert.messageText = NSLocalizedString("Installation Failed", comment: "")
|
||||||
|
|
||||||
|
if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? Error
|
||||||
|
{
|
||||||
|
alert.informativeText = underlyingError.localizedDescription
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alert.informativeText = error.localizedDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
||||||
|
|
||||||
|
alert.runModal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
|
||||||
|
{
|
||||||
|
if item.state == .on
|
||||||
|
{
|
||||||
|
item.state = .off
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.state = .on
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchAtLogin.isEnabled.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,63 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@16.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@32-1.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@32.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@64.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@128.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@256-1.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "256x256",
|
"size" : "256x256",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@256.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "256x256",
|
"size" : "256x256",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@512-1.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "512x512",
|
"size" : "512x512",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@512.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "mac",
|
|
||||||
"size" : "512x512",
|
"size" : "512x512",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "Icon@1024.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@1024.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@128.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@16.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256-1.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32-1.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512-1.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
AltServer/Assets.xcassets/AppIcon.appiconset/Icon@64.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
@@ -2,12 +2,12 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "MenuBarIcon.png",
|
"filename" : "MenuBar@19.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "MenuBarIcon@2x.png",
|
"filename" : "MenuBar@38.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@19.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@38.png
vendored
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -61,6 +61,7 @@
|
|||||||
<outlet property="authenticationAppleIDTextField" destination="zLd-d8-ghZ" id="wW5-0J-zdq"/>
|
<outlet property="authenticationAppleIDTextField" destination="zLd-d8-ghZ" id="wW5-0J-zdq"/>
|
||||||
<outlet property="authenticationPasswordTextField" destination="9rp-Vx-rvB" id="ZoC-DI-jzQ"/>
|
<outlet property="authenticationPasswordTextField" destination="9rp-Vx-rvB" id="ZoC-DI-jzQ"/>
|
||||||
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
|
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
|
||||||
|
<outlet property="launchAtLoginMenuItem" destination="IyR-FQ-upe" id="Fxn-EP-hwH"/>
|
||||||
</connections>
|
</connections>
|
||||||
</customObject>
|
</customObject>
|
||||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||||
@@ -93,6 +94,10 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>
|
||||||
|
<menuItem title="Launch at Login" id="IyR-FQ-upe" userLabel="Launch At Login">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="mVM-Nm-Zi9"/>
|
<menuItem isSeparatorItem="YES" id="mVM-Nm-Zi9"/>
|
||||||
<menuItem title="Quit AltServer" keyEquivalent="q" id="4sb-4s-VLi">
|
<menuItem title="Quit AltServer" keyEquivalent="q" id="4sb-4s-VLi">
|
||||||
<connections>
|
<connections>
|
||||||
|
|||||||
@@ -220,20 +220,35 @@ private extension ConnectionManager
|
|||||||
|
|
||||||
func receiveApp(from connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
func receiveApp(from connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
||||||
{
|
{
|
||||||
|
var temporaryURL: URL?
|
||||||
|
|
||||||
|
func finish(_ result: Result<Void, ALTServerError>)
|
||||||
|
{
|
||||||
|
if let temporaryURL = temporaryURL
|
||||||
|
{
|
||||||
|
do { try FileManager.default.removeItem(at: temporaryURL) }
|
||||||
|
catch { print("Failed to remove .ipa.", error) }
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(result)
|
||||||
|
}
|
||||||
|
|
||||||
self.receive(PrepareAppRequest.self, from: connection) { (result) in
|
self.receive(PrepareAppRequest.self, from: connection) { (result) in
|
||||||
print("Received request with result:", result)
|
print("Received request with result:", result)
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): finish(.failure(error))
|
||||||
case .success(let request):
|
case .success(let request):
|
||||||
self.receiveApp(for: request, from: connection) { (result) in
|
self.receiveApp(for: request, from: connection) { (result) in
|
||||||
print("Received app with result:", result)
|
print("Received app with result:", result)
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): finish(.failure(error))
|
||||||
case .success(let request, let fileURL):
|
case .success(let request, let fileURL):
|
||||||
|
temporaryURL = fileURL
|
||||||
|
|
||||||
print("Awaiting begin installation request...")
|
print("Awaiting begin installation request...")
|
||||||
|
|
||||||
self.receive(BeginInstallationRequest.self, from: connection) { (result) in
|
self.receive(BeginInstallationRequest.self, from: connection) { (result) in
|
||||||
@@ -241,7 +256,7 @@ private extension ConnectionManager
|
|||||||
|
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): finish(.failure(error))
|
||||||
case .success:
|
case .success:
|
||||||
print("Installing to device \(request.udid)...")
|
print("Installing to device \(request.udid)...")
|
||||||
|
|
||||||
@@ -249,8 +264,8 @@ private extension ConnectionManager
|
|||||||
print("Installed to device with result:", result)
|
print("Installed to device with result:", result)
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): finish(.failure(error))
|
||||||
case .success: completionHandler(.success(()))
|
case .success: finish(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
enum InstallError: Error
|
enum InstallError: LocalizedError
|
||||||
{
|
{
|
||||||
case invalidCredentials
|
case cancelled
|
||||||
case noTeam
|
case noTeam
|
||||||
case missingPrivateKey
|
case missingPrivateKey
|
||||||
case missingCertificate
|
case missingCertificate
|
||||||
|
|
||||||
var localizedDescription: String {
|
var errorDescription: String? {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .invalidCredentials: return "The provided Apple ID and password are incorrect."
|
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
||||||
case .noTeam: return "You are not a member of any developer teams."
|
case .noTeam: return "You are not a member of any developer teams."
|
||||||
case .missingPrivateKey: return "The developer certificate's private key could not be found."
|
case .missingPrivateKey: return "The developer certificate's private key could not be found."
|
||||||
case .missingCertificate: return "The developer certificate could not be found."
|
case .missingCertificate: return "The developer certificate could not be found."
|
||||||
@@ -54,12 +54,6 @@ extension ALTDeviceManager
|
|||||||
{
|
{
|
||||||
let account = try result.get()
|
let account = try result.get()
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
content.title = String(format: NSLocalizedString("Installing AltStore to %@...", comment: ""), device.name)
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
|
|
||||||
self.fetchTeam(for: account) { (result) in
|
self.fetchTeam(for: account) { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -75,6 +69,13 @@ extension ALTDeviceManager
|
|||||||
{
|
{
|
||||||
let certificate = try result.get()
|
let certificate = try result.get()
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = String(format: NSLocalizedString("Installing AltStore to %@...", comment: ""), device.name)
|
||||||
|
content.body = NSLocalizedString("This may take a few seconds.", comment: "")
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
|
||||||
self.downloadApp { (result) in
|
self.downloadApp { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -84,6 +85,15 @@ extension ALTDeviceManager
|
|||||||
|
|
||||||
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
|
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to remove downloaded .ipa.", error)
|
||||||
|
}
|
||||||
|
|
||||||
guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }
|
guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }
|
||||||
|
|
||||||
self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team) { (result) in
|
self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team) { (result) in
|
||||||
@@ -157,7 +167,7 @@ extension ALTDeviceManager
|
|||||||
|
|
||||||
func downloadApp(completionHandler: @escaping (Result<URL, Error>) -> Void)
|
func downloadApp(completionHandler: @escaping (Result<URL, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let appURL = URL(string: "https://www.dropbox.com/s/w1gn9iztlqvltyp/AltStore.ipa?dl=1")!
|
let appURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
|
||||||
|
|
||||||
let downloadTask = URLSession.shared.downloadTask(with: appURL) { (fileURL, response, error) in
|
let downloadTask = URLSession.shared.downloadTask(with: appURL) { (fileURL, response, error) in
|
||||||
do
|
do
|
||||||
@@ -188,9 +198,23 @@ extension ALTDeviceManager
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
let teams = try Result(teams, error).get()
|
let teams = try Result(teams, error).get()
|
||||||
guard let team = teams.first else { throw InstallError.noTeam }
|
|
||||||
|
|
||||||
completionHandler(.success(team))
|
if let team = teams.first(where: { $0.type == .free })
|
||||||
|
{
|
||||||
|
return completionHandler(.success(team))
|
||||||
|
}
|
||||||
|
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
|
||||||
|
{
|
||||||
|
throw InstallError.noTeam
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -206,6 +230,34 @@ extension ALTDeviceManager
|
|||||||
{
|
{
|
||||||
let certificates = try Result(certificates, error).get()
|
let certificates = try Result(certificates, error).get()
|
||||||
|
|
||||||
|
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
|
||||||
|
if certificates.contains(where: { $0.machineName?.starts(with: "AltStore") == true })
|
||||||
|
{
|
||||||
|
var isCancelled = false
|
||||||
|
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = NSLocalizedString("AltStore already installed on another device.", comment: "")
|
||||||
|
alert.informativeText = NSLocalizedString("Apps installed with AltStore on your other devices will stop working. Are you sure you want to continue?", comment: "")
|
||||||
|
|
||||||
|
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
||||||
|
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
||||||
|
|
||||||
|
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
||||||
|
|
||||||
|
let buttonIndex = alert.runModal()
|
||||||
|
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
|
||||||
|
{
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCancelled
|
||||||
|
{
|
||||||
|
return completionHandler(.failure(InstallError.cancelled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let certificate = certificates.first
|
if let certificate = certificates.first
|
||||||
{
|
{
|
||||||
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
|
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
|||||||
|
|
||||||
if (![provisioningProfile.teamIdentifier isEqualToString:installationProvisioningProfile.teamIdentifier])
|
if (![provisioningProfile.teamIdentifier isEqualToString:installationProvisioningProfile.teamIdentifier])
|
||||||
{
|
{
|
||||||
NSLog(@"Ignoring: %@", installationProvisioningProfile.teamIdentifier);
|
NSLog(@"Ignoring: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,12 +622,12 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
|
|||||||
uint64_t code = 0;
|
uint64_t code = 0;
|
||||||
instproxy_status_get_error(status, &name, &description, &code);
|
instproxy_status_get_error(status, &name, &description, &code);
|
||||||
|
|
||||||
if ((percent == -1 && progress.completedUnitCount > 0) || code != 0)
|
if ((percent == -1 && progress.completedUnitCount > 0) || code != 0 || name != NULL)
|
||||||
{
|
{
|
||||||
void (^completionHandler)(NSError *) = ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID];
|
void (^completionHandler)(NSError *) = ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID];
|
||||||
if (completionHandler != nil)
|
if (completionHandler != nil)
|
||||||
{
|
{
|
||||||
if (code != 0)
|
if (code != 0 || name != NULL)
|
||||||
{
|
{
|
||||||
NSLog(@"Error installing app. %@ (%@). %@", @(code), @(name), @(description));
|
NSLog(@"Error installing app. %@ (%@). %@", @(code), @(name), @(description));
|
||||||
|
|
||||||
@@ -638,11 +638,18 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
|
|||||||
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorMaximumFreeAppLimitReached userInfo:nil];
|
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorMaximumFreeAppLimitReached userInfo:nil];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
NSString *errorName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
|
||||||
|
if ([errorName isEqualToString:@"DeviceOSVersionTooLow"])
|
||||||
|
{
|
||||||
|
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil];
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(description)}];
|
NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(description)}];
|
||||||
|
|
||||||
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorInstallationFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}];
|
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorInstallationFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
completionHandler(error);
|
completionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ extension UserDefaults
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var didPresentInitialNotification: Bool {
|
||||||
|
get {
|
||||||
|
return self.bool(forKey: "didPresentInitialNotification")
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.set(newValue, forKey: "didPresentInitialNotification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func registerDefaults()
|
func registerDefaults()
|
||||||
{
|
{
|
||||||
if self.serverID == nil
|
if self.serverID == nil
|
||||||
@@ -22,12 +22,12 @@
|
|||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
|
<key>LSUIElement</key>
|
||||||
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
|
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
|
||||||
<key>NSMainStoryboardFile</key>
|
<key>NSMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>LSUIElement</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
||||||
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */; };
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */; };
|
||||||
|
BF100C50232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */; };
|
||||||
|
BF100C54232D7DAE006A8926 /* StoreAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */; };
|
||||||
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B0F022E25DF9005C4CF5 /* ToastView.swift */; };
|
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B0F022E25DF9005C4CF5 /* ToastView.swift */; };
|
||||||
BF1E312B229F474900370A3C /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* ConnectionManager.swift */; };
|
BF1E312B229F474900370A3C /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* ConnectionManager.swift */; };
|
||||||
BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
||||||
BF1E316022A0636400370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
BF1E316022A0636400370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
||||||
BF258CE322EBAE2800023032 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF258CE222EBAE2800023032 /* AppProtocol.swift */; };
|
BF258CE322EBAE2800023032 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF258CE222EBAE2800023032 /* AppProtocol.swift */; };
|
||||||
|
BF29012F2318F6B100D88A45 /* AppBannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF29012E2318F6B100D88A45 /* AppBannerView.xib */; };
|
||||||
|
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2901302318F7A800D88A45 /* AppBannerView.swift */; };
|
||||||
BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648722E79A3700E9056B /* AppPermission.swift */; };
|
BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648722E79A3700E9056B /* AppPermission.swift */; };
|
||||||
BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648C22E79AC800E9056B /* ALTAppPermission.m */; };
|
BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648C22E79AC800E9056B /* ALTAppPermission.m */; };
|
||||||
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */; };
|
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */; };
|
||||||
@@ -33,8 +37,12 @@
|
|||||||
BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64A122E8031100E9056B /* MergePolicy.swift */; };
|
BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64A122E8031100E9056B /* MergePolicy.swift */; };
|
||||||
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */; };
|
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */; };
|
||||||
BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */; };
|
BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */; };
|
||||||
|
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF41B805233423AE00C593A3 /* TabBarController.swift */; };
|
||||||
|
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF41B807233433C100C593A3 /* LoadingState.swift */; };
|
||||||
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002D22A714AF0051E2BC /* Keychain.swift */; };
|
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002D22A714AF0051E2BC /* Keychain.swift */; };
|
||||||
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */; };
|
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */; };
|
||||||
|
BF44CC6C232AEB90004DA9C3 /* LaunchAtLogin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */; };
|
||||||
|
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF45868F229872EA00BD7491 /* AppDelegate.swift */; };
|
BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF45868F229872EA00BD7491 /* AppDelegate.swift */; };
|
||||||
BF458694229872EA00BD7491 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF458693229872EA00BD7491 /* Assets.xcassets */; };
|
BF458694229872EA00BD7491 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF458693229872EA00BD7491 /* Assets.xcassets */; };
|
||||||
BF458697229872EA00BD7491 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF458695229872EA00BD7491 /* Main.storyboard */; };
|
BF458697229872EA00BD7491 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF458695229872EA00BD7491 /* Main.storyboard */; };
|
||||||
@@ -104,6 +112,7 @@
|
|||||||
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4588872298DD3F00BD7491 /* libxml2.tbd */; };
|
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4588872298DD3F00BD7491 /* libxml2.tbd */; };
|
||||||
BF4713A522976D1E00784A2F /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4713A422976CFC00784A2F /* openssl.framework */; };
|
BF4713A522976D1E00784A2F /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4713A422976CFC00784A2F /* openssl.framework */; };
|
||||||
BF4713A622976D1E00784A2F /* openssl.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF4713A422976CFC00784A2F /* openssl.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
BF4713A622976D1E00784A2F /* openssl.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF4713A422976CFC00784A2F /* openssl.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */; };
|
||||||
BF5AB3A82285FE7500DC914B /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; };
|
BF5AB3A82285FE7500DC914B /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; };
|
||||||
BF5AB3A92285FE7500DC914B /* AltSign.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
BF5AB3A92285FE7500DC914B /* AltSign.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */; };
|
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */; };
|
||||||
@@ -122,10 +131,15 @@
|
|||||||
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */; };
|
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */; };
|
||||||
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; };
|
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; };
|
||||||
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */; };
|
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */; };
|
||||||
BFB1169D22932DB100BB457C /* Apps-Dev.json in Resources */ = {isa = PBXBuildFile; fileRef = BFB1169C22932DB100BB457C /* Apps-Dev.json */; };
|
BFB1169D22932DB100BB457C /* apps.json in Resources */ = {isa = PBXBuildFile; fileRef = BFB1169C22932DB100BB457C /* apps.json */; };
|
||||||
|
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB364592325985F00CD0EB1 /* FindServerOperation.swift */; };
|
||||||
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */; };
|
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */; };
|
||||||
|
BFB6B21B23186D640022A802 /* NewsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21A23186D640022A802 /* NewsItem.swift */; };
|
||||||
|
BFB6B21E231870160022A802 /* NewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21D231870160022A802 /* NewsViewController.swift */; };
|
||||||
|
BFB6B220231870B00022A802 /* NewsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21F231870B00022A802 /* NewsCollectionViewCell.swift */; };
|
||||||
|
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB6B22323187A3D0022A802 /* NewsCollectionViewCell.xib */; };
|
||||||
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */; };
|
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */; };
|
||||||
BFBBE2DF22931F73002097FA /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2DE22931F73002097FA /* App.swift */; };
|
BFBBE2DF22931F73002097FA /* StoreApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2DE22931F73002097FA /* StoreApp.swift */; };
|
||||||
BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2E022931F81002097FA /* InstalledApp.swift */; };
|
BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2E022931F81002097FA /* InstalledApp.swift */; };
|
||||||
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */; };
|
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */; };
|
||||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2476D2284B9A500981D42 /* AppDelegate.swift */; };
|
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2476D2284B9A500981D42 /* AppDelegate.swift */; };
|
||||||
@@ -169,10 +183,15 @@
|
|||||||
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1D22A1A9EC000B7ED1 /* node.c */; };
|
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1D22A1A9EC000B7ED1 /* node.c */; };
|
||||||
BFD52C2122A1A9EC000B7ED1 /* node_list.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1E22A1A9EC000B7ED1 /* node_list.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 */; };
|
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; };
|
||||||
|
BFD5D6E8230CC961007955AB /* PatreonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E7230CC961007955AB /* PatreonAPI.swift */; };
|
||||||
|
BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */; };
|
||||||
|
BFD5D6EE230D8A86007955AB /* Patron.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6ED230D8A86007955AB /* Patron.swift */; };
|
||||||
|
BFD5D6F2230DD974007955AB /* Benefit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F1230DD974007955AB /* Benefit.swift */; };
|
||||||
|
BFD5D6F4230DDB0A007955AB /* Campaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F3230DDB0A007955AB /* Campaign.swift */; };
|
||||||
|
BFD5D6F6230DDB12007955AB /* Tier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F5230DDB12007955AB /* Tier.swift */; };
|
||||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */; };
|
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */; };
|
||||||
BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */; };
|
BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */; };
|
||||||
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */; };
|
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */; };
|
||||||
BFDB69FD22A9A7B7007EA6D6 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB69FC22A9A7B7007EA6D6 /* SettingsViewController.swift */; };
|
|
||||||
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */; };
|
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */; };
|
||||||
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */; };
|
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */; };
|
||||||
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */; };
|
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */; };
|
||||||
@@ -181,13 +200,22 @@
|
|||||||
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DC22F0E7F3002E24B9 /* Source.swift */; };
|
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DC22F0E7F3002E24B9 /* Source.swift */; };
|
||||||
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */; };
|
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */; };
|
||||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
||||||
|
BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE60737231ADF49002B0E8E /* Settings.storyboard */; };
|
||||||
|
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60739231ADF82002B0E8E /* SettingsViewController.swift */; };
|
||||||
|
BFE6073C231AE1E7002B0E8E /* SettingsHeaderFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */; };
|
||||||
|
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 */; };
|
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 */; };
|
BFE6326622A857C200F30809 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326522A857C100F30809 /* Team.swift */; };
|
||||||
BFE6326822A858F300F30809 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326722A858F300F30809 /* Account.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 */; };
|
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 */; };
|
||||||
|
BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B695232242D3007A79E1 /* LicensesViewController.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 */; };
|
DBAC68F8EC03F4A41D62EDE1 /* Pods_AltStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1039C07E517311FC499A0B64 /* Pods_AltStore.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -224,6 +252,7 @@
|
|||||||
files = (
|
files = (
|
||||||
BF0201BB22C2EFA3000B93E4 /* AltSign.framework in Embed Frameworks */,
|
BF0201BB22C2EFA3000B93E4 /* AltSign.framework in Embed Frameworks */,
|
||||||
BF0201BE22C2EFBC000B93E4 /* openssl.framework in Embed Frameworks */,
|
BF0201BE22C2EFBC000B93E4 /* openssl.framework in Embed Frameworks */,
|
||||||
|
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -261,6 +290,9 @@
|
|||||||
BF08858222DE795100DE9F1E /* MyAppsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewController.swift; sourceTree = "<group>"; };
|
BF08858222DE795100DE9F1E /* MyAppsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewController.swift; sourceTree = "<group>"; };
|
||||||
BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCollectionViewCell.swift; sourceTree = "<group>"; };
|
BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = "<group>"; };
|
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = "<group>"; };
|
||||||
|
BF100C46232D7828006A8926 /* AltStore 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||||
|
BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStoreToAltStore2.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
|
BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreAppPolicy.swift; sourceTree = "<group>"; };
|
||||||
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||||
BF1E3128229F474900370A3C /* ServerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = "<group>"; };
|
BF1E3128229F474900370A3C /* ServerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = "<group>"; };
|
||||||
BF1E3129229F474900370A3C /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = "<group>"; };
|
BF1E3129229F474900370A3C /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = "<group>"; };
|
||||||
@@ -271,6 +303,8 @@
|
|||||||
BF1E315022A0616100370A3C /* libAltKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAltKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF1E315022A0616100370A3C /* libAltKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAltKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BF219A7E22CAC431007676A6 /* AltStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltStore.entitlements; sourceTree = "<group>"; };
|
BF219A7E22CAC431007676A6 /* AltStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltStore.entitlements; sourceTree = "<group>"; };
|
||||||
BF258CE222EBAE2800023032 /* AppProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = "<group>"; };
|
BF258CE222EBAE2800023032 /* AppProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
BF29012E2318F6B100D88A45 /* AppBannerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AppBannerView.xib; sourceTree = "<group>"; };
|
||||||
|
BF2901302318F7A800D88A45 /* AppBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBannerView.swift; sourceTree = "<group>"; };
|
||||||
BF3D648722E79A3700E9056B /* AppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission.swift; sourceTree = "<group>"; };
|
BF3D648722E79A3700E9056B /* AppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission.swift; sourceTree = "<group>"; };
|
||||||
BF3D648B22E79AC800E9056B /* ALTAppPermission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTAppPermission.h; sourceTree = "<group>"; };
|
BF3D648B22E79AC800E9056B /* ALTAppPermission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTAppPermission.h; sourceTree = "<group>"; };
|
||||||
BF3D648C22E79AC800E9056B /* ALTAppPermission.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermission.m; sourceTree = "<group>"; };
|
BF3D648C22E79AC800E9056B /* ALTAppPermission.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermission.m; sourceTree = "<group>"; };
|
||||||
@@ -279,8 +313,11 @@
|
|||||||
BF3D64A122E8031100E9056B /* MergePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergePolicy.swift; sourceTree = "<group>"; };
|
BF3D64A122E8031100E9056B /* MergePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergePolicy.swift; sourceTree = "<group>"; };
|
||||||
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewControllerCells.swift; sourceTree = "<group>"; };
|
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewControllerCells.swift; sourceTree = "<group>"; };
|
||||||
BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTDeviceManager+Installation.swift"; sourceTree = "<group>"; };
|
BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTDeviceManager+Installation.swift"; sourceTree = "<group>"; };
|
||||||
|
BF41B805233423AE00C593A3 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = "<group>"; };
|
||||||
|
BF41B807233433C100C593A3 /* LoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingState.swift; sourceTree = "<group>"; };
|
||||||
BF43002D22A714AF0051E2BC /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
BF43002D22A714AF0051E2BC /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = "<group>"; };
|
BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = "<group>"; };
|
||||||
|
BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LaunchAtLogin.framework; path = Carthage/Build/Mac/LaunchAtLogin.framework; sourceTree = "<group>"; };
|
||||||
BF45868D229872EA00BD7491 /* AltServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltServer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF45868D229872EA00BD7491 /* AltServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltServer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BF45868F229872EA00BD7491 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
BF45868F229872EA00BD7491 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
BF458693229872EA00BD7491 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
BF458693229872EA00BD7491 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
@@ -356,6 +393,8 @@
|
|||||||
BF4588872298DD3F00BD7491 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
|
BF4588872298DD3F00BD7491 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
|
||||||
BF4588962298DE6E00BD7491 /* libzip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libzip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF4588962298DE6E00BD7491 /* libzip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libzip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BF4713A422976CFC00784A2F /* openssl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF4713A422976CFC00784A2F /* openssl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
BF54E81F2315EF0D000AE0D8 /* ALTPatreonBenefitType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitType.h; sourceTree = "<group>"; };
|
||||||
|
BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitType.m; sourceTree = "<group>"; };
|
||||||
BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallAppOperation.swift; sourceTree = "<group>"; };
|
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallAppOperation.swift; sourceTree = "<group>"; };
|
||||||
BF770E5322BC044E002A40FE /* AppOperationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppOperationContext.swift; sourceTree = "<group>"; };
|
BF770E5322BC044E002A40FE /* AppOperationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppOperationContext.swift; sourceTree = "<group>"; };
|
||||||
@@ -374,11 +413,16 @@
|
|||||||
BF9B63C5229DD44D002F0A62 /* AltSign.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF9B63C5229DD44D002F0A62 /* AltSign.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BFB11691229322E400BB457C /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
|
BFB11691229322E400BB457C /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
|
||||||
BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+ManagedObjectContext.swift"; sourceTree = "<group>"; };
|
BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+ManagedObjectContext.swift"; sourceTree = "<group>"; };
|
||||||
BFB1169C22932DB100BB457C /* Apps-Dev.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "Apps-Dev.json"; sourceTree = "<group>"; };
|
BFB1169C22932DB100BB457C /* apps.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apps.json; sourceTree = "<group>"; };
|
||||||
|
BFB364592325985F00CD0EB1 /* FindServerOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindServerOperation.swift; sourceTree = "<group>"; };
|
||||||
BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UpdateCollectionViewCell.xib; sourceTree = "<group>"; };
|
BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UpdateCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
BFB6B21A23186D640022A802 /* NewsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsItem.swift; sourceTree = "<group>"; };
|
||||||
|
BFB6B21D231870160022A802 /* NewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFB6B21F231870B00022A802 /* NewsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
BFB6B22323187A3D0022A802 /* NewsCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NewsCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
BFBAC8852295C90300587369 /* Result+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Conveniences.swift"; sourceTree = "<group>"; };
|
BFBAC8852295C90300587369 /* Result+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Conveniences.swift"; sourceTree = "<group>"; };
|
||||||
BFBBE2DC22931B20002097FA /* AltStore.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AltStore.xcdatamodel; sourceTree = "<group>"; };
|
BFBBE2DC22931B20002097FA /* AltStore.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AltStore.xcdatamodel; sourceTree = "<group>"; };
|
||||||
BFBBE2DE22931F73002097FA /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
BFBBE2DE22931F73002097FA /* StoreApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp.swift; sourceTree = "<group>"; };
|
||||||
BFBBE2E022931F81002097FA /* InstalledApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledApp.swift; sourceTree = "<group>"; };
|
BFBBE2E022931F81002097FA /* InstalledApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledApp.swift; sourceTree = "<group>"; };
|
||||||
BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadAppOperation.swift; sourceTree = "<group>"; };
|
BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadAppOperation.swift; sourceTree = "<group>"; };
|
||||||
BFD2476A2284B9A500981D42 /* AltStore.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltStore.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
BFD2476A2284B9A500981D42 /* AltStore.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltStore.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -424,10 +468,15 @@
|
|||||||
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
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; };
|
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; };
|
BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; };
|
||||||
|
BFD5D6E7230CC961007955AB /* PatreonAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonAPI.swift; sourceTree = "<group>"; };
|
||||||
|
BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonAccount.swift; sourceTree = "<group>"; };
|
||||||
|
BFD5D6ED230D8A86007955AB /* Patron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patron.swift; sourceTree = "<group>"; };
|
||||||
|
BFD5D6F1230DD974007955AB /* Benefit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benefit.swift; sourceTree = "<group>"; };
|
||||||
|
BFD5D6F3230DDB0A007955AB /* Campaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Campaign.swift; sourceTree = "<group>"; };
|
||||||
|
BFD5D6F5230DDB12007955AB /* Tier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tier.swift; sourceTree = "<group>"; };
|
||||||
BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsComponents.swift; sourceTree = "<group>"; };
|
BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsComponents.swift; sourceTree = "<group>"; };
|
||||||
BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+RelativeDate.swift"; sourceTree = "<group>"; };
|
BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+RelativeDate.swift"; sourceTree = "<group>"; };
|
||||||
BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowseCollectionViewCell.xib; sourceTree = "<group>"; };
|
BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowseCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
BFDB69FC22A9A7B7007EA6D6 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
|
||||||
BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = "<group>"; };
|
BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = "<group>"; };
|
||||||
BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResignAppOperation.swift; sourceTree = "<group>"; };
|
BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResignAppOperation.swift; sourceTree = "<group>"; };
|
||||||
BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
||||||
@@ -436,14 +485,24 @@
|
|||||||
BFE338DC22F0E7F3002E24B9 /* Source.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source.swift; sourceTree = "<group>"; };
|
BFE338DC22F0E7F3002E24B9 /* Source.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source.swift; sourceTree = "<group>"; };
|
||||||
BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchSourceOperation.swift; sourceTree = "<group>"; };
|
BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchSourceOperation.swift; sourceTree = "<group>"; };
|
||||||
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFE60737231ADF49002B0E8E /* Settings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
|
||||||
|
BFE60739231ADF82002B0E8E /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsHeaderFooterView.xib; sourceTree = "<group>"; };
|
||||||
|
BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetGroupTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderFooterView.swift; sourceTree = "<group>"; };
|
||||||
BFE6325922A83BEB00F30809 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; 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>"; };
|
|
||||||
BFE6326522A857C100F30809 /* Team.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = "<group>"; };
|
BFE6326522A857C100F30809 /* Team.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = "<group>"; };
|
||||||
BFE6326722A858F300F30809 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
|
BFE6326722A858F300F30809 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
|
||||||
BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaceCertificateViewController.swift; sourceTree = "<group>"; };
|
|
||||||
BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationOperation.swift; sourceTree = "<group>"; };
|
BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationOperation.swift; sourceTree = "<group>"; };
|
||||||
|
BFF0B68D23219520007A79E1 /* PatreonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonComponents.swift; sourceTree = "<group>"; };
|
||||||
|
BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutPatreonHeaderView.xib; sourceTree = "<group>"; };
|
||||||
|
BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFF0B695232242D3007A79E1 /* LicensesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+CompactHeight.swift"; sourceTree = "<group>"; };
|
||||||
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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
|
FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -467,6 +526,7 @@
|
|||||||
files = (
|
files = (
|
||||||
BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */,
|
BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */,
|
||||||
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */,
|
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */,
|
||||||
|
BF44CC6C232AEB90004DA9C3 /* LaunchAtLogin.framework in Frameworks */,
|
||||||
BF4588472298D4B000BD7491 /* libimobiledevice.a in Frameworks */,
|
BF4588472298D4B000BD7491 /* libimobiledevice.a in Frameworks */,
|
||||||
BF0201BD22C2EFBC000B93E4 /* openssl.framework in Frameworks */,
|
BF0201BD22C2EFBC000B93E4 /* openssl.framework in Frameworks */,
|
||||||
BF0201BA22C2EFA3000B93E4 /* AltSign.framework in Frameworks */,
|
BF0201BA22C2EFA3000B93E4 /* AltSign.framework in Frameworks */,
|
||||||
@@ -497,6 +557,39 @@
|
|||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BF055B4A233B528B0086DEA9 /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */,
|
||||||
|
);
|
||||||
|
path = Extensions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF100C4E232D7C95006A8926 /* Migrations */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF100C52232D7D9E006A8926 /* Policies */,
|
||||||
|
BF100C51232D7D91006A8926 /* Mapping Models */,
|
||||||
|
);
|
||||||
|
path = Migrations;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF100C51232D7D91006A8926 /* Mapping Models */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */,
|
||||||
|
);
|
||||||
|
path = "Mapping Models";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF100C52232D7D9E006A8926 /* Policies */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */,
|
||||||
|
);
|
||||||
|
path = Policies;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
BF1E315122A0616100370A3C /* AltKit */ = {
|
BF1E315122A0616100370A3C /* AltKit */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -515,6 +608,9 @@
|
|||||||
children = (
|
children = (
|
||||||
BF3D648B22E79AC800E9056B /* ALTAppPermission.h */,
|
BF3D648B22E79AC800E9056B /* ALTAppPermission.h */,
|
||||||
BF3D648C22E79AC800E9056B /* ALTAppPermission.m */,
|
BF3D648C22E79AC800E9056B /* ALTAppPermission.m */,
|
||||||
|
BF54E81F2315EF0D000AE0D8 /* ALTPatreonBenefitType.h */,
|
||||||
|
BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */,
|
||||||
|
BF41B807233433C100C593A3 /* LoadingState.swift */,
|
||||||
);
|
);
|
||||||
path = Types;
|
path = Types;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -537,6 +633,7 @@
|
|||||||
BF458695229872EA00BD7491 /* Main.storyboard */,
|
BF458695229872EA00BD7491 /* Main.storyboard */,
|
||||||
BF703195229F36FF006E110F /* Devices */,
|
BF703195229F36FF006E110F /* Devices */,
|
||||||
BFD52BDC22A0A659000B7ED1 /* Connections */,
|
BFD52BDC22A0A659000B7ED1 /* Connections */,
|
||||||
|
BF055B4A233B528B0086DEA9 /* Extensions */,
|
||||||
BF703194229F36F6006E110F /* Resources */,
|
BF703194229F36F6006E110F /* Resources */,
|
||||||
BF703196229F370F006E110F /* Supporting Files */,
|
BF703196229F370F006E110F /* Supporting Files */,
|
||||||
);
|
);
|
||||||
@@ -721,6 +818,16 @@
|
|||||||
path = Browse;
|
path = Browse;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BFB6B21C2318700D0022A802 /* News */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BFB6B21D231870160022A802 /* NewsViewController.swift */,
|
||||||
|
BFB6B21F231870B00022A802 /* NewsCollectionViewCell.swift */,
|
||||||
|
BFB6B22323187A3D0022A802 /* NewsCollectionViewCell.xib */,
|
||||||
|
);
|
||||||
|
path = News;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
BFBBE2E2229320A2002097FA /* My Apps */ = {
|
BFBBE2E2229320A2002097FA /* My Apps */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -770,13 +877,16 @@
|
|||||||
children = (
|
children = (
|
||||||
BF219A7E22CAC431007676A6 /* AltStore.entitlements */,
|
BF219A7E22CAC431007676A6 /* AltStore.entitlements */,
|
||||||
BFD2476D2284B9A500981D42 /* AppDelegate.swift */,
|
BFD2476D2284B9A500981D42 /* AppDelegate.swift */,
|
||||||
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */,
|
|
||||||
BFD247732284B9A500981D42 /* Main.storyboard */,
|
BFD247732284B9A500981D42 /* Main.storyboard */,
|
||||||
|
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */,
|
||||||
|
BF41B805233423AE00C593A3 /* TabBarController.swift */,
|
||||||
BFE6325822A83BA800F30809 /* Authentication */,
|
BFE6325822A83BA800F30809 /* Authentication */,
|
||||||
|
BFB6B21C2318700D0022A802 /* News */,
|
||||||
BF9ABA4322DCFF33008935CF /* Browse */,
|
BF9ABA4322DCFF33008935CF /* Browse */,
|
||||||
BF3D64A022E7FAD800E9056B /* App Detail */,
|
BF3D64A022E7FAD800E9056B /* App Detail */,
|
||||||
BFBBE2E2229320A2002097FA /* My Apps */,
|
BFBBE2E2229320A2002097FA /* My Apps */,
|
||||||
BFDB69FB22A9A7A6007EA6D6 /* Settings */,
|
BFDB69FB22A9A7A6007EA6D6 /* Settings */,
|
||||||
|
BFD5D6E6230CC94B007955AB /* Patreon */,
|
||||||
BFD2478A2284C49000981D42 /* Managing Apps */,
|
BFD2478A2284C49000981D42 /* Managing Apps */,
|
||||||
BFC51D7922972F1F00388324 /* Server */,
|
BFC51D7922972F1F00388324 /* Server */,
|
||||||
BFD247982284D7FC00981D42 /* Model */,
|
BFD247982284D7FC00981D42 /* Model */,
|
||||||
@@ -794,6 +904,7 @@
|
|||||||
BFD247852284BB3300981D42 /* Frameworks */ = {
|
BFD247852284BB3300981D42 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */,
|
||||||
BF9B63C5229DD44D002F0A62 /* AltSign.framework */,
|
BF9B63C5229DD44D002F0A62 /* AltSign.framework */,
|
||||||
BF4588962298DE6E00BD7491 /* libzip.framework */,
|
BF4588962298DE6E00BD7491 /* libzip.framework */,
|
||||||
BF4588872298DD3F00BD7491 /* libxml2.tbd */,
|
BF4588872298DD3F00BD7491 /* libxml2.tbd */,
|
||||||
@@ -802,6 +913,7 @@
|
|||||||
BF5AB3A72285FE6C00DC914B /* AltSign.framework */,
|
BF5AB3A72285FE6C00DC914B /* AltSign.framework */,
|
||||||
BF4713A422976CFC00784A2F /* openssl.framework */,
|
BF4713A422976CFC00784A2F /* openssl.framework */,
|
||||||
1039C07E517311FC499A0B64 /* Pods_AltStore.framework */,
|
1039C07E517311FC499A0B64 /* Pods_AltStore.framework */,
|
||||||
|
FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -825,6 +937,8 @@
|
|||||||
BF9ABA4C22DD16DE008935CF /* PillButton.swift */,
|
BF9ABA4C22DD16DE008935CF /* PillButton.swift */,
|
||||||
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */,
|
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */,
|
||||||
BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */,
|
BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */,
|
||||||
|
BF2901302318F7A800D88A45 /* AppBannerView.swift */,
|
||||||
|
BF29012E2318F6B100D88A45 /* AppBannerView.xib */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -832,7 +946,7 @@
|
|||||||
BFD247962284D7C100981D42 /* Resources */ = {
|
BFD247962284D7C100981D42 /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFB1169C22932DB100BB457C /* Apps-Dev.json */,
|
BFB1169C22932DB100BB457C /* apps.json */,
|
||||||
BFD247762284B9A700981D42 /* Assets.xcassets */,
|
BFD247762284B9A700981D42 /* Assets.xcassets */,
|
||||||
BF770E6822BD57DD002A40FE /* Silence.m4a */,
|
BF770E6822BD57DD002A40FE /* Silence.m4a */,
|
||||||
);
|
);
|
||||||
@@ -856,12 +970,15 @@
|
|||||||
BFB11691229322E400BB457C /* DatabaseManager.swift */,
|
BFB11691229322E400BB457C /* DatabaseManager.swift */,
|
||||||
BF3D64A122E8031100E9056B /* MergePolicy.swift */,
|
BF3D64A122E8031100E9056B /* MergePolicy.swift */,
|
||||||
BFE6326722A858F300F30809 /* Account.swift */,
|
BFE6326722A858F300F30809 /* Account.swift */,
|
||||||
BFBBE2DE22931F73002097FA /* App.swift */,
|
|
||||||
BF3D648722E79A3700E9056B /* AppPermission.swift */,
|
BF3D648722E79A3700E9056B /* AppPermission.swift */,
|
||||||
BFBBE2E022931F81002097FA /* InstalledApp.swift */,
|
BFBBE2E022931F81002097FA /* InstalledApp.swift */,
|
||||||
|
BFB6B21A23186D640022A802 /* NewsItem.swift */,
|
||||||
|
BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */,
|
||||||
BF02419322F2156E00129732 /* RefreshAttempt.swift */,
|
BF02419322F2156E00129732 /* RefreshAttempt.swift */,
|
||||||
BFE338DC22F0E7F3002E24B9 /* Source.swift */,
|
BFE338DC22F0E7F3002E24B9 /* Source.swift */,
|
||||||
|
BFBBE2DE22931F73002097FA /* StoreApp.swift */,
|
||||||
BFE6326522A857C100F30809 /* Team.swift */,
|
BFE6326522A857C100F30809 /* Team.swift */,
|
||||||
|
BF100C4E232D7C95006A8926 /* Migrations */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -874,6 +991,7 @@
|
|||||||
BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */,
|
BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */,
|
||||||
BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */,
|
BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */,
|
||||||
BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */,
|
BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */,
|
||||||
|
BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -882,16 +1000,35 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BF1E3129229F474900370A3C /* ConnectionManager.swift */,
|
BF1E3129229F474900370A3C /* ConnectionManager.swift */,
|
||||||
BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */,
|
|
||||||
);
|
);
|
||||||
path = Connections;
|
path = Connections;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BFD5D6E6230CC94B007955AB /* Patreon */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BFD5D6E7230CC961007955AB /* PatreonAPI.swift */,
|
||||||
|
BFD5D6ED230D8A86007955AB /* Patron.swift */,
|
||||||
|
BFD5D6F3230DDB0A007955AB /* Campaign.swift */,
|
||||||
|
BFD5D6F5230DDB12007955AB /* Tier.swift */,
|
||||||
|
BFD5D6F1230DD974007955AB /* Benefit.swift */,
|
||||||
|
);
|
||||||
|
path = Patreon;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
BFDB69FB22A9A7A6007EA6D6 /* Settings */ = {
|
BFDB69FB22A9A7A6007EA6D6 /* Settings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFDB69FC22A9A7B7007EA6D6 /* SettingsViewController.swift */,
|
BFE60737231ADF49002B0E8E /* Settings.storyboard */,
|
||||||
|
BFE60739231ADF82002B0E8E /* SettingsViewController.swift */,
|
||||||
|
BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */,
|
||||||
|
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */,
|
||||||
|
BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */,
|
||||||
BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */,
|
BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */,
|
||||||
|
BFF0B68D23219520007A79E1 /* PatreonViewController.swift */,
|
||||||
|
BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */,
|
||||||
|
BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */,
|
||||||
|
BFF0B695232242D3007A79E1 /* LicensesViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -913,6 +1050,7 @@
|
|||||||
BF770E5722BC3D0F002A40FE /* OperationGroup.swift */,
|
BF770E5722BC3D0F002A40FE /* OperationGroup.swift */,
|
||||||
BF770E5322BC044E002A40FE /* AppOperationContext.swift */,
|
BF770E5322BC044E002A40FE /* AppOperationContext.swift */,
|
||||||
BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */,
|
BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */,
|
||||||
|
BFB364592325985F00CD0EB1 /* FindServerOperation.swift */,
|
||||||
BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */,
|
BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */,
|
||||||
BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */,
|
BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */,
|
||||||
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */,
|
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */,
|
||||||
@@ -926,9 +1064,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFE6325922A83BEB00F30809 /* Authentication.storyboard */,
|
BFE6325922A83BEB00F30809 /* Authentication.storyboard */,
|
||||||
BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */,
|
BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */,
|
||||||
BFE6325D22A8497000F30809 /* SelectTeamViewController.swift */,
|
BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */,
|
||||||
BFE6326922A85DAF00F30809 /* ReplaceCertificateViewController.swift */,
|
|
||||||
);
|
);
|
||||||
path = Authentication;
|
path = Authentication;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1009,6 +1146,7 @@
|
|||||||
BF45868B229872EA00BD7491 /* Resources */,
|
BF45868B229872EA00BD7491 /* Resources */,
|
||||||
BF4588462298D4AA00BD7491 /* Frameworks */,
|
BF4588462298D4AA00BD7491 /* Frameworks */,
|
||||||
BF0201BC22C2EFA3000B93E4 /* Embed Frameworks */,
|
BF0201BC22C2EFA3000B93E4 /* Embed Frameworks */,
|
||||||
|
BF7FDA2C23203B6B00B5D3A4 /* Copy Launcher App */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -1136,13 +1274,18 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
BFB1169D22932DB100BB457C /* Apps-Dev.json in Resources */,
|
BFB1169D22932DB100BB457C /* apps.json in Resources */,
|
||||||
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */,
|
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */,
|
||||||
|
BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */,
|
||||||
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
||||||
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */,
|
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */,
|
||||||
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */,
|
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */,
|
||||||
|
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */,
|
||||||
|
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */,
|
||||||
BFD247752284B9A500981D42 /* Main.storyboard in Resources */,
|
BFD247752284B9A500981D42 /* Main.storyboard in Resources */,
|
||||||
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */,
|
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */,
|
||||||
|
BFE6073C231AE1E7002B0E8E /* SettingsHeaderFooterView.xib in Resources */,
|
||||||
|
BF29012F2318F6B100D88A45 /* AppBannerView.xib in Resources */,
|
||||||
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */,
|
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -1160,18 +1303,38 @@
|
|||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-AltStore/Pods-AltStore-frameworks.sh",
|
"${PODS_ROOT}/Target Support Files/Pods-AltStore/Pods-AltStore-frameworks.sh",
|
||||||
"${BUILT_PRODUCTS_DIR}/KeychainAccess/KeychainAccess.framework",
|
"${BUILT_PRODUCTS_DIR}/KeychainAccess/KeychainAccess.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/Nuke/Nuke.framework",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
);
|
);
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainAccess.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainAccess.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nuke.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AltStore/Pods-AltStore-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AltStore/Pods-AltStore-frameworks.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
BF7FDA2C23203B6B00B5D3A4 /* Copy Launcher App */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Copy Launcher App";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PROJECT_DIR}/Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh\"\n";
|
||||||
|
};
|
||||||
FFB93342C7EB2021A1FFFB6A /* [CP] Check Pods Manifest.lock */ = {
|
FFB93342C7EB2021A1FFFB6A /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -1288,35 +1451,49 @@
|
|||||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
||||||
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
||||||
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
|
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
|
||||||
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
|
|
||||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
||||||
BFDB69FD22A9A7B7007EA6D6 /* SettingsViewController.swift in Sources */,
|
BFD5D6F6230DDB12007955AB /* Tier.swift in Sources */,
|
||||||
BFE6326A22A85DAF00F30809 /* ReplaceCertificateViewController.swift in Sources */,
|
|
||||||
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */,
|
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */,
|
||||||
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
||||||
|
BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */,
|
||||||
BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */,
|
BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */,
|
||||||
|
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */,
|
||||||
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */,
|
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */,
|
||||||
BFBBE2DF22931F73002097FA /* App.swift in Sources */,
|
BFBBE2DF22931F73002097FA /* StoreApp.swift in Sources */,
|
||||||
|
BFB6B21E231870160022A802 /* NewsViewController.swift in Sources */,
|
||||||
|
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 */,
|
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */,
|
||||||
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */,
|
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */,
|
||||||
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */,
|
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */,
|
||||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
||||||
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */,
|
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */,
|
||||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||||
|
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
||||||
|
BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */,
|
||||||
BFE6326822A858F300F30809 /* Account.swift in Sources */,
|
BFE6326822A858F300F30809 /* Account.swift in Sources */,
|
||||||
BFE6326622A857C200F30809 /* Team.swift in Sources */,
|
BFE6326622A857C200F30809 /* Team.swift in Sources */,
|
||||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||||
|
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
||||||
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
||||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
||||||
|
BF100C54232D7DAE006A8926 /* StoreAppPolicy.swift in Sources */,
|
||||||
|
BF100C50232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel in Sources */,
|
||||||
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
||||||
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
||||||
BF02419422F2156E00129732 /* RefreshAttempt.swift in Sources */,
|
BF02419422F2156E00129732 /* RefreshAttempt.swift in Sources */,
|
||||||
|
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */,
|
||||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
||||||
BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */,
|
|
||||||
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
||||||
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
||||||
BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */,
|
BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */,
|
||||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
||||||
|
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
||||||
|
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
||||||
|
BFD5D6EE230D8A86007955AB /* Patron.swift in Sources */,
|
||||||
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
||||||
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */,
|
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */,
|
||||||
BF258CE322EBAE2800023032 /* AppProtocol.swift in Sources */,
|
BF258CE322EBAE2800023032 /* AppProtocol.swift in Sources */,
|
||||||
@@ -1326,19 +1503,26 @@
|
|||||||
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */,
|
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */,
|
||||||
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */,
|
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */,
|
||||||
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */,
|
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */,
|
||||||
|
BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */,
|
||||||
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */,
|
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */,
|
||||||
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
||||||
BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */,
|
BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */,
|
||||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
||||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||||
|
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||||
BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */,
|
BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */,
|
||||||
|
BFD5D6F2230DD974007955AB /* Benefit.swift in Sources */,
|
||||||
|
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
||||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
||||||
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
|
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
|
||||||
|
BFB6B220231870B00022A802 /* NewsCollectionViewCell.swift in Sources */,
|
||||||
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */,
|
BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */,
|
||||||
|
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */,
|
||||||
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */,
|
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */,
|
||||||
BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */,
|
BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */,
|
||||||
|
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */,
|
||||||
BF770E5622BC3C03002A40FE /* Server.swift in Sources */,
|
BF770E5622BC3C03002A40FE /* Server.swift in Sources */,
|
||||||
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */,
|
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */,
|
||||||
);
|
);
|
||||||
@@ -1434,6 +1618,10 @@
|
|||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = 6XVY5G3U44;
|
DEVELOPMENT_TEAM = 6XVY5G3U44;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PROJECT_DIR)/Carthage/Build/Mac",
|
||||||
|
);
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
HAVE_OPENSSL,
|
HAVE_OPENSSL,
|
||||||
@@ -1459,7 +1647,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.14.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltServer;
|
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltServer;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -1479,6 +1667,10 @@
|
|||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = 6XVY5G3U44;
|
DEVELOPMENT_TEAM = 6XVY5G3U44;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PROJECT_DIR)/Carthage/Build/Mac",
|
||||||
|
);
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
HAVE_OPENSSL,
|
HAVE_OPENSSL,
|
||||||
@@ -1504,7 +1696,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.14.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltServer;
|
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltServer;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -1810,9 +2002,10 @@
|
|||||||
BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */ = {
|
BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */ = {
|
||||||
isa = XCVersionGroup;
|
isa = XCVersionGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF100C46232D7828006A8926 /* AltStore 2.xcdatamodel */,
|
||||||
BFBBE2DC22931B20002097FA /* AltStore.xcdatamodel */,
|
BFBBE2DC22931B20002097FA /* AltStore.xcdatamodel */,
|
||||||
);
|
);
|
||||||
currentVersion = BFBBE2DC22931B20002097FA /* AltStore.xcdatamodel */;
|
currentVersion = BF100C46232D7828006A8926 /* AltStore 2.xcdatamodel */;
|
||||||
path = AltStore.xcdatamodeld;
|
path = AltStore.xcdatamodeld;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
versionGroupType = wrapper.xcdatamodel;
|
versionGroupType = wrapper.xcdatamodel;
|
||||||
|
|||||||
@@ -4,3 +4,4 @@
|
|||||||
|
|
||||||
#import "NSError+ALTServerError.h"
|
#import "NSError+ALTServerError.h"
|
||||||
#import "ALTAppPermission.h"
|
#import "ALTAppPermission.h"
|
||||||
|
#import "ALTPatreonBenefitType.h"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import Nuke
|
||||||
|
|
||||||
extension AppContentViewController
|
extension AppContentViewController
|
||||||
{
|
{
|
||||||
private enum Row: Int, CaseIterable
|
private enum Row: Int, CaseIterable
|
||||||
@@ -52,11 +54,9 @@ class AppContentViewController: UITableViewController
|
|||||||
@IBOutlet private var permissionsCollectionView: UICollectionView!
|
@IBOutlet private var permissionsCollectionView: UICollectionView!
|
||||||
|
|
||||||
var preferredScreenshotSize: CGSize? {
|
var preferredScreenshotSize: CGSize? {
|
||||||
guard let image = self.screenshotsDataSource.items.first else { return nil }
|
|
||||||
|
|
||||||
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
||||||
|
|
||||||
let aspectRatio = image.size.height / image.size.width
|
let aspectRatio: CGFloat = 16.0 / 9.0 // Hardcoded for now.
|
||||||
|
|
||||||
let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2)
|
let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2)
|
||||||
|
|
||||||
@@ -125,14 +125,39 @@ class AppContentViewController: UITableViewController
|
|||||||
|
|
||||||
private extension AppContentViewController
|
private extension AppContentViewController
|
||||||
{
|
{
|
||||||
func makeScreenshotsDataSource() -> RSTArrayCollectionViewDataSource<UIImage>
|
func makeScreenshotsDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>
|
||||||
{
|
{
|
||||||
let screenshots = self.app.screenshotNames.compactMap(UIImage.init(named:))
|
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>(items: self.app.screenshotURLs as [NSURL])
|
||||||
|
|
||||||
let dataSource = RSTArrayCollectionViewDataSource(items: screenshots)
|
|
||||||
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
||||||
let cell = cell as! ScreenshotCollectionViewCell
|
let cell = cell as! ScreenshotCollectionViewCell
|
||||||
cell.imageView.image = screenshot
|
cell.imageView.image = nil
|
||||||
|
cell.imageView.isIndicatingActivity = true
|
||||||
|
}
|
||||||
|
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
||||||
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
|
ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in
|
||||||
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
|
if let image = response?.image
|
||||||
|
{
|
||||||
|
completionHandler(image, nil)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completionHandler(nil, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||||
|
let cell = cell as! ScreenshotCollectionViewCell
|
||||||
|
cell.imageView.isIndicatingActivity = false
|
||||||
|
cell.imageView.image = image
|
||||||
|
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
print("Error loading image:", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import Nuke
|
||||||
|
|
||||||
class AppViewController: UIViewController
|
class AppViewController: UIViewController
|
||||||
{
|
{
|
||||||
var app: StoreApp!
|
var app: StoreApp!
|
||||||
@@ -35,6 +37,7 @@ class AppViewController: UIViewController
|
|||||||
@IBOutlet private var developerLabel: UILabel!
|
@IBOutlet private var developerLabel: UILabel!
|
||||||
@IBOutlet private var downloadButton: PillButton!
|
@IBOutlet private var downloadButton: PillButton!
|
||||||
@IBOutlet private var appIconImageView: UIImageView!
|
@IBOutlet private var appIconImageView: UIImageView!
|
||||||
|
@IBOutlet private var betaBadgeView: UIImageView!
|
||||||
|
|
||||||
@IBOutlet private var backgroundAppIconImageView: UIImageView!
|
@IBOutlet private var backgroundAppIconImageView: UIImageView!
|
||||||
@IBOutlet private var backgroundBlurView: UIVisualEffectView!
|
@IBOutlet private var backgroundBlurView: UIVisualEffectView!
|
||||||
@@ -83,17 +86,16 @@ class AppViewController: UIViewController
|
|||||||
self.nameLabel.text = self.app.name
|
self.nameLabel.text = self.app.name
|
||||||
self.developerLabel.text = self.app.developerName
|
self.developerLabel.text = self.app.developerName
|
||||||
self.developerLabel.textColor = self.app.tintColor
|
self.developerLabel.textColor = self.app.tintColor
|
||||||
self.appIconImageView.image = UIImage(named: self.app.iconName)
|
self.appIconImageView.image = nil
|
||||||
self.appIconImageView.tintColor = self.app.tintColor
|
self.appIconImageView.tintColor = self.app.tintColor
|
||||||
self.downloadButton.tintColor = self.app.tintColor
|
self.downloadButton.tintColor = self.app.tintColor
|
||||||
self.backgroundAppIconImageView.image = UIImage(named: self.app.iconName)
|
self.betaBadgeView.isHidden = !self.app.isBeta
|
||||||
|
|
||||||
self.backButtonContainerView.tintColor = self.app.tintColor
|
self.backButtonContainerView.tintColor = self.app.tintColor
|
||||||
|
|
||||||
self.navigationController?.navigationBar.tintColor = self.app.tintColor
|
self.navigationController?.navigationBar.tintColor = self.app.tintColor
|
||||||
self.navigationBarDownloadButton.tintColor = self.app.tintColor
|
self.navigationBarDownloadButton.tintColor = self.app.tintColor
|
||||||
self.navigationBarAppNameLabel.text = self.app.name
|
self.navigationBarAppNameLabel.text = self.app.name
|
||||||
self.navigationBarAppIconImageView.image = UIImage(named: self.app.iconName)
|
|
||||||
self.navigationBarAppIconImageView.tintColor = self.app.tintColor
|
self.navigationBarAppIconImageView.tintColor = self.app.tintColor
|
||||||
|
|
||||||
self.contentSizeObservation = self.contentViewController.tableView.observe(\.contentSize) { [weak self] (tableView, change) in
|
self.contentSizeObservation = self.contentViewController.tableView.observe(\.contentSize) { [weak self] (tableView, change) in
|
||||||
@@ -108,6 +110,19 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
self._backgroundBlurEffect = self.backgroundBlurView.effect as? UIBlurEffect
|
self._backgroundBlurEffect = self.backgroundBlurView.effect as? UIBlurEffect
|
||||||
self._backgroundBlurTintColor = self.backgroundBlurView.contentView.backgroundColor
|
self._backgroundBlurTintColor = self.backgroundBlurView.contentView.backgroundColor
|
||||||
|
|
||||||
|
// Load Images
|
||||||
|
for imageView in [self.appIconImageView!, self.backgroundAppIconImageView!, self.navigationBarAppIconImageView!]
|
||||||
|
{
|
||||||
|
imageView.isIndicatingActivity = true
|
||||||
|
|
||||||
|
Nuke.loadImage(with: self.app.iconURL, options: .shared, into: imageView, progress: nil) { [weak imageView] (response, error) in
|
||||||
|
if response?.image != nil
|
||||||
|
{
|
||||||
|
imageView?.isIndicatingActivity = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool)
|
override func viewWillAppear(_ animated: Bool)
|
||||||
@@ -355,6 +370,17 @@ private extension AppViewController
|
|||||||
button.progress = progress
|
button.progress = progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Date() < self.app.versionDate
|
||||||
|
{
|
||||||
|
self.downloadButton.countdownDate = self.app.versionDate
|
||||||
|
self.navigationBarDownloadButton.countdownDate = self.app.versionDate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.downloadButton.countdownDate = nil
|
||||||
|
self.navigationBarDownloadButton.countdownDate = nil
|
||||||
|
}
|
||||||
|
|
||||||
let barButtonItem = self.navigationItem.rightBarButtonItem
|
let barButtonItem = self.navigationItem.rightBarButtonItem
|
||||||
self.navigationItem.rightBarButtonItem = nil
|
self.navigationItem.rightBarButtonItem = nil
|
||||||
self.navigationItem.rightBarButtonItem = barButtonItem
|
self.navigationItem.rightBarButtonItem = barButtonItem
|
||||||
@@ -366,7 +392,7 @@ private extension AppViewController
|
|||||||
navigationController?.navigationBar.barStyle = .default
|
navigationController?.navigationBar.barStyle = .default
|
||||||
navigationController?.navigationBar.alpha = 1.0
|
navigationController?.navigationBar.alpha = 1.0
|
||||||
navigationController?.navigationBar.barTintColor = .white
|
navigationController?.navigationBar.barTintColor = .white
|
||||||
navigationController?.navigationBar.tintColor = .altGreen
|
navigationController?.navigationBar.tintColor = .altPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
func hideNavigationBar(for navigationController: UINavigationController? = nil)
|
func hideNavigationBar(for navigationController: UINavigationController? = nil)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import UserNotifications
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import AltKit
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
private enum RefreshError: LocalizedError
|
private enum RefreshError: LocalizedError
|
||||||
@@ -49,6 +50,11 @@ private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, Uns
|
|||||||
appDelegate.receivedApplicationState(notification: name)
|
appDelegate.receivedApplicationState(notification: name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AppDelegate
|
||||||
|
{
|
||||||
|
static let openPatreonSettingsDeepLinkNotification = Notification.Name("openPatreonSettingsDeepLinkNotification")
|
||||||
|
}
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
@@ -62,12 +68,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
|
UserDefaults.standard.registerDefaults()
|
||||||
|
|
||||||
if UserDefaults.standard.firstLaunch == nil
|
if UserDefaults.standard.firstLaunch == nil
|
||||||
{
|
{
|
||||||
Keychain.shared.reset()
|
Keychain.shared.reset()
|
||||||
UserDefaults.standard.firstLaunch = Date()
|
UserDefaults.standard.firstLaunch = Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserDefaults.standard.preferredServerID = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.serverID) as? String
|
||||||
|
|
||||||
|
#if DEBUG || BETA
|
||||||
|
UserDefaults.standard.isDebugModeEnabled = true
|
||||||
|
#endif
|
||||||
|
|
||||||
self.prepareForBackgroundFetch()
|
self.prepareForBackgroundFetch()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -82,6 +96,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
{
|
{
|
||||||
AppManager.shared.update()
|
AppManager.shared.update()
|
||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
|
PatreonAPI.shared.refreshPatreonAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
||||||
|
{
|
||||||
|
return self.open(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +110,19 @@ private extension AppDelegate
|
|||||||
{
|
{
|
||||||
func setTintColor()
|
func setTintColor()
|
||||||
{
|
{
|
||||||
self.window?.tintColor = .altGreen
|
self.window?.tintColor = .altPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
func open(_ url: URL) -> Bool
|
||||||
|
{
|
||||||
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
|
||||||
|
guard let host = components.host, host.lowercased() == "patreon" else { return false }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,9 +157,27 @@ extension AppDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||||||
|
{
|
||||||
|
if UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
{
|
{
|
||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
|
if !UserDefaults.standard.presentedLaunchReminderNotification
|
||||||
|
{
|
||||||
|
let threeHours: TimeInterval = 3 * 60 * 60
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
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: "")
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
|
||||||
|
UserDefaults.standard.presentedLaunchReminderNotification = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let refreshIdentifier = UUID().uuidString
|
let refreshIdentifier = UUID().uuidString
|
||||||
|
|
||||||
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
||||||
@@ -135,9 +186,11 @@ extension AppDelegate
|
|||||||
{
|
{
|
||||||
// If finish is actually called, that means an error occured during installation.
|
// If finish is actually called, that means an error occured during installation.
|
||||||
|
|
||||||
|
if UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
{
|
||||||
ServerManager.shared.stopDiscovering()
|
ServerManager.shared.stopDiscovering()
|
||||||
|
|
||||||
self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0)
|
self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0)
|
||||||
|
}
|
||||||
|
|
||||||
taskCompletionHandler()
|
taskCompletionHandler()
|
||||||
}
|
}
|
||||||
@@ -178,12 +231,106 @@ private extension AppDelegate
|
|||||||
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||||
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||||
{
|
{
|
||||||
|
var fetchSourceResult: Result<Source, Error>?
|
||||||
|
var serversResult: Result<Void, Error>?
|
||||||
|
|
||||||
|
let dispatchGroup = DispatchGroup()
|
||||||
|
dispatchGroup.enter()
|
||||||
|
|
||||||
|
AppManager.shared.fetchSource() { (result) in
|
||||||
|
fetchSourceResult = result
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let source = try result.get()
|
||||||
|
|
||||||
|
guard let context = source.managedObjectContext else { return }
|
||||||
|
|
||||||
|
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
||||||
|
previousUpdatesFetchRequest.includesPendingChanges = false
|
||||||
|
previousUpdatesFetchRequest.resultType = .dictionaryResultType
|
||||||
|
previousUpdatesFetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier)]
|
||||||
|
|
||||||
|
let previousNewsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
|
||||||
|
previousNewsItemsFetchRequest.includesPendingChanges = false
|
||||||
|
previousNewsItemsFetchRequest.resultType = .dictionaryResultType
|
||||||
|
previousNewsItemsFetchRequest.propertiesToFetch = [#keyPath(NewsItem.identifier)]
|
||||||
|
|
||||||
|
let previousUpdates = try context.fetch(previousUpdatesFetchRequest) as! [[String: String]]
|
||||||
|
let previousNewsItems = try context.fetch(previousNewsItemsFetchRequest) as! [[String: String]]
|
||||||
|
|
||||||
|
try context.save()
|
||||||
|
|
||||||
|
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
|
||||||
|
let newsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NewsItem>
|
||||||
|
|
||||||
|
let updates = try context.fetch(updatesFetchRequest)
|
||||||
|
let newsItems = try context.fetch(newsItemsFetchRequest)
|
||||||
|
|
||||||
|
for update in updates
|
||||||
|
{
|
||||||
|
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
||||||
|
guard let storeApp = update.storeApp else { continue }
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = NSLocalizedString("New Update Available", comment: "")
|
||||||
|
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
|
||||||
|
content.sound = .default
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
for newsItem in newsItems
|
||||||
|
{
|
||||||
|
guard !previousNewsItems.contains(where: { $0[#keyPath(NewsItem.identifier)] == newsItem.identifier }) else { continue }
|
||||||
|
guard !newsItem.isSilent else { continue }
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
|
if let app = newsItem.storeApp
|
||||||
|
{
|
||||||
|
content.title = String(format: NSLocalizedString("%@ News", comment: ""), app.name)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
content.title = NSLocalizedString("AltStore News", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
content.body = newsItem.title
|
||||||
|
content.sound = .default
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Error fetching apps:", error)
|
||||||
|
|
||||||
|
fetchSourceResult = .failure(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
|
|
||||||
|
if UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
{
|
||||||
|
dispatchGroup.enter()
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
|
||||||
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
||||||
guard !installedApps.isEmpty else {
|
guard !installedApps.isEmpty else {
|
||||||
backgroundFetchCompletionHandler(.noData)
|
serversResult = .success(())
|
||||||
|
dispatchGroup.leave()
|
||||||
|
|
||||||
completionHandler(.failure(RefreshError.noInstalledApps))
|
completionHandler(.failure(RefreshError.noInstalledApps))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,74 +352,6 @@ private extension AppDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var fetchSourceResult: Result<Source, Error>?
|
|
||||||
var serversResult: Result<Void, Error>?
|
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
dispatchGroup.enter()
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
AppManager.shared.fetchSource() { (result) in
|
|
||||||
fetchSourceResult = result
|
|
||||||
dispatchGroup.leave()
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let source = try result.get()
|
|
||||||
|
|
||||||
guard let context = source.managedObjectContext else { return }
|
|
||||||
|
|
||||||
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
|
|
||||||
updatesFetchRequest.includesPendingChanges = true
|
|
||||||
|
|
||||||
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest()
|
|
||||||
previousUpdatesFetchRequest.includesPendingChanges = false
|
|
||||||
|
|
||||||
let previousUpdates = try context.fetch(previousUpdatesFetchRequest)
|
|
||||||
|
|
||||||
try context.save()
|
|
||||||
|
|
||||||
let updates = try context.fetch(updatesFetchRequest)
|
|
||||||
|
|
||||||
for update in updates
|
|
||||||
{
|
|
||||||
guard !previousUpdates.contains(where: { $0.bundleIdentifier == update.bundleIdentifier }) else { continue }
|
|
||||||
|
|
||||||
guard let storeApp = update.storeApp else { continue }
|
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
content.title = NSLocalizedString("New Update Available", comment: "")
|
|
||||||
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Error fetching apps:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .main) {
|
|
||||||
guard let fetchSourceResult = fetchSourceResult, let serversResult = serversResult else {
|
|
||||||
backgroundFetchCompletionHandler(.failed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call completionHandler early to improve chances of refreshing in the background again.
|
|
||||||
switch (fetchSourceResult, serversResult)
|
|
||||||
{
|
|
||||||
case (.success, .success): backgroundFetchCompletionHandler(.newData)
|
|
||||||
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
|
|
||||||
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for three seconds to:
|
// Wait for three seconds to:
|
||||||
// a) give us time to discover AltServers
|
// a) give us time to discover AltServers
|
||||||
// b) give other processes a chance to respond to requestAppState notification
|
// b) give other processes a chance to respond to requestAppState notification
|
||||||
@@ -323,6 +402,40 @@ private extension AppDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatchGroup.notify(queue: .main) {
|
||||||
|
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
{
|
||||||
|
guard let fetchSourceResult = fetchSourceResult else {
|
||||||
|
backgroundFetchCompletionHandler(.failed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fetchSourceResult
|
||||||
|
{
|
||||||
|
case .failure: backgroundFetchCompletionHandler(.failed)
|
||||||
|
case .success: backgroundFetchCompletionHandler(.newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(.success([:]))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
guard let fetchSourceResult = fetchSourceResult, let serversResult = serversResult else {
|
||||||
|
backgroundFetchCompletionHandler(.failed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call completionHandler early to improve chances of refreshing in the background again.
|
||||||
|
switch (fetchSourceResult, serversResult)
|
||||||
|
{
|
||||||
|
case (.success, .success): backgroundFetchCompletionHandler(.newData)
|
||||||
|
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
|
||||||
|
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func receivedApplicationState(notification: CFNotificationName)
|
func receivedApplicationState(notification: CFNotificationName)
|
||||||
{
|
{
|
||||||
let baseName = String(CFNotificationName.appIsRunning.rawValue)
|
let baseName = String(CFNotificationName.appIsRunning.rawValue)
|
||||||
|
|||||||
@@ -4,239 +4,452 @@
|
|||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||||
|
<capability name="Named colors" 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"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Apple ID-->
|
<!--Navigation Controller-->
|
||||||
<scene sceneID="3cc-cd-zDK">
|
<scene sceneID="lNR-II-WoW">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController storyboardIdentifier="authenticationViewController" id="nRn-xt-2XS" customClass="AuthenticationViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" estimatedRowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="r38-H3-S3C">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<color key="barTintColor" name="Primary"/>
|
||||||
|
<textAttributes key="titleTextAttributes">
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</textAttributes>
|
||||||
|
<textAttributes key="largeTitleTextAttributes">
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</textAttributes>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="automaticallyAdjustsItemPositions" value="NO"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</navigationBar>
|
||||||
|
</navigationController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="9J6-jc-46k" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="-164" y="735"/>
|
||||||
|
</scene>
|
||||||
|
<!--Authentication View Controller-->
|
||||||
|
<scene sceneID="OCd-xc-Ms7">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="mjy-4S-hyH">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
|
||||||
<sections>
|
|
||||||
<tableViewSection id="uDm-cx-LdY">
|
|
||||||
<string key="footerTitle">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.</string>
|
|
||||||
<cells>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ER5-4r-tld">
|
|
||||||
<rect key="frame" x="0.0" y="35" width="375" height="44"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ER5-4r-tld" id="BnC-HI-d8z">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="70T-cn-6XF">
|
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
|
||||||
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||||
|
</view>
|
||||||
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apple ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="09n-b4-DRC">
|
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="74" height="43.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
|
||||||
<constraints>
|
<subviews>
|
||||||
<constraint firstAttribute="width" constant="74" id="Y87-hZ-IsD"/>
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
|
||||||
</constraints>
|
<rect key="frame" x="16" y="6" width="343" height="397"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<subviews>
|
||||||
<nil key="textColor"/>
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Yfu-hI-0B7" userLabel="Welcome">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to AltStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="333.5" height="41"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Email Address" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="V6B-NM-wpL">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in with your Apple ID to get started." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="SNU-tv-8Au">
|
||||||
<rect key="frame" x="90" y="0.0" width="253" height="43.5"/>
|
<rect key="frame" x="0.0" y="47" width="308.5" height="20.5"/>
|
||||||
<nil key="textColor"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<textInputTraits key="textInputTraits" returnKeyType="next" enablesReturnKeyAutomatically="YES" textContentType="email"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="Aqh-MD-HFf">
|
||||||
|
<rect key="frame" x="0.0" y="117.5" width="343" height="279.5"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="Oy6-xr-cZ7">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="343" height="196.5"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="H95-7V-Kk8" userLabel="Apple ID">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="343" height="72"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="KN1-Kp-M1q">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="343" height="17"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="APPLE ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="59N-O1-6bM">
|
||||||
|
<rect key="frame" x="14" y="0.0" width="329" height="17"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="0.0"/>
|
||||||
|
</stackView>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gNe-dC-oI1">
|
||||||
|
<rect key="frame" x="0.0" y="21" width="343" height="51"/>
|
||||||
|
<subviews>
|
||||||
|
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="name@email.com" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="DBu-vt-hlo">
|
||||||
|
<rect key="frame" x="14" y="0.0" width="315" height="51"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
|
||||||
|
<textInputTraits key="textInputTraits" returnKeyType="next" textContentType="email"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="nRn-xt-2XS" id="5Us-OB-B4F"/>
|
<outlet property="delegate" destination="yO1-iT-7NP" id="G13-jV-DLX"/>
|
||||||
</connections>
|
</connections>
|
||||||
</textField>
|
</textField>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
<color key="backgroundColor" white="1" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="DBu-vt-hlo" secondAttribute="trailing" id="0Lf-vH-juh"/>
|
||||||
|
<constraint firstItem="DBu-vt-hlo" firstAttribute="centerY" secondItem="gNe-dC-oI1" secondAttribute="centerY" id="kgs-hg-ECM"/>
|
||||||
|
<constraint firstItem="DBu-vt-hlo" firstAttribute="height" secondItem="gNe-dC-oI1" secondAttribute="height" id="n7y-Xg-8MP"/>
|
||||||
|
<constraint firstItem="DBu-vt-hlo" firstAttribute="leading" secondItem="gNe-dC-oI1" secondAttribute="leadingMargin" id="sat-rb-OIu"/>
|
||||||
|
<constraint firstAttribute="height" constant="51" id="tuP-Uo-6qp"/>
|
||||||
|
</constraints>
|
||||||
|
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="14"/>
|
||||||
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
</stackView>
|
||||||
<constraint firstItem="70T-cn-6XF" firstAttribute="top" secondItem="BnC-HI-d8z" secondAttribute="top" id="Zyt-OB-o6T"/>
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="hd5-yc-rcq" userLabel="Password">
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="70T-cn-6XF" secondAttribute="trailing" id="lYn-uy-vRk"/>
|
<rect key="frame" x="0.0" y="87" width="343" height="109.5"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="70T-cn-6XF" secondAttribute="bottom" id="urj-EQ-5WK"/>
|
|
||||||
<constraint firstItem="70T-cn-6XF" firstAttribute="leading" secondItem="BnC-HI-d8z" secondAttribute="leadingMargin" id="yqr-Kr-I93"/>
|
|
||||||
</constraints>
|
|
||||||
</tableViewCellContentView>
|
|
||||||
</tableViewCell>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="E9B-Cb-M5e">
|
|
||||||
<rect key="frame" x="0.0" y="79" width="375" height="44"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="E9B-Cb-M5e" id="S4n-4w-12m">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="pON-cO-VYR">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="lvX-im-C95">
|
||||||
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="17"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vqv-cC-kya">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PASSWORD" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ava-XY-7vs">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="74" height="43.5"/>
|
<rect key="frame" x="14" y="0.0" width="329" height="17"/>
|
||||||
<constraints>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<constraint firstAttribute="width" constant="74" id="Egk-ba-Kh3"/>
|
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Password" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="z98-Sm-yDv">
|
</subviews>
|
||||||
<rect key="frame" x="90" y="0.0" width="253" height="43.5"/>
|
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="0.0"/>
|
||||||
<nil key="textColor"/>
|
</stackView>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cLc-iA-yq5">
|
||||||
|
<rect key="frame" x="0.0" y="21" width="343" height="51"/>
|
||||||
|
<subviews>
|
||||||
|
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="••••••••" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="R77-TQ-lVT">
|
||||||
|
<rect key="frame" x="14" y="0.0" width="315" height="51"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
|
||||||
<textInputTraits key="textInputTraits" returnKeyType="go" enablesReturnKeyAutomatically="YES" secureTextEntry="YES" textContentType="password"/>
|
<textInputTraits key="textInputTraits" returnKeyType="go" enablesReturnKeyAutomatically="YES" secureTextEntry="YES" textContentType="password"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="nRn-xt-2XS" id="7pH-Sf-Wmb"/>
|
<outlet property="delegate" destination="yO1-iT-7NP" id="Wpg-DV-BNL"/>
|
||||||
</connections>
|
</connections>
|
||||||
</textField>
|
</textField>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="R77-TQ-lVT" firstAttribute="leading" secondItem="cLc-iA-yq5" secondAttribute="leadingMargin" id="130-RD-MwU"/>
|
||||||
|
<constraint firstAttribute="height" constant="51" id="9Jw-2V-fgf"/>
|
||||||
|
<constraint firstItem="R77-TQ-lVT" firstAttribute="height" secondItem="cLc-iA-yq5" secondAttribute="height" id="FFf-Bp-LPT"/>
|
||||||
|
<constraint firstItem="R77-TQ-lVT" firstAttribute="centerY" secondItem="cLc-iA-yq5" secondAttribute="centerY" id="agB-KM-ba3"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="R77-TQ-lVT" secondAttribute="trailing" id="jB5-Ye-cJB"/>
|
||||||
|
</constraints>
|
||||||
|
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="14"/>
|
||||||
|
</view>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Glz-dw-2Eg">
|
||||||
|
<rect key="frame" x="0.0" y="76" width="343" height="33.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="If you used an app-specific password to install AltStore, please use that same password again." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a51-OQ-f3j">
|
||||||
|
<rect key="frame" x="14" y="0.0" width="315" height="33.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="14"/>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
||||||
|
<rect key="frame" x="0.0" y="228.5" width="343" height="51"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="pON-cO-VYR" secondAttribute="trailing" id="IPH-Og-2ch"/>
|
<constraint firstAttribute="height" constant="51" id="4BK-Un-5pl"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="pON-cO-VYR" secondAttribute="bottom" id="j7H-Ds-pJg"/>
|
|
||||||
<constraint firstItem="pON-cO-VYR" firstAttribute="leading" secondItem="S4n-4w-12m" secondAttribute="leadingMargin" id="uAc-4j-0pB"/>
|
|
||||||
<constraint firstItem="pON-cO-VYR" firstAttribute="top" secondItem="S4n-4w-12m" secondAttribute="top" id="xZe-CS-STZ"/>
|
|
||||||
</constraints>
|
</constraints>
|
||||||
</tableViewCellContentView>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
|
||||||
</tableViewCell>
|
<state key="normal" title="Sign in">
|
||||||
</cells>
|
<color key="titleColor" name="Pink"/>
|
||||||
</tableViewSection>
|
</state>
|
||||||
</sections>
|
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="nRn-xt-2XS" id="VWO-oe-ykv"/>
|
<action selector="authenticate" destination="yO1-iT-7NP" eventType="primaryActionTriggered" id="LER-a2-CbC"/>
|
||||||
<outlet property="delegate" destination="nRn-xt-2XS" id="CL1-Go-uiO"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</button>
|
||||||
<navigationItem key="navigationItem" title="Apple ID" id="viw-66-ZJ7">
|
</subviews>
|
||||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="KXh-qW-MIA">
|
</stackView>
|
||||||
<connections>
|
</subviews>
|
||||||
<action selector="cancel" destination="nRn-xt-2XS" id="l1X-bA-xsz"/>
|
</stackView>
|
||||||
</connections>
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
|
||||||
</barButtonItem>
|
<rect key="frame" x="16" y="498.5" width="343" height="96.5"/>
|
||||||
<barButtonItem key="rightBarButtonItem" title="Sign In" style="done" id="mkE-Q8-CxO">
|
|
||||||
<connections>
|
|
||||||
<action selector="authenticate" destination="nRn-xt-2XS" id="q60-9N-xVb"/>
|
|
||||||
</connections>
|
|
||||||
</barButtonItem>
|
|
||||||
</navigationItem>
|
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
|
||||||
<connections>
|
|
||||||
<outlet property="emailAddressTextField" destination="V6B-NM-wpL" id="N3F-eI-yhE"/>
|
|
||||||
<outlet property="passwordTextField" destination="z98-Sm-yDv" id="WDu-6c-oBa"/>
|
|
||||||
</connections>
|
|
||||||
</tableViewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="v2u-D2-stc" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="605.60000000000002" y="19.340329835082461"/>
|
|
||||||
</scene>
|
|
||||||
<!--Select Team-->
|
|
||||||
<scene sceneID="0Hb-4t-vQ3">
|
|
||||||
<objects>
|
|
||||||
<tableViewController storyboardIdentifier="selectTeamViewController" id="R11-Yh-Wb1" customClass="SelectTeamViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="g2d-7w-OVl">
|
|
||||||
<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"/>
|
|
||||||
<prototypes>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="iCV-rW-IhB" detailTextLabel="2hi-el-KvN" style="IBUITableViewCellStyleSubtitle" id="pPa-pY-koy">
|
|
||||||
<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="pPa-pY-koy" id="DjO-Wt-6j2">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="iCV-rW-IhB">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
|
||||||
<rect key="frame" x="16" y="5" width="33.5" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2hi-el-KvN">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="on2-62-waY">
|
||||||
<rect key="frame" x="16" y="25.5" width="33" height="14.5"/>
|
<rect key="frame" x="0.0" y="24.5" width="343" height="72"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<string key="text">Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.</string>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
</tableViewCellContentView>
|
</stackView>
|
||||||
</tableViewCell>
|
</subviews>
|
||||||
</prototypes>
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="leading" secondItem="WXx-hX-AXv" secondAttribute="leading" id="13j-ii-X7W"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="2wp-qG-f0Z" secondAttribute="bottom" id="Ggl-es-C4C"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="2wp-qG-f0Z" secondAttribute="trailing" id="nl1-88-5mM"/>
|
||||||
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="top" secondItem="WXx-hX-AXv" secondAttribute="top" id="wiH-lv-L9P"/>
|
||||||
|
</constraints>
|
||||||
|
</scrollView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" name="Primary"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
||||||
|
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
||||||
|
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||||
|
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
||||||
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||||
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
||||||
|
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
||||||
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
||||||
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
||||||
|
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
||||||
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||||
|
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
||||||
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
||||||
|
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||||
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
|
||||||
|
</view>
|
||||||
|
<toolbarItems/>
|
||||||
|
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="jCf-N4-xVD">
|
||||||
|
<barButtonItem key="leftBarButtonItem" title="Close" id="nDc-Zs-wnK">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="R11-Yh-Wb1" id="zkX-xW-GvZ"/>
|
<action selector="cancel:" destination="yO1-iT-7NP" id="xls-in-Pre"/>
|
||||||
<outlet property="delegate" destination="R11-Yh-Wb1" id="vP7-NA-Y0n"/>
|
|
||||||
</connections>
|
|
||||||
</tableView>
|
|
||||||
<navigationItem key="navigationItem" title="Select Team" id="ALr-U3-Ucl">
|
|
||||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="HUE-P1-xa1">
|
|
||||||
<connections>
|
|
||||||
<action selector="cancel" destination="R11-Yh-Wb1" id="Ckg-bQ-0nv"/>
|
|
||||||
</connections>
|
|
||||||
</barButtonItem>
|
|
||||||
<barButtonItem key="rightBarButtonItem" title="Next" style="done" id="7Ou-hQ-Cr3">
|
|
||||||
<connections>
|
|
||||||
<action selector="chooseTeam:" destination="R11-Yh-Wb1" id="nin-nM-lxU"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
</tableViewController>
|
<nil key="simulatedBottomBarMetrics"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="HxT-dJ-1Ry" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<connections>
|
||||||
|
<outlet property="appleIDBackgroundView" destination="gNe-dC-oI1" id="lab-WG-pyJ"/>
|
||||||
|
<outlet property="appleIDTextField" destination="DBu-vt-hlo" id="ZMK-9K-phY"/>
|
||||||
|
<outlet property="contentStackView" destination="YmX-7v-pxh" id="ZX5-Af-cEB"/>
|
||||||
|
<outlet property="passwordBackgroundView" destination="cLc-iA-yq5" id="2JD-nS-Gf7"/>
|
||||||
|
<outlet property="passwordTextField" destination="R77-TQ-lVT" id="cLQ-Wn-MsE"/>
|
||||||
|
<outlet property="scrollView" destination="WXx-hX-AXv" id="hOb-gl-0OP"/>
|
||||||
|
<outlet property="signInButton" destination="2N5-zd-fUj" id="ul1-bh-4l4"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="U7A-Cx-Bo9" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1354" y="20"/>
|
<point key="canvasLocation" x="605.60000000000002" y="736.28185907046486"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Replace Certificate-->
|
<!--How it works-->
|
||||||
<scene sceneID="fW2-QW-a2Z">
|
<scene sceneID="dMt-EA-SGy">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController storyboardIdentifier="replaceCertificateViewController" id="LAG-dk-a0f" customClass="ReplaceCertificateViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="enT-LI-CNI">
|
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="Otz-hn-WGS">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
|
||||||
<prototypes>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="luH-7x-QoO" style="IBUITableViewCellStyleDefault" id="i0O-XG-rRJ">
|
|
||||||
<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="i0O-XG-rRJ" id="GCT-3I-GCy">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="luH-7x-QoO">
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
|
||||||
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="544"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<subviews>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
|
||||||
<nil key="textColor"/>
|
<rect key="frame" x="16" y="35" width="343" height="95.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i9V-3h-B8f">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="59" id="ILg-0e-PW8"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="black" pointSize="80"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Q20-ml-9D0">
|
||||||
|
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Launch AltServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="XKD-XH-eB0">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Leave AltServer running in the background on your computer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="6HP-Xh-sAH">
|
||||||
|
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
</tableViewCellContentView>
|
</stackView>
|
||||||
</tableViewCell>
|
</subviews>
|
||||||
</prototypes>
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
|
||||||
|
<rect key="frame" x="16" y="161" width="343" height="95.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="59" id="HzE-AA-eE5"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="black" pointSize="80"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
||||||
|
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to WiFi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable iTunes WiFi Sync and connect to the same WiFi as AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
||||||
|
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
|
||||||
|
<rect key="frame" x="16" y="287.5" width="343" height="95.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="59" id="fRj-b4-VTe"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="black" pointSize="80"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
|
||||||
|
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Browse and download apps directly from AltStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="M7T-9j-uyt">
|
||||||
|
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
|
||||||
|
<rect key="frame" x="16" y="413.5" width="343" height="95.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="59" id="4Qg-s9-p7s"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="black" pointSize="80"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
|
||||||
|
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps Refresh Automatically" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nvb-Aq-sYa">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background when on same WiFi as AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
|
||||||
|
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="35" left="0.0" bottom="35" right="0.0"/>
|
||||||
|
</stackView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qZ9-AR-2zK">
|
||||||
|
<rect key="frame" x="16" y="608" width="343" height="51"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="51" id="LQz-qG-ZJK"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
|
||||||
|
<state key="normal" title="Got it">
|
||||||
|
<color key="titleColor" name="Pink"/>
|
||||||
|
</state>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="LAG-dk-a0f" id="kOS-KX-Duz"/>
|
<action selector="dismiss" destination="aFi-fb-W0B" eventType="primaryActionTriggered" id="sBq-zj-Mln"/>
|
||||||
<outlet property="delegate" destination="LAG-dk-a0f" id="plW-kJ-BmR"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</button>
|
||||||
<navigationItem key="navigationItem" title="Replace Certificate" id="BM2-Vg-AJk">
|
</subviews>
|
||||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="lPC-Dj-3Ik">
|
<color key="backgroundColor" name="Primary"/>
|
||||||
<connections>
|
<constraints>
|
||||||
<action selector="cancel" destination="LAG-dk-a0f" id="5C2-Hg-Les"/>
|
<constraint firstItem="qZ9-AR-2zK" firstAttribute="top" secondItem="bp6-55-IG2" secondAttribute="bottom" id="3yt-cr-swd"/>
|
||||||
</connections>
|
<constraint firstItem="bp6-55-IG2" firstAttribute="top" secondItem="Zek-aC-HOO" secondAttribute="top" id="42S-q2-YZn"/>
|
||||||
</barButtonItem>
|
<constraint firstAttribute="trailingMargin" secondItem="qZ9-AR-2zK" secondAttribute="trailing" id="8b4-iU-U7R"/>
|
||||||
<barButtonItem key="rightBarButtonItem" title="Next" style="done" id="ndJ-l9-HeM">
|
<constraint firstItem="bp6-55-IG2" firstAttribute="leading" secondItem="Zek-aC-HOO" secondAttribute="leading" id="K1R-1r-FP3"/>
|
||||||
<connections>
|
<constraint firstItem="Zek-aC-HOO" firstAttribute="trailing" secondItem="bp6-55-IG2" secondAttribute="trailing" id="aKV-sS-alh"/>
|
||||||
<action selector="replaceCertificate:" destination="LAG-dk-a0f" id="vl2-E6-qi4"/>
|
<constraint firstAttribute="bottomMargin" secondItem="qZ9-AR-2zK" secondAttribute="bottom" id="e8e-9l-Mkt"/>
|
||||||
</connections>
|
<constraint firstItem="qZ9-AR-2zK" firstAttribute="leading" secondItem="Otz-hn-WGS" secondAttribute="leadingMargin" id="t2b-3e-6ld"/>
|
||||||
</barButtonItem>
|
</constraints>
|
||||||
</navigationItem>
|
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" title="How it works" largeTitleDisplayMode="always" id="bCq-Jq-gf1"/>
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
</tableViewController>
|
<connections>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yxU-EG-3sE" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<outlet property="contentStackView" destination="bp6-55-IG2" id="k0Q-yS-Dxp"/>
|
||||||
|
<outlet property="dismissButton" destination="qZ9-AR-2zK" id="w5c-v6-TcC"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="3Q4-ya-qhc" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2135" y="19"/>
|
<point key="canvasLocation" x="1353" y="736"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<color key="tintColor" name="Purple"/>
|
<resources>
|
||||||
|
<namedColor name="Pink">
|
||||||
|
<color red="0.92549019607843142" green="0.25490196078431371" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="Primary">
|
||||||
|
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
</resources>
|
||||||
|
<color key="tintColor" name="Primary"/>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -2,48 +2,57 @@
|
|||||||
// AuthenticationViewController.swift
|
// AuthenticationViewController.swift
|
||||||
// AltStore
|
// AltStore
|
||||||
//
|
//
|
||||||
// Created by Riley Testut on 6/5/19.
|
// Created by Riley Testut on 9/5/19.
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
|
||||||
|
|
||||||
class AuthenticationViewController: UITableViewController
|
class AuthenticationViewController: UIViewController
|
||||||
{
|
{
|
||||||
var authenticationHandler: (((ALTAccount, String)?) -> Void)?
|
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 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()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
|
||||||
|
{
|
||||||
|
view.clipsToBounds = true
|
||||||
|
view.layer.cornerRadius = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
if UIScreen.main.isExtraCompactHeight
|
||||||
|
{
|
||||||
|
self.contentStackView.spacing = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews()
|
|
||||||
{
|
|
||||||
super.viewDidLayoutSubviews()
|
|
||||||
|
|
||||||
if !_didLayoutSubviews
|
|
||||||
{
|
|
||||||
self.emailAddressTextField.becomeFirstResponder()
|
|
||||||
}
|
|
||||||
|
|
||||||
_didLayoutSubviews = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool)
|
override func viewDidDisappear(_ animated: Bool)
|
||||||
{
|
{
|
||||||
super.viewDidDisappear(animated)
|
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()
|
if let _ = self.validate()
|
||||||
{
|
{
|
||||||
self.navigationItem.rightBarButtonItem?.isEnabled = true
|
self.signInButton.isEnabled = true
|
||||||
|
self.signInButton.alpha = 1.0
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.navigationItem.rightBarButtonItem?.isEnabled = false
|
self.signInButton.isEnabled = false
|
||||||
|
self.signInButton.alpha = 0.6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate() -> (String, String)?
|
func validate() -> (String, String)?
|
||||||
{
|
{
|
||||||
guard
|
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
|
let password = self.passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
return (emailAddress, password)
|
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
|
private extension AuthenticationViewController
|
||||||
@@ -94,10 +89,10 @@ private extension AuthenticationViewController
|
|||||||
{
|
{
|
||||||
guard let (emailAddress, password) = self.validate() else { return }
|
guard let (emailAddress, password) = self.validate() else { return }
|
||||||
|
|
||||||
self.emailAddressTextField.resignFirstResponder()
|
self.appleIDTextField.resignFirstResponder()
|
||||||
self.passwordTextField.resignFirstResponder()
|
self.passwordTextField.resignFirstResponder()
|
||||||
|
|
||||||
self.navigationItem.rightBarButtonItem?.isIndicatingActivity = true
|
self.signInButton.isIndicatingActivity = true
|
||||||
|
|
||||||
ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in
|
ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in
|
||||||
do
|
do
|
||||||
@@ -108,17 +103,23 @@ private extension AuthenticationViewController
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = RSTToastView(text: NSLocalizedString("Failed to Log In", comment: ""), detailText: error.localizedDescription)
|
let toastView = ToastView(text: NSLocalizedString("Failed to Log In", comment: ""), detailText: error.localizedDescription)
|
||||||
toastView.tintColor = .altPurple
|
toastView.textLabel.textColor = .altPink
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
toastView.detailTextLabel.textColor = .altPink
|
||||||
|
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)
|
self.authenticationHandler?(nil)
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ extension AuthenticationViewController: UITextFieldDelegate
|
|||||||
{
|
{
|
||||||
switch textField
|
switch textField
|
||||||
{
|
{
|
||||||
case self.emailAddressTextField: self.passwordTextField.becomeFirstResponder()
|
case self.appleIDTextField: self.passwordTextField.becomeFirstResponder()
|
||||||
case self.passwordTextField: self.authenticate()
|
case self.passwordTextField: self.authenticate()
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
@@ -140,12 +141,21 @@ extension AuthenticationViewController: UITextFieldDelegate
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
|
func textFieldDidBeginEditing(_ textField: UITextField)
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
guard UIScreen.main.isExtraCompactHeight else { return }
|
||||||
self.update()
|
|
||||||
|
// Position all the controls within visible frame.
|
||||||
|
var contentOffset = self.scrollView.contentOffset
|
||||||
|
contentOffset.y = 44
|
||||||
|
self.scrollView.setContentOffset(contentOffset, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
extension AuthenticationViewController
|
||||||
|
{
|
||||||
|
@objc func textFieldDidChangeText(_ notification: Notification)
|
||||||
|
{
|
||||||
|
self.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
AltStore/Authentication/InstructionsViewController.swift
Normal file
@@ -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?()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ALTCertificate>
|
|
||||||
{
|
|
||||||
let dataSource = RSTArrayTableViewDataSource<ALTCertificate>(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<ALTTeam>
|
|
||||||
{
|
|
||||||
let dataSource = RSTArrayTableViewDataSource<ALTTeam>(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,16 +32,17 @@
|
|||||||
<!--Tab Bar Controller-->
|
<!--Tab Bar Controller-->
|
||||||
<scene sceneID="yl2-sM-qoP">
|
<scene sceneID="yl2-sM-qoP">
|
||||||
<objects>
|
<objects>
|
||||||
<tabBarController id="49e-Tb-3d3" sceneMemberID="viewController">
|
<tabBarController id="49e-Tb-3d3" customClass="TabBarController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
|
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
|
||||||
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
|
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</tabBar>
|
</tabBar>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="faz-B4-Sub" kind="relationship" relationship="viewControllers" id="kCE-KJ-sWv"/>
|
<segue destination="kjR-gi-fgT" kind="relationship" relationship="viewControllers" id="eWy-uk-nwG"/>
|
||||||
<segue destination="3Ew-ox-i4n" kind="relationship" relationship="viewControllers" id="F8I-ea-yTZ"/>
|
<segue destination="faz-B4-Sub" kind="relationship" relationship="viewControllers" id="dXz-Tu-hW8"/>
|
||||||
<segue destination="MGm-Zy-ffn" kind="relationship" relationship="viewControllers" id="9m0-Rb-vjU"/>
|
<segue destination="3Ew-ox-i4n" kind="relationship" relationship="viewControllers" id="zii-dF-qEt"/>
|
||||||
|
<segue destination="p3d-dP-Swg" kind="relationship" relationship="viewControllers" id="4Nf-rL-P4c"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tabBarController>
|
</tabBarController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
</collectionViewController>
|
</collectionViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="cMN-i4-Bxk" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="cMN-i4-Bxk" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1517.5999999999999" y="-1013.3433283358322"/>
|
<point key="canvasLocation" x="1730" y="-17"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--App View Controller-->
|
<!--App View Controller-->
|
||||||
<scene sceneID="TgT-LO-3Er">
|
<scene sceneID="TgT-LO-3Er">
|
||||||
@@ -133,7 +134,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="LZw-eU-5SO" userLabel="App Info">
|
<stackView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="LZw-eU-5SO" userLabel="App Info">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="273" height="93"/>
|
<rect key="frame" x="0.0" y="0.0" width="320" height="93"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Ey-6S-HJx" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Ey-6S-HJx" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
|
||||||
@@ -143,17 +144,25 @@
|
|||||||
<constraint firstAttribute="width" secondItem="3Ey-6S-HJx" secondAttribute="height" multiplier="1:1" id="GCk-a1-dDk"/>
|
<constraint firstAttribute="width" secondItem="3Ey-6S-HJx" secondAttribute="height" multiplier="1:1" id="GCk-a1-dDk"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="bR7-SO-m8f">
|
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="bR7-SO-m8f">
|
||||||
<rect key="frame" x="90" y="26.5" width="88" height="40.5"/>
|
<rect key="frame" x="90" y="26.5" width="135" height="40.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dNE-IO-y3o">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="9z7-I4-q6g">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="135" height="21.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="dNE-IO-y3o">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="88" height="21.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="88" height="21.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="2XC-Fe-yG4">
|
||||||
|
<rect key="frame" x="94" y="0.0" width="41" height="21.5"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NKT-el-rRF">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NKT-el-rRF">
|
||||||
<rect key="frame" x="0.0" y="23.5" width="88" height="17"/>
|
<rect key="frame" x="0.0" y="23.5" width="66" height="17"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -161,10 +170,10 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mgB-Gs-bik" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mgB-Gs-bik" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="189" y="31" width="72" height="31"/>
|
<rect key="frame" x="236" y="31" width="72" height="31"/>
|
||||||
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" constant="72" id="j44-T1-0dc"/>
|
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="j44-T1-0dc"/>
|
||||||
<constraint firstAttribute="height" constant="31" id="qY2-Ng-KJy"/>
|
<constraint firstAttribute="height" constant="31" id="qY2-Ng-KJy"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||||
@@ -230,7 +239,7 @@
|
|||||||
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="31" id="S2r-fI-PQB"/>
|
<constraint firstAttribute="height" constant="31" id="S2r-fI-PQB"/>
|
||||||
<constraint firstAttribute="width" constant="72" id="Xtq-UG-h3b"/>
|
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="Xtq-UG-h3b"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||||
<state key="normal" title="FREE"/>
|
<state key="normal" title="FREE"/>
|
||||||
@@ -246,6 +255,7 @@
|
|||||||
<outlet property="backButtonContainerView" destination="tUK-0J-07U" id="POZ-dP-f12"/>
|
<outlet property="backButtonContainerView" destination="tUK-0J-07U" id="POZ-dP-f12"/>
|
||||||
<outlet property="backgroundAppIconImageView" destination="CUB-SN-zdM" id="dFx-py-yMm"/>
|
<outlet property="backgroundAppIconImageView" destination="CUB-SN-zdM" id="dFx-py-yMm"/>
|
||||||
<outlet property="backgroundBlurView" destination="8Tg-wk-r0u" id="B8c-ng-nI5"/>
|
<outlet property="backgroundBlurView" destination="8Tg-wk-r0u" id="B8c-ng-nI5"/>
|
||||||
|
<outlet property="betaBadgeView" destination="2XC-Fe-yG4" id="FCf-t9-Aab"/>
|
||||||
<outlet property="contentView" destination="Qlg-m3-lXg" id="JhH-hh-vBN"/>
|
<outlet property="contentView" destination="Qlg-m3-lXg" id="JhH-hh-vBN"/>
|
||||||
<outlet property="developerLabel" destination="NKT-el-rRF" id="GUc-jy-kvv"/>
|
<outlet property="developerLabel" destination="NKT-el-rRF" id="GUc-jy-kvv"/>
|
||||||
<outlet property="downloadButton" destination="mgB-Gs-bik" id="x95-gu-NBy"/>
|
<outlet property="downloadButton" destination="mgB-Gs-bik" id="x95-gu-NBy"/>
|
||||||
@@ -261,7 +271,7 @@
|
|||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="C9o-C3-sMK" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="C9o-C3-sMK" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2312.8000000000002" y="-1013.3433283358322"/>
|
<point key="canvasLocation" x="2526" y="-17"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--App-->
|
<!--App-->
|
||||||
<scene sceneID="CgX-7h-sRI">
|
<scene sceneID="CgX-7h-sRI">
|
||||||
@@ -538,7 +548,7 @@ World</string>
|
|||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dhh-ZN-LoG" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dhh-ZN-LoG" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="3088.8000000000002" y="-1014.2428785607198"/>
|
<point key="canvasLocation" x="3302" y="-18"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Permission Popover View Controller-->
|
<!--Permission Popover View Controller-->
|
||||||
<scene sceneID="24j-EJ-G4e">
|
<scene sceneID="24j-EJ-G4e">
|
||||||
@@ -585,241 +595,43 @@ World</string>
|
|||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7Tu-x9-xBb" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="7Tu-x9-xBb" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="3908" y="-1484"/>
|
<point key="canvasLocation" x="4257" y="-412"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Settings-->
|
<!--Settings-->
|
||||||
<scene sceneID="GaO-Ug-BdZ">
|
<scene sceneID="KlD-j0-ROn">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController id="MGm-Zy-ffn" sceneMemberID="viewController">
|
<viewControllerPlaceholder storyboardName="Settings" id="p3d-dP-Swg" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="Settings" image="Settings" id="8Ic-ki-txH"/>
|
<tabBarItem key="tabBarItem" title="Settings" image="Settings" id="OZm-Le-7oJ"/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="rzJ-pZ-611">
|
</viewControllerPlaceholder>
|
||||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="HgE-PD-dC2" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
<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>
|
</objects>
|
||||||
<point key="canvasLocation" x="750" y="515"/>
|
<point key="canvasLocation" x="962" y="1197"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Settings-->
|
<!--News-->
|
||||||
<scene sceneID="Xdi-2V-rwM">
|
<scene sceneID="bqw-wB-hyB">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="VBC-qD-V1a" customClass="SettingsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController id="3sa-FZ-PTg" customClass="NewsViewController" 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">
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="736-lq-Aef">
|
||||||
<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 headerTitle="Debug" id="K7R-6x-gHl">
|
|
||||||
<cells>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="nQj-qq-PmI" style="IBUITableViewCellStyleDefault" id="8M3-mu-gRd">
|
|
||||||
<rect key="frame" x="0.0" y="299.5" width="375" height="44"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="8M3-mu-gRd" id="6TQ-nF-Rkl">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Background Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="nQj-qq-PmI">
|
|
||||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
</tableViewCellContentView>
|
|
||||||
<connections>
|
|
||||||
<segue destination="RUB-gO-oQ6" kind="show" id="tkT-F7-PA4"/>
|
|
||||||
</connections>
|
|
||||||
</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="Settings" 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="515"/>
|
|
||||||
</scene>
|
|
||||||
<!--Refresh Attempts-->
|
|
||||||
<scene sceneID="tCt-AY-k3Z">
|
|
||||||
<objects>
|
|
||||||
<tableViewController id="RUB-gO-oQ6" customClass="RefreshAttemptsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="g2y-h0-0xu">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<prototypes>
|
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="40" minimumInteritemSpacing="40" id="63d-78-Y24">
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="bBs-eV-Rlj" customClass="RefreshAttemptTableViewCell">
|
<size key="itemSize" width="335" height="300"/>
|
||||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="bBs-eV-Rlj" id="YfI-8z-wCv">
|
<inset key="sectionInset" minX="20" minY="40" maxX="20" maxY="13"/>
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
</collectionViewFlowLayout>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<cells/>
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="SFD-t6-Qk3">
|
|
||||||
<rect key="frame" x="16" y="11" width="343" height="22"/>
|
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="gQk-PG-cjg">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="17"/>
|
|
||||||
<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="CZj-FM-9Qv">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="67.5" height="17"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</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="0Md-jd-XXe">
|
|
||||||
<rect key="frame" x="312.5" y="0.0" width="30.5" height="17"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Could not connect to AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pwo-OM-Gm3">
|
|
||||||
<rect key="frame" x="0.0" y="21" width="343" height="1"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="SFD-t6-Qk3" firstAttribute="leading" secondItem="YfI-8z-wCv" secondAttribute="leadingMargin" id="ehs-Sf-kyZ"/>
|
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="SFD-t6-Qk3" secondAttribute="trailing" id="g4i-l8-aln"/>
|
|
||||||
<constraint firstAttribute="bottomMargin" secondItem="SFD-t6-Qk3" secondAttribute="bottom" id="h38-79-xRT"/>
|
|
||||||
<constraint firstItem="SFD-t6-Qk3" firstAttribute="top" secondItem="YfI-8z-wCv" secondAttribute="topMargin" id="mCE-eA-UKd"/>
|
|
||||||
</constraints>
|
|
||||||
</tableViewCellContentView>
|
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dateLabel" destination="0Md-jd-XXe" id="znM-W1-cZi"/>
|
<outlet property="dataSource" destination="3sa-FZ-PTg" id="80N-Sr-Foq"/>
|
||||||
<outlet property="errorDescriptionLabel" destination="Pwo-OM-Gm3" id="3zi-gX-I5t"/>
|
<outlet property="delegate" destination="3sa-FZ-PTg" id="9fB-sR-8Xt"/>
|
||||||
<outlet property="successLabel" destination="CZj-FM-9Qv" id="vtL-iu-aHR"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</collectionView>
|
||||||
</prototypes>
|
<navigationItem key="navigationItem" title="News" id="ZxL-Ws-lJO"/>
|
||||||
<connections>
|
</collectionViewController>
|
||||||
<outlet property="dataSource" destination="RUB-gO-oQ6" id="vqw-LK-wuY"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="YS7-2X-joz" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
<outlet property="delegate" destination="RUB-gO-oQ6" id="9uY-h3-7qR"/>
|
|
||||||
</connections>
|
|
||||||
</tableView>
|
|
||||||
<navigationItem key="navigationItem" title="Refresh Attempts" largeTitleDisplayMode="never" id="jO4-kG-Alq">
|
|
||||||
<barButtonItem key="rightBarButtonItem" title="Sign Out" id="9Pj-GZ-Rra">
|
|
||||||
<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="m55-18-kgT"/>
|
|
||||||
</connections>
|
|
||||||
</barButtonItem>
|
|
||||||
</navigationItem>
|
|
||||||
</tableViewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Wk8-xA-fxp" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2313" y="515"/>
|
<point key="canvasLocation" x="1730" y="-752"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Browse-->
|
<!--Browse-->
|
||||||
<scene sceneID="VHa-uP-bFU">
|
<scene sceneID="VHa-uP-bFU">
|
||||||
@@ -830,7 +642,7 @@ World</string>
|
|||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" name="Green"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<nil name="viewControllers"/>
|
<nil name="viewControllers"/>
|
||||||
<connections>
|
<connections>
|
||||||
@@ -839,14 +651,14 @@ World</string>
|
|||||||
</navigationController>
|
</navigationController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="OkH-49-O0J" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="OkH-49-O0J" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="750" y="-1013"/>
|
<point key="canvasLocation" x="962" y="-17"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--My Apps-->
|
<!--My Apps-->
|
||||||
<scene sceneID="nhh-BJ-XiT">
|
<scene sceneID="nhh-BJ-XiT">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3Ew-ox-i4n" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3Ew-ox-i4n" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="My Apps" image="MyApps" id="4gT-9u-k7y">
|
<tabBarItem key="tabBarItem" title="My Apps" image="MyApps" id="4gT-9u-k7y">
|
||||||
<color key="badgeColor" name="Green"/>
|
<color key="badgeColor" name="Primary"/>
|
||||||
</tabBarItem>
|
</tabBarItem>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||||
@@ -860,7 +672,7 @@ World</string>
|
|||||||
</navigationController>
|
</navigationController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="9Nj-f6-CAf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="9Nj-f6-CAf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="749.60000000000002" y="-279.31034482758622"/>
|
<point key="canvasLocation" x="962" y="717"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--My Apps-->
|
<!--My Apps-->
|
||||||
<scene sceneID="EC8-Sf-AF9">
|
<scene sceneID="EC8-Sf-AF9">
|
||||||
@@ -894,17 +706,25 @@ World</string>
|
|||||||
<constraint firstAttribute="width" secondItem="H12-ip-Bbl" secondAttribute="height" multiplier="1:1" id="ZIR-f8-Jc4"/>
|
<constraint firstAttribute="width" secondItem="H12-ip-Bbl" secondAttribute="height" multiplier="1:1" id="ZIR-f8-Jc4"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="7iy-Zp-LEj">
|
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="7iy-Zp-LEj">
|
||||||
<rect key="frame" x="71" y="12" width="203" height="36"/>
|
<rect key="frame" x="71" y="12" width="203" height="36"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Short" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Nhl-6I-9gW">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="MRz-3W-aTM">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="203" height="18"/>
|
<rect key="frame" x="0.0" y="0.0" width="85" height="18"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="Short" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Nhl-6I-9gW">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="38" height="18"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="mtL-iA-JnD">
|
||||||
|
<rect key="frame" x="44" y="0.0" width="41" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hp4-uP-55T">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hp4-uP-55T">
|
||||||
<rect key="frame" x="0.0" y="20" width="203" height="16"/>
|
<rect key="frame" x="0.0" y="20" width="62" height="16"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -936,6 +756,7 @@ World</string>
|
|||||||
</constraints>
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="appIconImageView" destination="H12-ip-Bbl" id="61F-4i-4Q3"/>
|
<outlet property="appIconImageView" destination="H12-ip-Bbl" id="61F-4i-4Q3"/>
|
||||||
|
<outlet property="betaBadgeView" destination="mtL-iA-JnD" id="v8W-bc-EB7"/>
|
||||||
<outlet property="developerLabel" destination="Hp4-uP-55T" id="Cqx-3O-knq"/>
|
<outlet property="developerLabel" destination="Hp4-uP-55T" id="Cqx-3O-knq"/>
|
||||||
<outlet property="nameLabel" destination="Nhl-6I-9gW" id="lzd-pp-PEQ"/>
|
<outlet property="nameLabel" destination="Nhl-6I-9gW" id="lzd-pp-PEQ"/>
|
||||||
<outlet property="refreshButton" destination="dh4-fU-DFx" id="KWX-9y-2w8"/>
|
<outlet property="refreshButton" destination="dh4-fU-DFx" id="KWX-9y-2w8"/>
|
||||||
@@ -955,7 +776,7 @@ World</string>
|
|||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No Updates Available" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="z04-yg-x1t">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No Updates Available" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="z04-yg-x1t">
|
||||||
<rect key="frame" x="104" y="20" width="167" height="20.5"/>
|
<rect key="frame" x="104" y="20" width="167" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
|
||||||
<color key="textColor" name="Green"/>
|
<color key="textColor" name="Primary"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -1007,23 +828,48 @@ World</string>
|
|||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
|
<connections>
|
||||||
|
<segue destination="0V6-N4-hTO" kind="show" identifier="showUpdate" id="dzt-2e-VM9"/>
|
||||||
|
</connections>
|
||||||
</collectionViewController>
|
</collectionViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="kiO-UO-esV" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="kiO-UO-esV" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1517.5999999999999" y="-279.31034482758622"/>
|
<point key="canvasLocation" x="1730" y="717"/>
|
||||||
|
</scene>
|
||||||
|
<!--News-->
|
||||||
|
<scene sceneID="BV8-6J-nIv">
|
||||||
|
<objects>
|
||||||
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="kjR-gi-fgT" sceneMemberID="viewController">
|
||||||
|
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
||||||
|
<toolbarItems/>
|
||||||
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||||
|
</navigationBar>
|
||||||
|
<nil name="viewControllers"/>
|
||||||
|
<connections>
|
||||||
|
<segue destination="3sa-FZ-PTg" kind="relationship" relationship="rootViewController" id="Dcj-St-vt5"/>
|
||||||
|
</connections>
|
||||||
|
</navigationController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iUr-Sd-9ER" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="962" y="-752"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="Back" width="18" height="18"/>
|
<image name="Back" width="18" height="18"/>
|
||||||
|
<image name="BetaBadge" width="41" height="17"/>
|
||||||
<image name="Browse" width="19.5" height="20.5"/>
|
<image name="Browse" width="19.5" height="20.5"/>
|
||||||
<image name="MyApps" width="28" height="24"/>
|
<image name="MyApps" width="28" height="24"/>
|
||||||
|
<image name="News" width="17" height="21"/>
|
||||||
<image name="Settings" width="21" height="21"/>
|
<image name="Settings" width="21" height="21"/>
|
||||||
<namedColor name="Green">
|
<namedColor name="Primary">
|
||||||
<color red="0.22352941176470589" green="0.49411764705882355" blue="0.396078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
<inferredMetricsTieBreakers>
|
<inferredMetricsTieBreakers>
|
||||||
<segue reference="cnd-KK-o60"/>
|
<segue reference="dzt-2e-VM9"/>
|
||||||
</inferredMetricsTieBreakers>
|
</inferredMetricsTieBreakers>
|
||||||
<color key="tintColor" name="Green"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import Nuke
|
||||||
|
|
||||||
@objc class BrowseCollectionViewCell: UICollectionViewCell
|
@objc class BrowseCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
var imageNames: [String] = [] {
|
var imageURLs: [URL] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
self.dataSource.items = self.imageNames.map { $0 as NSString }
|
self.dataSource.items = self.imageURLs as [NSURL]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
@@ -26,6 +28,7 @@ import Roxas
|
|||||||
@IBOutlet var subtitleLabel: UILabel!
|
@IBOutlet var subtitleLabel: UILabel!
|
||||||
|
|
||||||
@IBOutlet var screenshotsCollectionView: UICollectionView!
|
@IBOutlet var screenshotsCollectionView: UICollectionView!
|
||||||
|
@IBOutlet var betaBadgeView: UIImageView!
|
||||||
|
|
||||||
@IBOutlet private var screenshotsContentView: UIView!
|
@IBOutlet private var screenshotsContentView: UIView!
|
||||||
|
|
||||||
@@ -56,24 +59,39 @@ import Roxas
|
|||||||
|
|
||||||
private extension BrowseCollectionViewCell
|
private extension BrowseCollectionViewCell
|
||||||
{
|
{
|
||||||
func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSString, UIImage>
|
func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>
|
||||||
{
|
{
|
||||||
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSString, UIImage>(items: [])
|
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>(items: [])
|
||||||
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
||||||
let cell = cell as! ScreenshotCollectionViewCell
|
let cell = cell as! ScreenshotCollectionViewCell
|
||||||
cell.imageView.image = nil
|
cell.imageView.image = nil
|
||||||
cell.imageView.isIndicatingActivity = true
|
cell.imageView.isIndicatingActivity = true
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (imageName, indexPath, completion) in
|
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
||||||
return BlockOperation {
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
let image = UIImage(named: imageName as String)
|
ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in
|
||||||
completion(image, nil)
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
|
if let image = response?.image
|
||||||
|
{
|
||||||
|
completionHandler(image, nil)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completionHandler(nil, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||||
let cell = cell as! ScreenshotCollectionViewCell
|
let cell = cell as! ScreenshotCollectionViewCell
|
||||||
cell.imageView.isIndicatingActivity = false
|
cell.imageView.isIndicatingActivity = false
|
||||||
cell.imageView.image = image
|
cell.imageView.image = image
|
||||||
|
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
print("Error loading image:", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
|
|||||||
@@ -29,17 +29,25 @@
|
|||||||
<constraint firstAttribute="height" constant="65" id="ufl-3d-nkT"/>
|
<constraint firstAttribute="height" constant="65" id="ufl-3d-nkT"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="zkp-KH-OyV">
|
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="zkp-KH-OyV">
|
||||||
<rect key="frame" x="76" y="21" width="176" height="37"/>
|
<rect key="frame" x="76" y="21" width="176" height="37"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xni-8I-ewW">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Ykl-yo-ncv">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="176" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="127.5" height="20.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="xni-8I-ewW">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="80.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-I2-QOB">
|
||||||
|
<rect key="frame" x="86.5" y="0.0" width="41" height="20.5"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B5S-HI-tWJ">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B5S-HI-tWJ">
|
||||||
<rect key="frame" x="0.0" y="22.5" width="176" height="14.5"/>
|
<rect key="frame" x="0.0" y="22.5" width="57.5" height="14.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -50,7 +58,7 @@
|
|||||||
<rect key="frame" x="263" y="24" width="72" height="31"/>
|
<rect key="frame" x="263" y="24" width="72" height="31"/>
|
||||||
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" constant="72" id="X7D-DN-WnD"/>
|
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="X7D-DN-WnD"/>
|
||||||
<constraint firstAttribute="height" constant="31" id="svo-Sc-wpR"/>
|
<constraint firstAttribute="height" constant="31" id="svo-Sc-wpR"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||||
@@ -108,6 +116,7 @@
|
|||||||
<connections>
|
<connections>
|
||||||
<outlet property="actionButton" destination="DeC-Y2-fvR" id="VDk-4D-STy"/>
|
<outlet property="actionButton" destination="DeC-Y2-fvR" id="VDk-4D-STy"/>
|
||||||
<outlet property="appIconImageView" destination="F2j-pX-09A" id="COe-74-adn"/>
|
<outlet property="appIconImageView" destination="F2j-pX-09A" id="COe-74-adn"/>
|
||||||
|
<outlet property="betaBadgeView" destination="5gN-I2-QOB" id="hu7-Ax-Wbc"/>
|
||||||
<outlet property="developerLabel" destination="B5S-HI-tWJ" id="QGh-1g-fFv"/>
|
<outlet property="developerLabel" destination="B5S-HI-tWJ" id="QGh-1g-fFv"/>
|
||||||
<outlet property="nameLabel" destination="xni-8I-ewW" id="V56-ZT-vFa"/>
|
<outlet property="nameLabel" destination="xni-8I-ewW" id="V56-ZT-vFa"/>
|
||||||
<outlet property="screenshotsCollectionView" destination="RFs-qp-Ca4" id="xfi-AN-l17"/>
|
<outlet property="screenshotsCollectionView" destination="RFs-qp-Ca4" id="xfi-AN-l17"/>
|
||||||
@@ -116,4 +125,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</objects>
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="BetaBadge" width="41" height="17"/>
|
||||||
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -10,12 +10,21 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
|
import Nuke
|
||||||
|
|
||||||
class BrowseViewController: UICollectionViewController
|
class BrowseViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
|
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
||||||
|
|
||||||
private let prototypeCell = BrowseCollectionViewCell.instantiate(with: BrowseCollectionViewCell.nib!)!
|
private let prototypeCell = BrowseCollectionViewCell.instantiate(with: BrowseCollectionViewCell.nib!)!
|
||||||
|
|
||||||
|
private var loadingState: LoadingState = .loading {
|
||||||
|
didSet {
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var cachedItemSizes = [String: CGSize]()
|
private var cachedItemSizes = [String: CGSize]()
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
@@ -30,6 +39,8 @@ class BrowseViewController: UICollectionViewController
|
|||||||
self.collectionView.prefetchDataSource = self.dataSource
|
self.collectionView.prefetchDataSource = self.dataSource
|
||||||
|
|
||||||
self.registerForPreviewing(with: self, sourceView: self.collectionView)
|
self.registerForPreviewing(with: self, sourceView: self.collectionView)
|
||||||
|
|
||||||
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool)
|
override func viewWillAppear(_ animated: Bool)
|
||||||
@@ -37,18 +48,7 @@ class BrowseViewController: UICollectionViewController
|
|||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
self.fetchSource()
|
self.fetchSource()
|
||||||
}
|
self.updateDataSource()
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
|
||||||
{
|
|
||||||
guard segue.identifier == "showApp" else { return }
|
|
||||||
|
|
||||||
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return }
|
|
||||||
|
|
||||||
let app = self.dataSource.item(at: indexPath)
|
|
||||||
|
|
||||||
let appViewController = segue.destination as! AppViewController
|
|
||||||
appViewController.app = app
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +75,10 @@ private extension BrowseViewController
|
|||||||
cell.nameLabel.text = app.name
|
cell.nameLabel.text = app.name
|
||||||
cell.developerLabel.text = app.developerName
|
cell.developerLabel.text = app.developerName
|
||||||
cell.subtitleLabel.text = app.subtitle
|
cell.subtitleLabel.text = app.subtitle
|
||||||
cell.imageNames = Array(app.screenshotNames.prefix(3))
|
cell.imageURLs = Array(app.screenshotURLs.prefix(2))
|
||||||
cell.appIconImageView.image = UIImage(named: app.iconName)
|
cell.appIconImageView.image = nil
|
||||||
|
cell.appIconImageView.isIndicatingActivity = true
|
||||||
|
cell.betaBadgeView.isHidden = !app.isBeta
|
||||||
|
|
||||||
cell.actionButton.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
cell.actionButton.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
cell.actionButton.activityIndicatorView.style = .white
|
cell.actionButton.activityIndicatorView.style = .white
|
||||||
@@ -85,7 +87,7 @@ private extension BrowseViewController
|
|||||||
// Otherwise, cell reuse can mess up some cached values.
|
// Otherwise, cell reuse can mess up some cached values.
|
||||||
cell.actionButton.isIndicatingActivity = false
|
cell.actionButton.isIndicatingActivity = false
|
||||||
|
|
||||||
let tintColor = app.tintColor ?? .altGreen
|
let tintColor = app.tintColor ?? .altPrimary
|
||||||
cell.tintColor = tintColor
|
cell.tintColor = tintColor
|
||||||
|
|
||||||
if app.installedApp == nil
|
if app.installedApp == nil
|
||||||
@@ -95,37 +97,129 @@ private extension BrowseViewController
|
|||||||
let progress = AppManager.shared.installationProgress(for: app)
|
let progress = AppManager.shared.installationProgress(for: app)
|
||||||
cell.actionButton.progress = progress
|
cell.actionButton.progress = progress
|
||||||
cell.actionButton.isInverted = false
|
cell.actionButton.isInverted = false
|
||||||
|
|
||||||
|
if Date() < app.versionDate
|
||||||
|
{
|
||||||
|
cell.actionButton.countdownDate = app.versionDate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cell.actionButton.countdownDate = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cell.actionButton.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
cell.actionButton.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
||||||
cell.actionButton.progress = nil
|
cell.actionButton.progress = nil
|
||||||
cell.actionButton.isInverted = true
|
cell.actionButton.isInverted = true
|
||||||
|
cell.actionButton.countdownDate = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dataSource.prefetchHandler = { (storeApp, indexPath, completionHandler) -> Foundation.Operation? in
|
||||||
|
let iconURL = storeApp.iconURL
|
||||||
|
|
||||||
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
|
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in
|
||||||
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
|
if let image = response?.image
|
||||||
|
{
|
||||||
|
completionHandler(image, nil)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completionHandler(nil, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||||
|
let cell = cell as! BrowseCollectionViewCell
|
||||||
|
cell.appIconImageView.isIndicatingActivity = false
|
||||||
|
cell.appIconImageView.image = image
|
||||||
|
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
print("Error loading image:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.placeholderView = self.placeholderView
|
||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDataSource()
|
||||||
|
{
|
||||||
|
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||||
|
{
|
||||||
|
self.dataSource.predicate = nil
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.dataSource.predicate = NSPredicate(format: "%K == NO", #keyPath(StoreApp.isBeta))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fetchSource()
|
func fetchSource()
|
||||||
{
|
{
|
||||||
|
self.loadingState = .loading
|
||||||
|
|
||||||
AppManager.shared.fetchSource() { (result) in
|
AppManager.shared.fetchSource() { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let source = try result.get()
|
let source = try result.get()
|
||||||
try source.managedObjectContext?.save()
|
try source.managedObjectContext?.save()
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.loadingState = .finished(.success(()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
if self.dataSource.itemCount > 0
|
||||||
|
{
|
||||||
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.loadingState = .finished(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update()
|
||||||
|
{
|
||||||
|
switch self.loadingState
|
||||||
|
{
|
||||||
|
case .loading:
|
||||||
|
self.placeholderView.textLabel.isHidden = true
|
||||||
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
|
self.placeholderView.detailTextLabel.text = NSLocalizedString("Loading...", comment: "")
|
||||||
|
|
||||||
|
self.placeholderView.activityIndicatorView.startAnimating()
|
||||||
|
|
||||||
|
case .finished(.failure(let error)):
|
||||||
|
self.placeholderView.textLabel.isHidden = false
|
||||||
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
|
self.placeholderView.textLabel.text = NSLocalizedString("Unable to Fetch Apps", comment: "")
|
||||||
|
self.placeholderView.detailTextLabel.text = error.localizedDescription
|
||||||
|
|
||||||
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
|
||||||
|
case .finished(.success):
|
||||||
|
self.placeholderView.textLabel.isHidden = true
|
||||||
|
self.placeholderView.detailTextLabel.isHidden = true
|
||||||
|
|
||||||
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extension BrowseViewController
|
private extension BrowseViewController
|
||||||
{
|
{
|
||||||
@IBAction func performAppAction(_ sender: PillButton)
|
@IBAction func performAppAction(_ sender: PillButton)
|
||||||
|
|||||||
40
AltStore/Components/AppBannerView.swift
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// AppBannerView.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/29/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
class AppBannerView: RSTNibView
|
||||||
|
{
|
||||||
|
@IBOutlet var titleLabel: UILabel!
|
||||||
|
@IBOutlet var subtitleLabel: UILabel!
|
||||||
|
@IBOutlet var iconImageView: AppIconImageView!
|
||||||
|
@IBOutlet var button: PillButton!
|
||||||
|
@IBOutlet var betaBadgeView: UIView!
|
||||||
|
|
||||||
|
override func tintColorDidChange()
|
||||||
|
{
|
||||||
|
super.tintColorDidChange()
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AppBannerView
|
||||||
|
{
|
||||||
|
func update()
|
||||||
|
{
|
||||||
|
self.clipsToBounds = true
|
||||||
|
self.layer.cornerRadius = 22
|
||||||
|
|
||||||
|
self.subtitleLabel.textColor = self.tintColor
|
||||||
|
self.button.tintColor = self.tintColor
|
||||||
|
|
||||||
|
self.backgroundColor = self.tintColor.withAlphaComponent(0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
87
AltStore/Components/AppBannerView.xib
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait">
|
||||||
|
<adaptation id="fullscreen"/>
|
||||||
|
</device>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="betaBadgeView" destination="qQl-Ez-zC5" id="6O1-Cx-7qz"/>
|
||||||
|
<outlet property="button" destination="tVx-3G-dcu" id="joa-AH-syX"/>
|
||||||
|
<outlet property="iconImageView" destination="avS-dx-4iy" id="TQs-Ej-gin"/>
|
||||||
|
<outlet property="subtitleLabel" destination="oN5-vu-Dnw" id="gA4-iJ-Tix"/>
|
||||||
|
<outlet property="titleLabel" destination="mFe-zJ-eva" id="2OH-f8-cid"/>
|
||||||
|
</connections>
|
||||||
|
</placeholder>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<view opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="FxI-Fh-ll5">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="d1T-UD-gWG" userLabel="App Info">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="avS-dx-4iy" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="14" y="14" width="60" height="60"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="60" id="6lU-H8-nEw"/>
|
||||||
|
<constraint firstAttribute="width" secondItem="avS-dx-4iy" secondAttribute="height" multiplier="1:1" id="AYT-3g-wcV"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="caL-vN-Svn">
|
||||||
|
<rect key="frame" x="85" y="24" width="195" height="40.5"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="1Es-pv-zwd">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="135" height="21.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="mFe-zJ-eva">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="88" height="21.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="qQl-Ez-zC5">
|
||||||
|
<rect key="frame" x="94" y="0.0" width="41" height="21.5"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="oN5-vu-Dnw">
|
||||||
|
<rect key="frame" x="0.0" y="23.5" width="66" height="17"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tVx-3G-dcu" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="291" y="28.5" width="72" height="31"/>
|
||||||
|
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="31" id="Zwh-yQ-GTu"/>
|
||||||
|
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="eGc-Dk-QbL"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||||
|
<state key="normal" title="FREE"/>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="14" left="14" bottom="14" right="12"/>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="d1T-UD-gWG" secondAttribute="bottom" id="B9e-Mf-cy5"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="d1T-UD-gWG" secondAttribute="trailing" id="HcT-2k-z0H"/>
|
||||||
|
<constraint firstItem="d1T-UD-gWG" firstAttribute="leading" secondItem="FxI-Fh-ll5" secondAttribute="leading" id="PIM-W5-dkh"/>
|
||||||
|
<constraint firstItem="d1T-UD-gWG" firstAttribute="top" secondItem="FxI-Fh-ll5" secondAttribute="top" id="RHn-ZK-jgl"/>
|
||||||
|
</constraints>
|
||||||
|
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||||
|
</view>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="BetaBadge" width="41" height="17"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
@@ -58,30 +58,33 @@ class CollapsingTextView: UITextView
|
|||||||
let buttonFont = UIFont.systemFont(ofSize: font.pointSize, weight: .medium)
|
let buttonFont = UIFont.systemFont(ofSize: font.pointSize, weight: .medium)
|
||||||
self.moreButton.titleLabel?.font = buttonFont
|
self.moreButton.titleLabel?.font = buttonFont
|
||||||
|
|
||||||
|
let buttonY = (font.lineHeight + self.lineSpacing) * CGFloat(self.maximumNumberOfLines - 1)
|
||||||
let size = self.moreButton.sizeThatFits(CGSize(width: 1000, height: 1000))
|
let size = self.moreButton.sizeThatFits(CGSize(width: 1000, height: 1000))
|
||||||
|
|
||||||
let moreButtonFrame = CGRect(x: self.bounds.width - self.moreButton.bounds.width,
|
let moreButtonFrame = CGRect(x: self.bounds.width - self.moreButton.bounds.width,
|
||||||
y: self.bounds.height - self.moreButton.bounds.height - self.lineSpacing,
|
y: buttonY,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: font.lineHeight)
|
height: font.lineHeight)
|
||||||
self.moreButton.frame = moreButtonFrame
|
self.moreButton.frame = moreButtonFrame
|
||||||
|
|
||||||
if self.isCollapsed
|
if self.isCollapsed
|
||||||
|
{
|
||||||
|
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||||
|
|
||||||
|
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
|
||||||
|
if self.intrinsicContentSize.height > maximumCollapsedHeight
|
||||||
{
|
{
|
||||||
var exclusionFrame = moreButtonFrame
|
var exclusionFrame = moreButtonFrame
|
||||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||||
exclusionFrame.size.width = self.bounds.width // Extra wide to make sure it wraps to next line.
|
exclusionFrame.size.width = self.bounds.width // Extra wide to make sure it wraps to next line.
|
||||||
|
|
||||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
|
||||||
self.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
|
self.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
|
||||||
|
|
||||||
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
|
|
||||||
if self.bounds.height > maximumCollapsedHeight
|
|
||||||
{
|
|
||||||
self.moreButton.isHidden = false
|
self.moreButton.isHidden = false
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
self.textContainer.exclusionPaths = []
|
||||||
|
|
||||||
self.moreButton.isHidden = true
|
self.moreButton.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,4 +71,24 @@ extension Keychain
|
|||||||
self.keychain["signingCertificateSerialNumber"] = newValue
|
self.keychain["signingCertificateSerialNumber"] = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var patreonAccessToken: String? {
|
||||||
|
get {
|
||||||
|
let accessToken = try? self.keychain.get("patreonAccessToken")
|
||||||
|
return accessToken
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.keychain["patreonAccessToken"] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var patreonRefreshToken: String? {
|
||||||
|
get {
|
||||||
|
let refreshToken = try? self.keychain.get("patreonRefreshToken")
|
||||||
|
return refreshToken
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.keychain["patreonRefreshToken"] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import Roxas
|
|||||||
|
|
||||||
class NavigationBar: UINavigationBar
|
class NavigationBar: UINavigationBar
|
||||||
{
|
{
|
||||||
|
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
||||||
|
|
||||||
|
private let backgroundColorView = UIView()
|
||||||
|
|
||||||
override init(frame: CGRect)
|
override init(frame: CGRect)
|
||||||
{
|
{
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@@ -28,14 +32,33 @@ class NavigationBar: UINavigationBar
|
|||||||
|
|
||||||
private func initialize()
|
private func initialize()
|
||||||
{
|
{
|
||||||
self.barTintColor = .white
|
|
||||||
self.shadowImage = UIImage()
|
self.shadowImage = UIImage()
|
||||||
|
|
||||||
|
if let tintColor = self.barTintColor
|
||||||
|
{
|
||||||
|
self.backgroundColorView.backgroundColor = tintColor
|
||||||
|
|
||||||
|
// Top = -50 to cover status bar area above navigation bar on any device.
|
||||||
|
// Bottom = -1 to prevent a flickering gray line from appearing.
|
||||||
|
self.addSubview(self.backgroundColorView, pinningEdgesWith: UIEdgeInsets(top: -50, left: 0, bottom: -1, right: 0))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.barTintColor = .white
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews()
|
override func layoutSubviews()
|
||||||
{
|
{
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
if self.backgroundColorView.superview != nil
|
||||||
|
{
|
||||||
|
self.insertSubview(self.backgroundColorView, at: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.automaticallyAdjustsItemPositions
|
||||||
|
{
|
||||||
// We can't easily shift just the back button up, so we shift the entire content view slightly.
|
// We can't easily shift just the back button up, so we shift the entire content view slightly.
|
||||||
for contentView in self.subviews
|
for contentView in self.subviews
|
||||||
{
|
{
|
||||||
@@ -44,3 +67,4 @@ class NavigationBar: UINavigationBar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,8 +36,35 @@ class PillButton: UIButton
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var countdownDate: Date? {
|
||||||
|
didSet {
|
||||||
|
self.isEnabled = (self.countdownDate == nil)
|
||||||
|
self.displayLink.isPaused = (self.countdownDate == nil)
|
||||||
|
|
||||||
|
if self.countdownDate == nil
|
||||||
|
{
|
||||||
|
self.setTitle(nil, for: .disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let progressView = UIProgressView(progressViewStyle: .default)
|
private let progressView = UIProgressView(progressViewStyle: .default)
|
||||||
|
|
||||||
|
private lazy var displayLink: CADisplayLink = {
|
||||||
|
let displayLink = CADisplayLink(target: self, selector: #selector(PillButton.updateCountdown))
|
||||||
|
displayLink.preferredFramesPerSecond = 15
|
||||||
|
displayLink.isPaused = true
|
||||||
|
displayLink.add(to: .main, forMode: .common)
|
||||||
|
return displayLink
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let dateComponentsFormatter: DateComponentsFormatter = {
|
||||||
|
let dateComponentsFormatter = DateComponentsFormatter()
|
||||||
|
dateComponentsFormatter.zeroFormattingBehavior = [.pad]
|
||||||
|
dateComponentsFormatter.collapsesLargestUnit = false
|
||||||
|
return dateComponentsFormatter
|
||||||
|
}()
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
var size = super.intrinsicContentSize
|
var size = super.intrinsicContentSize
|
||||||
size.width += 26
|
size.width += 26
|
||||||
@@ -45,6 +72,11 @@ class PillButton: UIButton
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit
|
||||||
|
{
|
||||||
|
self.displayLink.remove(from: .main, forMode: RunLoop.Mode.default)
|
||||||
|
}
|
||||||
|
|
||||||
override func awakeFromNib()
|
override func awakeFromNib()
|
||||||
{
|
{
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
@@ -101,4 +133,55 @@ private extension PillButton
|
|||||||
self.progressView.progressTintColor = self.tintColor
|
self.progressView.progressTintColor = self.tintColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func updateCountdown()
|
||||||
|
{
|
||||||
|
guard let endDate = self.countdownDate else { return }
|
||||||
|
|
||||||
|
let startDate = Date()
|
||||||
|
|
||||||
|
let interval = endDate.timeIntervalSince(startDate)
|
||||||
|
guard interval > 0 else {
|
||||||
|
self.isEnabled = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let text: String?
|
||||||
|
|
||||||
|
if interval < (1 * 60 * 60)
|
||||||
|
{
|
||||||
|
self.dateComponentsFormatter.unitsStyle = .positional
|
||||||
|
self.dateComponentsFormatter.allowedUnits = [.minute, .second]
|
||||||
|
|
||||||
|
text = self.dateComponentsFormatter.string(from: startDate, to: endDate)
|
||||||
|
}
|
||||||
|
else if interval < (2 * 24 * 60 * 60)
|
||||||
|
{
|
||||||
|
self.dateComponentsFormatter.unitsStyle = .positional
|
||||||
|
self.dateComponentsFormatter.allowedUnits = [.hour, .minute, .second]
|
||||||
|
|
||||||
|
text = self.dateComponentsFormatter.string(from: startDate, to: endDate)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.dateComponentsFormatter.unitsStyle = .full
|
||||||
|
self.dateComponentsFormatter.allowedUnits = [.day]
|
||||||
|
|
||||||
|
let numberOfDays = endDate.numberOfCalendarDays(since: startDate)
|
||||||
|
text = String(format: NSLocalizedString("%@ DAYS", comment: ""), NSNumber(value: numberOfDays))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let text = text
|
||||||
|
{
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.isEnabled = false
|
||||||
|
self.setTitle(text, for: .disabled)
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import UIKit
|
|||||||
|
|
||||||
extension UIColor
|
extension UIColor
|
||||||
{
|
{
|
||||||
static let altPurple = UIColor(named: "Purple")!
|
static let altPrimary = UIColor(named: "Primary")!
|
||||||
static let altGreen = UIColor(named: "Green")!
|
|
||||||
|
static let altPink = UIColor(named: "Pink")!
|
||||||
|
|
||||||
static let refreshRed = UIColor(named: "RefreshRed")!
|
static let refreshRed = UIColor(named: "RefreshRed")!
|
||||||
static let refreshOrange = UIColor(named: "RefreshOrange")!
|
static let refreshOrange = UIColor(named: "RefreshOrange")!
|
||||||
|
|||||||
16
AltStore/Extensions/UIScreen+CompactHeight.swift
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,20 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
extension UserDefaults
|
extension UserDefaults
|
||||||
{
|
{
|
||||||
@NSManaged var firstLaunch: Date?
|
@NSManaged var firstLaunch: Date?
|
||||||
|
|
||||||
|
@NSManaged var preferredServerID: String?
|
||||||
|
|
||||||
|
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||||
|
@NSManaged var isDebugModeEnabled: Bool
|
||||||
|
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||||
|
|
||||||
|
func registerDefaults()
|
||||||
|
{
|
||||||
|
self.register(defaults: [#keyPath(UserDefaults.isBackgroundRefreshEnabled): true])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>ALTDeviceID</key>
|
<key>ALTDeviceID</key>
|
||||||
<string>1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc</string>
|
<string>1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc</string>
|
||||||
|
<key>ALTServerID</key>
|
||||||
|
<string>1AAAB6FD-E8CE-4B70-8F26-4073215C03B0</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@@ -17,17 +19,17 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.2</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Editor</string>
|
<string>Editor</string>
|
||||||
<key>CFBundleURLName</key>
|
<key>CFBundleURLName</key>
|
||||||
<string>AltStore</string>
|
<string>AltStore General</string>
|
||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>altstore-com.rileytestut.altstore</string>
|
<string>altstore</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
@@ -36,8 +38,13 @@
|
|||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>altstore-com.rileytestut.AltStore</string>
|
<string>altstore-com.rileytestut.AltStore</string>
|
||||||
|
<string>altstore-com.rileytestut.AltStore.Beta</string>
|
||||||
<string>altstore-com.rileytestut.Delta</string>
|
<string>altstore-com.rileytestut.Delta</string>
|
||||||
|
<string>altstore-com.rileytestut.Delta.Beta</string>
|
||||||
|
<string>altstore-com.rileytestut.Delta.Lite</string>
|
||||||
|
<string>altstore-com.rileytestut.Delta.Lite.Beta</string>
|
||||||
<string>altstore-com.rileytestut.Clip</string>
|
<string>altstore-com.rileytestut.Clip</string>
|
||||||
|
<string>altstore-com.rileytestut.Clip.Beta</string>
|
||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
@@ -76,5 +83,25 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UTImportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>iOS App</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.apple.itunes.ipa</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<string>ipa</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ extension LaunchViewController
|
|||||||
super.finishLaunching()
|
super.finishLaunching()
|
||||||
|
|
||||||
AppManager.shared.update()
|
AppManager.shared.update()
|
||||||
|
PatreonAPI.shared.refreshPatreonAccount()
|
||||||
|
|
||||||
self.performSegue(withIdentifier: "finishLaunching", sender: nil)
|
self.performSegue(withIdentifier: "finishLaunching", sender: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
import AltKit
|
import AltKit
|
||||||
@@ -17,6 +18,8 @@ import Roxas
|
|||||||
extension AppManager
|
extension AppManager
|
||||||
{
|
{
|
||||||
static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource")
|
static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource")
|
||||||
|
|
||||||
|
static let expirationWarningNotificationID = "altstore-expiration-warning"
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppManager
|
class AppManager
|
||||||
@@ -54,15 +57,18 @@ extension AppManager
|
|||||||
let installedApps = try context.fetch(fetchRequest)
|
let installedApps = try context.fetch(fetchRequest)
|
||||||
for app in installedApps where app.storeApp != nil
|
for app in installedApps where app.storeApp != nil
|
||||||
{
|
{
|
||||||
if UIApplication.shared.canOpenURL(app.openAppURL)
|
if app.bundleIdentifier == StoreApp.altstoreAppID
|
||||||
{
|
{
|
||||||
// App is still installed, good!
|
self.scheduleExpirationWarningLocalNotification(for: app)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if !UIApplication.shared.canOpenURL(app.openAppURL)
|
||||||
{
|
{
|
||||||
context.delete(app)
|
context.delete(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try context.save()
|
try context.save()
|
||||||
}
|
}
|
||||||
@@ -175,17 +181,6 @@ private extension AppManager
|
|||||||
{
|
{
|
||||||
// Authenticate -> Download (if necessary) -> Resign -> Send -> Install.
|
// Authenticate -> Download (if necessary) -> Resign -> Send -> Install.
|
||||||
let group = group ?? OperationGroup()
|
let group = group ?? OperationGroup()
|
||||||
|
|
||||||
guard let server = ServerManager.shared.discoveredServers.first else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
group.completionHandler?(.failure(ConnectionError.serverNotFound))
|
|
||||||
}
|
|
||||||
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
group.server = server
|
|
||||||
|
|
||||||
var operations = [Operation]()
|
var operations = [Operation]()
|
||||||
|
|
||||||
|
|
||||||
@@ -200,6 +195,18 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
operations.append(authenticationOperation)
|
operations.append(authenticationOperation)
|
||||||
|
|
||||||
|
/* Find Server */
|
||||||
|
let findServerOperation = FindServerOperation(group: group)
|
||||||
|
findServerOperation.resultHandler = { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): group.error = error
|
||||||
|
case .success(let server): group.server = server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findServerOperation.addDependency(authenticationOperation)
|
||||||
|
operations.append(findServerOperation)
|
||||||
|
|
||||||
|
|
||||||
for app in apps
|
for app in apps
|
||||||
{
|
{
|
||||||
@@ -213,7 +220,7 @@ private extension AppManager
|
|||||||
guard let resignedApp = self.process(result, context: context) else { return }
|
guard let resignedApp = self.process(result, context: context) else { return }
|
||||||
context.resignedApp = resignedApp
|
context.resignedApp = resignedApp
|
||||||
}
|
}
|
||||||
resignAppOperation.addDependency(authenticationOperation)
|
resignAppOperation.addDependency(findServerOperation)
|
||||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||||
operations.append(resignAppOperation)
|
operations.append(resignAppOperation)
|
||||||
|
|
||||||
@@ -246,12 +253,13 @@ private extension AppManager
|
|||||||
{
|
{
|
||||||
// App is not yet installed (or we're forcing it to download a new version), so download it before resigning it.
|
// App is not yet installed (or we're forcing it to download a new version), so download it before resigning it.
|
||||||
|
|
||||||
let downloadOperation = DownloadAppOperation(app: app)
|
let downloadOperation = DownloadAppOperation(app: app, context: context)
|
||||||
downloadOperation.resultHandler = { (result) in
|
downloadOperation.resultHandler = { (result) in
|
||||||
guard let app = self.process(result, context: context) else { return }
|
guard let app = self.process(result, context: context) else { return }
|
||||||
context.app = app
|
context.app = app
|
||||||
}
|
}
|
||||||
progress.addChild(downloadOperation.progress, withPendingUnitCount: 40)
|
progress.addChild(downloadOperation.progress, withPendingUnitCount: 40)
|
||||||
|
downloadOperation.addDependency(findServerOperation)
|
||||||
resignAppOperation.addDependency(downloadOperation)
|
resignAppOperation.addDependency(downloadOperation)
|
||||||
operations.append(downloadOperation)
|
operations.append(downloadOperation)
|
||||||
}
|
}
|
||||||
@@ -267,6 +275,16 @@ private extension AppManager
|
|||||||
operations.append(sendAppOperation)
|
operations.append(sendAppOperation)
|
||||||
|
|
||||||
|
|
||||||
|
let beginInstallationHandler = group.beginInstallationHandler
|
||||||
|
group.beginInstallationHandler = { (installedApp) in
|
||||||
|
if installedApp.bundleIdentifier == StoreApp.altstoreAppID
|
||||||
|
{
|
||||||
|
self.scheduleExpirationWarningLocalNotification(for: installedApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
beginInstallationHandler?(installedApp)
|
||||||
|
}
|
||||||
|
|
||||||
/* Install */
|
/* Install */
|
||||||
let installOperation = InstallAppOperation(context: context)
|
let installOperation = InstallAppOperation(context: context)
|
||||||
installOperation.resultHandler = { (result) in
|
installOperation.resultHandler = { (result) in
|
||||||
@@ -330,8 +348,25 @@ private extension AppManager
|
|||||||
|
|
||||||
if let error = context.error
|
if let error = context.error
|
||||||
{
|
{
|
||||||
|
switch error
|
||||||
|
{
|
||||||
|
case let error as ALTServerError where error.code == .deviceNotFound || error.code == .lostConnection:
|
||||||
|
if let server = context.group.server, server.isPreferred
|
||||||
|
{
|
||||||
|
// Preferred server, so report errors normally.
|
||||||
context.group.results[context.bundleIdentifier] = .failure(error)
|
context.group.results[context.bundleIdentifier] = .failure(error)
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not preferred server, so ignore these specific errors and throw serverNotFound instead.
|
||||||
|
context.group.results[context.bundleIdentifier] = .failure(ConnectionError.serverNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
case let error:
|
||||||
|
context.group.results[context.bundleIdentifier] = .failure(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
else if let installedApp = context.installedApp
|
else if let installedApp = context.installedApp
|
||||||
{
|
{
|
||||||
context.group.results[context.bundleIdentifier] = .success(installedApp)
|
context.group.results[context.bundleIdentifier] = .success(installedApp)
|
||||||
@@ -351,7 +386,34 @@ private extension AppManager
|
|||||||
if context.group.results.count == context.group.progress.totalUnitCount
|
if context.group.results.count == context.group.progress.totalUnitCount
|
||||||
{
|
{
|
||||||
context.group.completionHandler?(.success(context.group.results))
|
context.group.completionHandler?(.success(context.group.results))
|
||||||
|
|
||||||
|
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
|
backgroundContext.performAndWait {
|
||||||
|
guard let altstore = InstalledApp.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID), in: backgroundContext) else { return }
|
||||||
|
self.scheduleExpirationWarningLocalNotification(for: altstore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scheduleExpirationWarningLocalNotification(for app: InstalledApp)
|
||||||
|
{
|
||||||
|
let notificationDate = app.expirationDate.addingTimeInterval(-1 * 60 * 60 * 24) // 24 hours before expiration.
|
||||||
|
|
||||||
|
let timeIntervalUntilNotification = notificationDate.timeIntervalSinceNow
|
||||||
|
guard timeIntervalUntilNotification > 0 else {
|
||||||
|
// Crashes if we pass negative value to UNTimeIntervalNotificationTrigger initializer.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeIntervalUntilNotification, repeats: false)
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = NSLocalizedString("AltStore Expiring Soon", comment: "")
|
||||||
|
content.body = NSLocalizedString("AltStore will expire in 24 hours. Open the app and refresh it to prevent it from expiring.", comment: "")
|
||||||
|
content.sound = .default
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: AppManager.expirationWarningNotificationID, content: content, trigger: trigger)
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
8
AltStore/Model/AltStore.xcdatamodeld/.xccurrentversion
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?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>_XCCurrentVersionName</key>
|
||||||
|
<string>AltStore 2.xcdatamodel</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14492.1" systemVersion="18G95" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
|
<entity name="Account" representedClassName="Account" syncable="YES">
|
||||||
|
<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>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="AppPermission" representedClassName="AppPermission" syncable="YES">
|
||||||
|
<attribute name="type" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="usageDescription" attributeType="String" syncable="YES"/>
|
||||||
|
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="permissions" inverseEntity="StoreApp" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
|
||||||
|
<attribute name="bundleIdentifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
|
<attribute name="resignedBundleIdentifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="version" attributeType="String" syncable="YES"/>
|
||||||
|
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="installedApp" inverseEntity="StoreApp" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="bundleIdentifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="NewsItem" representedClassName="NewsItem" syncable="YES">
|
||||||
|
<attribute name="appID" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="caption" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="date" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
|
<attribute name="externalURL" optional="YES" attributeType="URI" syncable="YES"/>
|
||||||
|
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="imageURL" optional="YES" attributeType="URI" syncable="YES"/>
|
||||||
|
<attribute name="isSilent" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="tintColor" optional="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
|
<attribute name="title" attributeType="String" syncable="YES"/>
|
||||||
|
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="newsItems" inverseEntity="Source" syncable="YES"/>
|
||||||
|
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="newsItems" inverseEntity="StoreApp" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
|
||||||
|
<attribute name="firstName" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="isPatron" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="RefreshAttempt" representedClassName="RefreshAttempt" syncable="YES">
|
||||||
|
<attribute name="date" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
|
<attribute name="errorDescription" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="isSuccess" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="Source" representedClassName="Source" syncable="YES">
|
||||||
|
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="sourceURL" attributeType="URI" syncable="YES"/>
|
||||||
|
<relationship name="apps" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="StoreApp" inverseName="source" inverseEntity="StoreApp" syncable="YES"/>
|
||||||
|
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="NewsItem" inverseName="source" inverseEntity="NewsItem" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="StoreApp" representedClassName="StoreApp" syncable="YES">
|
||||||
|
<attribute name="bundleIdentifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="developerName" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="downloadURL" attributeType="URI" syncable="YES"/>
|
||||||
|
<attribute name="iconURL" attributeType="URI" syncable="YES"/>
|
||||||
|
<attribute name="isBeta" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="localizedDescription" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="screenshotURLs" attributeType="Transformable" syncable="YES"/>
|
||||||
|
<attribute name="size" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="tintColor" optional="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
|
<attribute name="version" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="versionDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
|
<attribute name="versionDescription" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
|
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp" syncable="YES"/>
|
||||||
|
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem" syncable="YES"/>
|
||||||
|
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission" syncable="YES"/>
|
||||||
|
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="bundleIdentifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</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"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<elements>
|
||||||
|
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
||||||
|
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
||||||
|
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="150"/>
|
||||||
|
<element name="NewsItem" positionX="-45" positionY="126" width="128" height="225"/>
|
||||||
|
<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="120"/>
|
||||||
|
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="330"/>
|
||||||
|
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
||||||
|
</elements>
|
||||||
|
</model>
|
||||||
@@ -115,6 +115,12 @@ extension DatabaseManager
|
|||||||
let activeTeam = Team.first(satisfying: predicate, in: context)
|
let activeTeam = Team.first(satisfying: predicate, in: context)
|
||||||
return activeTeam
|
return activeTeam
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func patreonAccount(in context: NSManagedObjectContext = DatabaseManager.shared.viewContext) -> PatreonAccount?
|
||||||
|
{
|
||||||
|
let patronAccount = PatreonAccount.first(in: context)
|
||||||
|
return patronAccount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension DatabaseManager
|
private extension DatabaseManager
|
||||||
@@ -125,6 +131,20 @@ private extension DatabaseManager
|
|||||||
context.performAndWait {
|
context.performAndWait {
|
||||||
guard let localApp = ALTApplication(fileURL: Bundle.main.bundleURL) else { return }
|
guard let localApp = ALTApplication(fileURL: Bundle.main.bundleURL) else { return }
|
||||||
|
|
||||||
|
let altStoreSource: Source
|
||||||
|
|
||||||
|
if let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), Source.altStoreIdentifier), in: context)
|
||||||
|
{
|
||||||
|
altStoreSource = source
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
altStoreSource = Source.makeAltStoreSource(in: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to always update source URL to be current.
|
||||||
|
altStoreSource.sourceURL = Source.altStoreSourceURL
|
||||||
|
|
||||||
let storeApp: StoreApp
|
let storeApp: StoreApp
|
||||||
|
|
||||||
if let app = StoreApp.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID), in: context)
|
if let app = StoreApp.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID), in: context)
|
||||||
@@ -133,11 +153,9 @@ private extension DatabaseManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
let source = Source.makeAltStoreSource(in: context)
|
|
||||||
|
|
||||||
storeApp = StoreApp.makeAltStoreApp(in: context)
|
storeApp = StoreApp.makeAltStoreApp(in: context)
|
||||||
storeApp.version = localApp.version
|
storeApp.version = localApp.version
|
||||||
storeApp.source = source
|
storeApp.source = altStoreSource
|
||||||
}
|
}
|
||||||
|
|
||||||
let installedApp: InstalledApp
|
let installedApp: InstalledApp
|
||||||
@@ -152,30 +170,31 @@ private extension DatabaseManager
|
|||||||
installedApp.storeApp = storeApp
|
installedApp.storeApp = storeApp
|
||||||
}
|
}
|
||||||
|
|
||||||
installedApp.version = localApp.version
|
|
||||||
|
|
||||||
let fileURL = installedApp.fileURL
|
let fileURL = installedApp.fileURL
|
||||||
|
if !FileManager.default.fileExists(atPath: fileURL.path) || installedApp.version != localApp.version
|
||||||
if !FileManager.default.fileExists(atPath: fileURL.path)
|
|
||||||
{
|
{
|
||||||
|
FileManager.default.prepareTemporaryURL() { (temporaryFileURL) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
try FileManager.default.copyItem(at: Bundle.main.bundleURL, to: fileURL)
|
try FileManager.default.copyItem(at: Bundle.main.bundleURL, to: temporaryFileURL)
|
||||||
|
|
||||||
let infoPlistURL = fileURL.appendingPathComponent("Info.plist")
|
let infoPlistURL = temporaryFileURL.appendingPathComponent("Info.plist")
|
||||||
|
|
||||||
// TODO: Copy to temporary location, modify it, _then_ copy to final destination.
|
|
||||||
guard var infoDictionary = Bundle.main.infoDictionary else { throw ALTError(.missingInfoPlist) }
|
guard var infoDictionary = Bundle.main.infoDictionary else { throw ALTError(.missingInfoPlist) }
|
||||||
infoDictionary[kCFBundleIdentifierKey as String] = StoreApp.altstoreAppID
|
infoDictionary[kCFBundleIdentifierKey as String] = StoreApp.altstoreAppID
|
||||||
try (infoDictionary as NSDictionary).write(to: infoPlistURL)
|
try (infoDictionary as NSDictionary).write(to: infoPlistURL)
|
||||||
|
|
||||||
|
try FileManager.default.copyItem(at: temporaryFileURL, to: fileURL, shouldReplace: true)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Failed to copy AltStore app bundle to its proper location.", error)
|
print("Failed to copy AltStore app bundle to its proper location.", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try? FileManager.default.removeItem(at: fileURL)
|
// Must go after comparing versions to see if we need to update our cached AltStore app bundle.
|
||||||
}
|
installedApp.version = localApp.version
|
||||||
}
|
|
||||||
|
|
||||||
if let provisioningProfile = localApp.provisioningProfile
|
if let provisioningProfile = localApp.provisioningProfile
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,7 +82,17 @@ extension InstalledApp
|
|||||||
|
|
||||||
class func fetchAppsForRefreshingAll(in context: NSManagedObjectContext) -> [InstalledApp]
|
class func fetchAppsForRefreshingAll(in context: NSManagedObjectContext) -> [InstalledApp]
|
||||||
{
|
{
|
||||||
let predicate = NSPredicate(format: "%K != %@", #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
var predicate = NSPredicate(format: "%K != %@", #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||||
|
|
||||||
|
if let patreonAccount = DatabaseManager.shared.patreonAccount(in: context), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||||
|
{
|
||||||
|
// No additional predicate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate,
|
||||||
|
NSPredicate(format: "%K == nil OR %K == NO", #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.storeApp.isBeta))])
|
||||||
|
}
|
||||||
|
|
||||||
var installedApps = InstalledApp.all(satisfying: predicate,
|
var installedApps = InstalledApp.all(satisfying: predicate,
|
||||||
sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)],
|
sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)],
|
||||||
@@ -99,12 +109,23 @@ extension InstalledApp
|
|||||||
|
|
||||||
class func fetchAppsForBackgroundRefresh(in context: NSManagedObjectContext) -> [InstalledApp]
|
class func fetchAppsForBackgroundRefresh(in context: NSManagedObjectContext) -> [InstalledApp]
|
||||||
{
|
{
|
||||||
let date = Date().addingTimeInterval(-120)
|
// Date 6 hours before now.
|
||||||
|
let date = Date().addingTimeInterval(-1 * 6 * 60 * 60)
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "(%K < %@) AND (%K != %@)",
|
var predicate = NSPredicate(format: "(%K < %@) AND (%K != %@)",
|
||||||
#keyPath(InstalledApp.refreshedDate), date as NSDate,
|
#keyPath(InstalledApp.refreshedDate), date as NSDate,
|
||||||
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||||
|
|
||||||
|
if let patreonAccount = DatabaseManager.shared.patreonAccount(in: context), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||||
|
{
|
||||||
|
// No additional predicate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate,
|
||||||
|
NSPredicate(format: "%K == nil OR %K == NO", #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.storeApp.isBeta))])
|
||||||
|
}
|
||||||
|
|
||||||
var installedApps = InstalledApp.all(satisfying: predicate,
|
var installedApps = InstalledApp.all(satisfying: predicate,
|
||||||
sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)],
|
sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)],
|
||||||
in: context)
|
in: context)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
guard let conflictedObject = conflict.conflictingObjects.first as? Source else { break }
|
guard let conflictedObject = conflict.conflictingObjects.first as? Source else { break }
|
||||||
|
|
||||||
let bundleIdentifiers = Set(conflictedObject.apps.map { $0.bundleIdentifier })
|
let bundleIdentifiers = Set(conflictedObject.apps.map { $0.bundleIdentifier })
|
||||||
|
let newsItemIdentifiers = Set(conflictedObject.newsItems.map { $0.identifier })
|
||||||
|
|
||||||
for app in databaseObject.apps
|
for app in databaseObject.apps
|
||||||
{
|
{
|
||||||
@@ -44,6 +45,15 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for newsItem in databaseObject.newsItems
|
||||||
|
{
|
||||||
|
if !newsItemIdentifiers.contains(newsItem.identifier)
|
||||||
|
{
|
||||||
|
// No longer listed in Source, so remove it from database.
|
||||||
|
newsItem.managedObjectContext?.delete(newsItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
AltStore/Model/Migrations/Policies/StoreAppPolicy.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// StoreAppPolicy.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/14/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc(StoreAppToStoreAppMigrationPolicy)
|
||||||
|
class StoreAppToStoreAppMigrationPolicy: NSEntityMigrationPolicy
|
||||||
|
{
|
||||||
|
@objc(migrateIconURL)
|
||||||
|
func migrateIconURL() -> URL
|
||||||
|
{
|
||||||
|
return URL(string: "https://via.placeholder.com/150")!
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(migrateScreenshotURLs)
|
||||||
|
func migrateScreenshotURLs() -> NSCopying
|
||||||
|
{
|
||||||
|
return [] as NSArray
|
||||||
|
}
|
||||||
|
}
|
||||||
90
AltStore/Model/NewsItem.swift
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// NewsItem.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/29/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc(NewsItem)
|
||||||
|
class NewsItem: NSManagedObject, Decodable, Fetchable
|
||||||
|
{
|
||||||
|
/* Properties */
|
||||||
|
@NSManaged var identifier: String
|
||||||
|
@NSManaged var date: Date
|
||||||
|
|
||||||
|
@NSManaged var title: String
|
||||||
|
@NSManaged var caption: String
|
||||||
|
@NSManaged var tintColor: UIColor
|
||||||
|
@NSManaged var sortIndex: Int32
|
||||||
|
@NSManaged var isSilent: Bool
|
||||||
|
|
||||||
|
@NSManaged var imageURL: URL?
|
||||||
|
@NSManaged var externalURL: URL?
|
||||||
|
|
||||||
|
@NSManaged var appID: String?
|
||||||
|
|
||||||
|
/* Relationships */
|
||||||
|
@NSManaged var storeApp: StoreApp?
|
||||||
|
@NSManaged var source: Source?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case identifier
|
||||||
|
case date
|
||||||
|
case title
|
||||||
|
case caption
|
||||||
|
case tintColor
|
||||||
|
case imageURL
|
||||||
|
case externalURL = "url"
|
||||||
|
case appID
|
||||||
|
case notify
|
||||||
|
}
|
||||||
|
|
||||||
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
|
{
|
||||||
|
super.init(entity: entity, insertInto: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
|
super.init(entity: NewsItem.entity(), insertInto: context)
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
self.identifier = try container.decode(String.self, forKey: .identifier)
|
||||||
|
self.date = try container.decode(Date.self, forKey: .date)
|
||||||
|
|
||||||
|
self.title = try container.decode(String.self, forKey: .title)
|
||||||
|
self.caption = try container.decode(String.self, forKey: .caption)
|
||||||
|
|
||||||
|
if let tintColorHex = try container.decodeIfPresent(String.self, forKey: .tintColor)
|
||||||
|
{
|
||||||
|
guard let tintColor = UIColor(hexString: tintColorHex) else {
|
||||||
|
throw DecodingError.dataCorruptedError(forKey: .tintColor, in: container, debugDescription: "Hex code is invalid.")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tintColor = tintColor
|
||||||
|
}
|
||||||
|
|
||||||
|
self.imageURL = try container.decodeIfPresent(URL.self, forKey: .imageURL)
|
||||||
|
self.externalURL = try container.decodeIfPresent(URL.self, forKey: .externalURL)
|
||||||
|
|
||||||
|
self.appID = try container.decodeIfPresent(String.self, forKey: .appID)
|
||||||
|
|
||||||
|
let notify = try container.decodeIfPresent(Bool.self, forKey: .notify) ?? false
|
||||||
|
self.isSilent = !notify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NewsItem
|
||||||
|
{
|
||||||
|
@nonobjc class func fetchRequest() -> NSFetchRequest<NewsItem>
|
||||||
|
{
|
||||||
|
return NSFetchRequest<NewsItem>(entityName: "NewsItem")
|
||||||
|
}
|
||||||
|
}
|
||||||
74
AltStore/Model/PatreonAccount.swift
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// PatreonAccount.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/20/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
struct AccountResponse: Decodable
|
||||||
|
{
|
||||||
|
struct Data: Decodable
|
||||||
|
{
|
||||||
|
struct Attributes: Decodable
|
||||||
|
{
|
||||||
|
var first_name: String?
|
||||||
|
var full_name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var attributes: Attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
var data: Data
|
||||||
|
var included: [PatronResponse]?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(PatreonAccount)
|
||||||
|
class PatreonAccount: NSManagedObject, Fetchable
|
||||||
|
{
|
||||||
|
@NSManaged var identifier: String
|
||||||
|
|
||||||
|
@NSManaged var name: String
|
||||||
|
@NSManaged var firstName: String?
|
||||||
|
|
||||||
|
@NSManaged var isPatron: Bool
|
||||||
|
|
||||||
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
|
{
|
||||||
|
super.init(entity: entity, insertInto: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(response: PatreonAPI.AccountResponse, context: NSManagedObjectContext)
|
||||||
|
{
|
||||||
|
super.init(entity: PatreonAccount.entity(), insertInto: context)
|
||||||
|
|
||||||
|
self.identifier = response.data.id
|
||||||
|
self.name = response.data.attributes.full_name
|
||||||
|
self.firstName = response.data.attributes.first_name
|
||||||
|
|
||||||
|
if let patronResponse = response.included?.first
|
||||||
|
{
|
||||||
|
let patron = Patron(response: patronResponse)
|
||||||
|
self.isPatron = (patron.status == .active)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.isPatron = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PatreonAccount
|
||||||
|
{
|
||||||
|
@nonobjc class func fetchRequest() -> NSFetchRequest<PatreonAccount>
|
||||||
|
{
|
||||||
|
return NSFetchRequest<PatreonAccount>(entityName: "PatreonAccount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ import CoreData
|
|||||||
extension Source
|
extension Source
|
||||||
{
|
{
|
||||||
static let altStoreIdentifier = "com.rileytestut.AltStore"
|
static let altStoreIdentifier = "com.rileytestut.AltStore"
|
||||||
|
static let altStoreSourceURL = URL(string: "https://cdn.altstore.io/file/altstore/apps.json")!
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(Source)
|
@objc(Source)
|
||||||
@@ -23,6 +24,7 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@objc(apps) @NSManaged private(set) var _apps: NSOrderedSet
|
@objc(apps) @NSManaged private(set) var _apps: NSOrderedSet
|
||||||
|
@objc(newsItems) @NSManaged private(set) var _newsItems: NSOrderedSet
|
||||||
|
|
||||||
@nonobjc var apps: [StoreApp] {
|
@nonobjc var apps: [StoreApp] {
|
||||||
get {
|
get {
|
||||||
@@ -33,12 +35,22 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@nonobjc var newsItems: [NewsItem] {
|
||||||
|
get {
|
||||||
|
return self._newsItems.array as! [NewsItem]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self._newsItems = NSOrderedSet(array: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey
|
private enum CodingKeys: String, CodingKey
|
||||||
{
|
{
|
||||||
case name
|
case name
|
||||||
case identifier
|
case identifier
|
||||||
case sourceURL
|
case sourceURL
|
||||||
case apps
|
case apps
|
||||||
|
case news
|
||||||
}
|
}
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
@@ -63,10 +75,31 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
app.sortIndex = Int32(index)
|
app.sortIndex = Int32(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newsItems = try container.decodeIfPresent([NewsItem].self, forKey: .news) ?? []
|
||||||
|
for (index, item) in newsItems.enumerated()
|
||||||
|
{
|
||||||
|
item.sortIndex = Int32(index)
|
||||||
|
}
|
||||||
|
|
||||||
context.insert(self)
|
context.insert(self)
|
||||||
|
|
||||||
|
let appsByID = Dictionary(apps.map { ($0.bundleIdentifier, $0) }, uniquingKeysWith: { (a, b) in return a })
|
||||||
|
|
||||||
|
for newsItem in newsItems
|
||||||
|
{
|
||||||
|
newsItem.source = self
|
||||||
|
|
||||||
|
guard let appID = newsItem.appID else { continue }
|
||||||
|
|
||||||
|
if let storeApp = appsByID[appID]
|
||||||
|
{
|
||||||
|
newsItem.storeApp = storeApp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Must assign after we're inserted into context.
|
// Must assign after we're inserted into context.
|
||||||
self._apps = NSMutableOrderedSet(array: apps)
|
self._apps = NSMutableOrderedSet(array: apps)
|
||||||
|
self._newsItems = NSMutableOrderedSet(array: newsItems)
|
||||||
|
|
||||||
print("Downloaded Order:", self.apps.map { $0.bundleIdentifier })
|
print("Downloaded Order:", self.apps.map { $0.bundleIdentifier })
|
||||||
}
|
}
|
||||||
@@ -79,7 +112,7 @@ extension Source
|
|||||||
let source = Source(context: context)
|
let source = Source(context: context)
|
||||||
source.name = "AltStore"
|
source.name = "AltStore"
|
||||||
source.identifier = Source.altStoreIdentifier
|
source.identifier = Source.altStoreIdentifier
|
||||||
source.sourceURL = URL(string: "https://www.dropbox.com/s/6qi1vt6hsi88lv6/Apps-Dev.json?dl=1")!
|
source.sourceURL = Source.altStoreSourceURL
|
||||||
|
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ import AltSign
|
|||||||
|
|
||||||
extension StoreApp
|
extension StoreApp
|
||||||
{
|
{
|
||||||
|
#if BETA
|
||||||
|
static let altstoreAppID = "com.rileytestut.AltStore.Beta"
|
||||||
|
static let alternativeAltStoreAppID = "com.rileytestut.AltStore"
|
||||||
|
#else
|
||||||
static let altstoreAppID = "com.rileytestut.AltStore"
|
static let altstoreAppID = "com.rileytestut.AltStore"
|
||||||
|
static let alternativeAltStoreAppID = "com.rileytestut.AltStore.Beta"
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(StoreApp)
|
@objc(StoreApp)
|
||||||
@@ -29,8 +35,8 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
@NSManaged private(set) var localizedDescription: String
|
@NSManaged private(set) var localizedDescription: String
|
||||||
@NSManaged private(set) var size: Int32
|
@NSManaged private(set) var size: Int32
|
||||||
|
|
||||||
@NSManaged private(set) var iconName: String
|
@NSManaged private(set) var iconURL: URL
|
||||||
@NSManaged private(set) var screenshotNames: [String]
|
@NSManaged private(set) var screenshotURLs: [URL]
|
||||||
|
|
||||||
@NSManaged var version: String
|
@NSManaged var version: String
|
||||||
@NSManaged private(set) var versionDate: Date
|
@NSManaged private(set) var versionDate: Date
|
||||||
@@ -38,6 +44,7 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
|
|
||||||
@NSManaged private(set) var downloadURL: URL
|
@NSManaged private(set) var downloadURL: URL
|
||||||
@NSManaged private(set) var tintColor: UIColor?
|
@NSManaged private(set) var tintColor: UIColor?
|
||||||
|
@NSManaged private(set) var isBeta: Bool
|
||||||
|
|
||||||
@NSManaged var sortIndex: Int32
|
@NSManaged var sortIndex: Int32
|
||||||
|
|
||||||
@@ -64,13 +71,14 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
case version
|
case version
|
||||||
case versionDescription
|
case versionDescription
|
||||||
case versionDate
|
case versionDate
|
||||||
case iconName
|
case iconURL
|
||||||
case screenshotNames
|
case screenshotURLs
|
||||||
case downloadURL
|
case downloadURL
|
||||||
case tintColor
|
case tintColor
|
||||||
case subtitle
|
case subtitle
|
||||||
case permissions
|
case permissions
|
||||||
case size
|
case size
|
||||||
|
case isBeta = "beta"
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws
|
required init(from decoder: Decoder) throws
|
||||||
@@ -91,8 +99,8 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
self.versionDate = try container.decode(Date.self, forKey: .versionDate)
|
self.versionDate = try container.decode(Date.self, forKey: .versionDate)
|
||||||
self.versionDescription = try container.decodeIfPresent(String.self, forKey: .versionDescription)
|
self.versionDescription = try container.decodeIfPresent(String.self, forKey: .versionDescription)
|
||||||
|
|
||||||
self.iconName = try container.decode(String.self, forKey: .iconName)
|
self.iconURL = try container.decode(URL.self, forKey: .iconURL)
|
||||||
self.screenshotNames = try container.decodeIfPresent([String].self, forKey: .screenshotNames) ?? []
|
self.screenshotURLs = try container.decodeIfPresent([URL].self, forKey: .screenshotURLs) ?? []
|
||||||
|
|
||||||
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
||||||
|
|
||||||
@@ -106,6 +114,7 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.size = try container.decode(Int32.self, forKey: .size)
|
self.size = try container.decode(Int32.self, forKey: .size)
|
||||||
|
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
|
||||||
|
|
||||||
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
||||||
|
|
||||||
@@ -130,12 +139,16 @@ extension StoreApp
|
|||||||
app.bundleIdentifier = StoreApp.altstoreAppID
|
app.bundleIdentifier = StoreApp.altstoreAppID
|
||||||
app.developerName = "Riley Testut"
|
app.developerName = "Riley Testut"
|
||||||
app.localizedDescription = "AltStore is an alternative App Store."
|
app.localizedDescription = "AltStore is an alternative App Store."
|
||||||
app.iconName = ""
|
app.iconURL = URL(string: "https://user-images.githubusercontent.com/705880/63392210-540c5980-c37b-11e9-968c-8742fc68ab2e.png")!
|
||||||
app.screenshotNames = []
|
app.screenshotURLs = []
|
||||||
app.version = "1.0"
|
app.version = "1.0"
|
||||||
app.versionDate = Date()
|
app.versionDate = Date()
|
||||||
app.downloadURL = URL(string: "http://rileytestut.com")!
|
app.downloadURL = URL(string: "http://rileytestut.com")!
|
||||||
|
|
||||||
|
#if BETA
|
||||||
|
app.isBeta = true
|
||||||
|
#endif
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ class InstalledAppCollectionViewCell: UICollectionViewCell
|
|||||||
@IBOutlet var nameLabel: UILabel!
|
@IBOutlet var nameLabel: UILabel!
|
||||||
@IBOutlet var developerLabel: UILabel!
|
@IBOutlet var developerLabel: UILabel!
|
||||||
@IBOutlet var refreshButton: PillButton!
|
@IBOutlet var refreshButton: PillButton!
|
||||||
|
@IBOutlet var betaBadgeView: UIImageView!
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstalledAppsCollectionHeaderView: UICollectionReusableView
|
class InstalledAppsCollectionHeaderView: UICollectionReusableView
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import Roxas
|
|||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
|
import Nuke
|
||||||
|
|
||||||
private let maximumCollapsedUpdatesCount = 2
|
private let maximumCollapsedUpdatesCount = 2
|
||||||
|
|
||||||
extension MyAppsViewController
|
extension MyAppsViewController
|
||||||
@@ -79,9 +81,13 @@ class MyAppsViewController: UICollectionViewController
|
|||||||
|
|
||||||
self.sideloadingProgressView = UIProgressView(progressViewStyle: .bar)
|
self.sideloadingProgressView = UIProgressView(progressViewStyle: .bar)
|
||||||
self.sideloadingProgressView.translatesAutoresizingMaskIntoConstraints = false
|
self.sideloadingProgressView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.sideloadingProgressView.progressTintColor = .altGreen
|
self.sideloadingProgressView.progressTintColor = .altPrimary
|
||||||
self.sideloadingProgressView.progress = 0
|
self.sideloadingProgressView.progress = 0
|
||||||
|
|
||||||
|
#if !BETA
|
||||||
|
self.navigationItem.leftBarButtonItem = nil
|
||||||
|
#endif
|
||||||
|
|
||||||
if let navigationBar = self.navigationController?.navigationBar
|
if let navigationBar = self.navigationController?.navigationBar
|
||||||
{
|
{
|
||||||
navigationBar.addSubview(self.sideloadingProgressView)
|
navigationBar.addSubview(self.sideloadingProgressView)
|
||||||
@@ -93,18 +99,33 @@ class MyAppsViewController: UICollectionViewController
|
|||||||
// Gestures
|
// Gestures
|
||||||
self.longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(MyAppsViewController.handleLongPressGesture(_:)))
|
self.longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(MyAppsViewController.handleLongPressGesture(_:)))
|
||||||
self.collectionView.addGestureRecognizer(self.longPressGestureRecognizer)
|
self.collectionView.addGestureRecognizer(self.longPressGestureRecognizer)
|
||||||
|
|
||||||
|
self.registerForPreviewing(with: self, sourceView: self.collectionView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
self.updateDataSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
{
|
{
|
||||||
guard segue.identifier == "showApp" else { return }
|
guard let identifier = segue.identifier else { return }
|
||||||
|
|
||||||
|
switch identifier
|
||||||
|
{
|
||||||
|
case "showApp", "showUpdate":
|
||||||
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return }
|
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return }
|
||||||
|
|
||||||
let installedApp = self.dataSource.item(at: indexPath)
|
let installedApp = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
let appViewController = segue.destination as! AppViewController
|
let appViewController = segue.destination as! AppViewController
|
||||||
appViewController.app = installedApp.storeApp
|
appViewController.app = installedApp.storeApp
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool
|
||||||
@@ -136,7 +157,7 @@ private extension MyAppsViewController
|
|||||||
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in
|
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in
|
||||||
cell.layer.cornerRadius = 20
|
cell.layer.cornerRadius = 20
|
||||||
cell.layer.masksToBounds = true
|
cell.layer.masksToBounds = true
|
||||||
cell.contentView.backgroundColor = UIColor.altGreen.withAlphaComponent(0.15)
|
cell.contentView.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dynamicDataSource
|
return dynamicDataSource
|
||||||
@@ -152,14 +173,17 @@ private extension MyAppsViewController
|
|||||||
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
|
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
|
||||||
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
||||||
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
||||||
|
guard let self = self else { return }
|
||||||
guard let app = installedApp.storeApp else { return }
|
guard let app = installedApp.storeApp else { return }
|
||||||
|
|
||||||
let cell = cell as! UpdateCollectionViewCell
|
let cell = cell as! UpdateCollectionViewCell
|
||||||
cell.tintColor = app.tintColor ?? .altGreen
|
cell.tintColor = app.tintColor ?? .altPrimary
|
||||||
cell.nameLabel.text = app.name
|
cell.nameLabel.text = app.name
|
||||||
cell.versionDescriptionTextView.text = app.versionDescription
|
cell.versionDescriptionTextView.text = app.versionDescription
|
||||||
cell.appIconImageView.image = UIImage(named: app.iconName)
|
cell.appIconImageView.image = nil
|
||||||
|
cell.appIconImageView.isIndicatingActivity = true
|
||||||
|
cell.betaBadgeView.isHidden = !app.isBeta
|
||||||
|
|
||||||
cell.updateButton.isIndicatingActivity = false
|
cell.updateButton.isIndicatingActivity = false
|
||||||
cell.updateButton.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
cell.updateButton.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
||||||
@@ -182,6 +206,34 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
cell.setNeedsLayout()
|
cell.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
||||||
|
guard let iconURL = installedApp.storeApp?.iconURL else { return nil }
|
||||||
|
|
||||||
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
|
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in
|
||||||
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
|
if let image = response?.image
|
||||||
|
{
|
||||||
|
completionHandler(image, nil)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completionHandler(nil, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||||
|
let cell = cell as! UpdateCollectionViewCell
|
||||||
|
cell.appIconImageView.isIndicatingActivity = false
|
||||||
|
cell.appIconImageView.image = image
|
||||||
|
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
print("Error loading image:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
}
|
}
|
||||||
@@ -198,11 +250,12 @@ private extension MyAppsViewController
|
|||||||
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.cellIdentifierHandler = { _ in "AppCell" }
|
dataSource.cellIdentifierHandler = { _ in "AppCell" }
|
||||||
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
|
||||||
let tintColor = installedApp.storeApp?.tintColor ?? .altGreen
|
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
|
||||||
|
|
||||||
let cell = cell as! InstalledAppCollectionViewCell
|
let cell = cell as! InstalledAppCollectionViewCell
|
||||||
cell.tintColor = tintColor
|
cell.tintColor = tintColor
|
||||||
cell.appIconImageView.isIndicatingActivity = true
|
cell.appIconImageView.isIndicatingActivity = true
|
||||||
|
cell.betaBadgeView.isHidden = !(installedApp.storeApp?.isBeta ?? false)
|
||||||
|
|
||||||
cell.refreshButton.isIndicatingActivity = false
|
cell.refreshButton.isIndicatingActivity = false
|
||||||
cell.refreshButton.addTarget(self, action: #selector(MyAppsViewController.refreshApp(_:)), for: .primaryActionTriggered)
|
cell.refreshButton.addTarget(self, action: #selector(MyAppsViewController.refreshApp(_:)), for: .primaryActionTriggered)
|
||||||
@@ -264,6 +317,21 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDataSource()
|
||||||
|
{
|
||||||
|
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||||
|
{
|
||||||
|
self.dataSource.predicate = nil
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.dataSource.predicate = NSPredicate(format: "%K == nil OR %K == NO OR %K == %@",
|
||||||
|
#keyPath(InstalledApp.storeApp),
|
||||||
|
#keyPath(InstalledApp.storeApp.isBeta),
|
||||||
|
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MyAppsViewController
|
private extension MyAppsViewController
|
||||||
@@ -281,10 +349,13 @@ private extension MyAppsViewController
|
|||||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.isViewLoaded
|
||||||
|
{
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation {
|
||||||
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
|
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping (Result<[String : Result<InstalledApp, Error>], Error>) -> Void)
|
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping (Result<[String : Result<InstalledApp, Error>], Error>) -> Void)
|
||||||
{
|
{
|
||||||
@@ -313,17 +384,20 @@ private extension MyAppsViewController
|
|||||||
guard !failures.isEmpty else { break }
|
guard !failures.isEmpty else { break }
|
||||||
|
|
||||||
let localizedText: String
|
let localizedText: String
|
||||||
|
let detailText: String?
|
||||||
|
|
||||||
if let failure = failures.first, failures.count == 1
|
if let failure = failures.first, failures.count == 1
|
||||||
{
|
{
|
||||||
localizedText = failure.value.localizedDescription
|
localizedText = failure.value.localizedDescription
|
||||||
|
detailText = nil
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
localizedText = String(format: NSLocalizedString("Failed to refresh %@ apps.", comment: ""), NSNumber(value: failures.count))
|
localizedText = String(format: NSLocalizedString("Failed to refresh %@ apps.", comment: ""), NSNumber(value: failures.count))
|
||||||
|
detailText = failures.first?.value.localizedDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
let toastView = ToastView(text: localizedText, detailText: nil)
|
let toastView = ToastView(text: localizedText, detailText: detailText)
|
||||||
toastView.tintColor = .refreshRed
|
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,7 +586,7 @@ private extension MyAppsViewController
|
|||||||
self.present(documentPickerViewController, animated: true, completion: nil)
|
self.present(documentPickerViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Sideload Apps (Beta)", comment: ""), message: NSLocalizedString("You may only install 10 apps + app extensions per week due to Apple's restrictions.\n\nIf you encounter an app that is not able to be sideloaded, please report the app to riley@rileytestut.com.", comment: ""), preferredStyle: .alert)
|
let alertController = UIAlertController(title: NSLocalizedString("Sideload Apps (Beta)", comment: ""), message: NSLocalizedString("You may only install 10 apps + app extensions per week due to Apple's restrictions.\n\nIf you encounter an app that is not able to be sideloaded, please report the app to support@altstore.io.", comment: ""), preferredStyle: .alert)
|
||||||
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .default, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .default, handler: { (action) in
|
||||||
sideloadApp()
|
sideloadApp()
|
||||||
}))
|
}))
|
||||||
@@ -584,10 +658,10 @@ extension MyAppsViewController
|
|||||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
|
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation {
|
||||||
headerView.button.backgroundColor = UIColor.altGreen.withAlphaComponent(0.15)
|
headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
|
||||||
headerView.button.setTitle("▾", for: .normal)
|
headerView.button.setTitle("▾", for: .normal)
|
||||||
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
|
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
|
||||||
headerView.button.setTitleColor(.altGreen, for: .normal)
|
headerView.button.setTitleColor(.altPrimary, for: .normal)
|
||||||
headerView.button.addTarget(self, action: #selector(MyAppsViewController.toggleAppUpdates), for: .primaryActionTriggered)
|
headerView.button.addTarget(self, action: #selector(MyAppsViewController.toggleAppUpdates), for: .primaryActionTriggered)
|
||||||
|
|
||||||
if self.isUpdateSectionCollapsed
|
if self.isUpdateSectionCollapsed
|
||||||
@@ -613,7 +687,7 @@ extension MyAppsViewController
|
|||||||
headerView.textLabel.text = NSLocalizedString("Installed", comment: "")
|
headerView.textLabel.text = NSLocalizedString("Installed", comment: "")
|
||||||
|
|
||||||
headerView.button.isIndicatingActivity = false
|
headerView.button.isIndicatingActivity = false
|
||||||
headerView.button.activityIndicatorView.color = .altGreen
|
headerView.button.activityIndicatorView.color = .altPrimary
|
||||||
headerView.button.setTitle(NSLocalizedString("Refresh All", comment: ""), for: .normal)
|
headerView.button.setTitle(NSLocalizedString("Refresh All", comment: ""), for: .normal)
|
||||||
headerView.button.addTarget(self, action: #selector(MyAppsViewController.refreshAllApps(_:)), for: .primaryActionTriggered)
|
headerView.button.addTarget(self, action: #selector(MyAppsViewController.refreshAllApps(_:)), for: .primaryActionTriggered)
|
||||||
headerView.button.isIndicatingActivity = self.isRefreshingAllApps
|
headerView.button.isIndicatingActivity = self.isRefreshingAllApps
|
||||||
@@ -624,6 +698,19 @@ extension MyAppsViewController
|
|||||||
return headerView
|
return headerView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||||
|
{
|
||||||
|
let section = Section.allCases[indexPath.section]
|
||||||
|
switch section
|
||||||
|
{
|
||||||
|
case .updates:
|
||||||
|
guard let cell = collectionView.cellForItem(at: indexPath) else { break }
|
||||||
|
self.performSegue(withIdentifier: "showUpdate", sender: cell)
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MyAppsViewController: UICollectionViewDelegateFlowLayout
|
extension MyAppsViewController: UICollectionViewDelegateFlowLayout
|
||||||
@@ -795,3 +882,37 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MyAppsViewController: UIViewControllerPreviewingDelegate
|
||||||
|
{
|
||||||
|
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?
|
||||||
|
{
|
||||||
|
guard
|
||||||
|
let indexPath = self.collectionView.indexPathForItem(at: location),
|
||||||
|
let cell = self.collectionView.cellForItem(at: indexPath)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
let section = Section.allCases[indexPath.section]
|
||||||
|
switch section
|
||||||
|
{
|
||||||
|
case .updates:
|
||||||
|
previewingContext.sourceRect = cell.frame
|
||||||
|
|
||||||
|
let app = self.dataSource.item(at: indexPath)
|
||||||
|
guard let storeApp = app.storeApp else { return nil}
|
||||||
|
|
||||||
|
let appViewController = AppViewController.makeAppViewController(app: storeApp)
|
||||||
|
return appViewController
|
||||||
|
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController)
|
||||||
|
{
|
||||||
|
let point = CGPoint(x: previewingContext.sourceRect.midX, y: previewingContext.sourceRect.midY)
|
||||||
|
guard let indexPath = self.collectionView.indexPathForItem(at: point), let cell = self.collectionView.cellForItem(at: indexPath) else { return }
|
||||||
|
|
||||||
|
self.performSegue(withIdentifier: "showUpdate", sender: cell)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ extension UpdateCollectionViewCell
|
|||||||
@IBOutlet var updateButton: PillButton!
|
@IBOutlet var updateButton: PillButton!
|
||||||
@IBOutlet var versionDescriptionTitleLabel: UILabel!
|
@IBOutlet var versionDescriptionTitleLabel: UILabel!
|
||||||
@IBOutlet var versionDescriptionTextView: CollapsingTextView!
|
@IBOutlet var versionDescriptionTextView: CollapsingTextView!
|
||||||
|
@IBOutlet var betaBadgeView: UIImageView!
|
||||||
|
|
||||||
override func awakeFromNib()
|
override func awakeFromNib()
|
||||||
{
|
{
|
||||||
@@ -57,6 +58,22 @@ extension UpdateCollectionViewCell
|
|||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
|
||||||
|
{
|
||||||
|
let view = super.hitTest(point, with: event)
|
||||||
|
|
||||||
|
if view == self.versionDescriptionTextView
|
||||||
|
{
|
||||||
|
// Forward touches on the text view (but not on the nested "more" button)
|
||||||
|
// so cell selection works as expected.
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension UpdateCollectionViewCell
|
private extension UpdateCollectionViewCell
|
||||||
|
|||||||
@@ -35,17 +35,25 @@
|
|||||||
<constraint firstAttribute="width" secondItem="jg6-wi-ngb" secondAttribute="height" multiplier="1:1" id="vt3-Qt-m21"/>
|
<constraint firstAttribute="width" secondItem="jg6-wi-ngb" secondAttribute="height" multiplier="1:1" id="vt3-Qt-m21"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="2Ii-Hu-4ru">
|
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="2Ii-Hu-4ru">
|
||||||
<rect key="frame" x="76" y="14" width="172" height="37"/>
|
<rect key="frame" x="76" y="14" width="172" height="37"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Short" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qmI-m4-Mra">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="9Zk-Mp-JI7">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="172" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="89.5" height="20.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="Short" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qmI-m4-Mra">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="42.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="4LS-dp-4VA">
|
||||||
|
<rect key="frame" x="48.5" y="0.0" width="41" height="20.5"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xaB-Kc-Par">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xaB-Kc-Par">
|
||||||
<rect key="frame" x="0.0" y="22.5" width="172" height="14.5"/>
|
<rect key="frame" x="0.0" y="22.5" width="57.5" height="14.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -76,7 +84,7 @@
|
|||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Version Notes" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="rNs-2O-k3V" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Version Notes" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rNs-2O-k3V" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="75" y="-10" width="265" height="24.5"/>
|
<rect key="frame" x="75" y="-10" width="265" height="24.5"/>
|
||||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
@@ -113,6 +121,7 @@
|
|||||||
<viewLayoutGuide key="safeArea" id="C6r-zO-INg"/>
|
<viewLayoutGuide key="safeArea" id="C6r-zO-INg"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="appIconImageView" destination="jg6-wi-ngb" id="j83-Dl-GT6"/>
|
<outlet property="appIconImageView" destination="jg6-wi-ngb" id="j83-Dl-GT6"/>
|
||||||
|
<outlet property="betaBadgeView" destination="4LS-dp-4VA" id="Q2Z-AG-Y19"/>
|
||||||
<outlet property="dateLabel" destination="xaB-Kc-Par" id="mfG-3C-r7j"/>
|
<outlet property="dateLabel" destination="xaB-Kc-Par" id="mfG-3C-r7j"/>
|
||||||
<outlet property="nameLabel" destination="qmI-m4-Mra" id="LQz-w7-HNb"/>
|
<outlet property="nameLabel" destination="qmI-m4-Mra" id="LQz-w7-HNb"/>
|
||||||
<outlet property="updateButton" destination="OSL-U2-BKa" id="WbI-96-Nel"/>
|
<outlet property="updateButton" destination="OSL-U2-BKa" id="WbI-96-Nel"/>
|
||||||
@@ -122,4 +131,7 @@
|
|||||||
<point key="canvasLocation" x="618.39999999999998" y="96.251874062968525"/>
|
<point key="canvasLocation" x="618.39999999999998" y="96.251874062968525"/>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</objects>
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="BetaBadge" width="41" height="17"/>
|
||||||
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
27
AltStore/News/NewsCollectionViewCell.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// NewsCollectionViewCell.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/29/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class NewsCollectionViewCell: UICollectionViewCell
|
||||||
|
{
|
||||||
|
@IBOutlet var titleLabel: UILabel!
|
||||||
|
@IBOutlet var captionLabel: UILabel!
|
||||||
|
@IBOutlet var imageView: UIImageView!
|
||||||
|
|
||||||
|
override func awakeFromNib()
|
||||||
|
{
|
||||||
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
self.contentView.layer.cornerRadius = 30
|
||||||
|
self.contentView.clipsToBounds = true
|
||||||
|
|
||||||
|
self.imageView.layer.cornerRadius = 30
|
||||||
|
self.imageView.clipsToBounds = true
|
||||||
|
}
|
||||||
|
}
|
||||||
77
AltStore/News/NewsCollectionViewCell.xib
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina4_7" orientation="portrait">
|
||||||
|
<adaptation id="fullscreen"/>
|
||||||
|
</device>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="wRF-2R-NUG" customClass="NewsCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="335" height="299"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="335" height="299"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xba-Qs-SQo">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="335" height="299"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="tNk-9u-1tk">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="335" height="298.5"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" alignment="top" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="akF-Tr-G5M">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="335" height="98.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Delta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AkN-BE-I1a">
|
||||||
|
<rect key="frame" x="25" y="25" width="54.5" height="26.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" alpha="0.75" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="999" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SHB-kk-YhL">
|
||||||
|
<rect key="frame" x="25" y="61.5" width="35.5" height="17"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="25" left="25" bottom="20" right="25"/>
|
||||||
|
</stackView>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="335" placeholderIntrinsicHeight="200" translatesAutoresizingMaskIntoConstraints="NO" id="l36-Bm-De0">
|
||||||
|
<rect key="frame" x="0.0" y="98.5" width="335" height="200"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" secondItem="l36-Bm-De0" secondAttribute="height" multiplier="67:40" id="QGD-YE-Hw2"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="tNk-9u-1tk" firstAttribute="top" secondItem="Xba-Qs-SQo" secondAttribute="top" id="Dw8-lF-Fzl"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="tNk-9u-1tk" secondAttribute="trailing" id="Zt8-Wa-oB9"/>
|
||||||
|
<constraint firstItem="tNk-9u-1tk" firstAttribute="leading" secondItem="Xba-Qs-SQo" secondAttribute="leading" id="m6p-Ee-dTh"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="tNk-9u-1tk" secondAttribute="bottom" constant="0.5" id="v9g-yC-db9"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Xba-Qs-SQo" firstAttribute="top" secondItem="wRF-2R-NUG" secondAttribute="top" id="0xe-Rt-MhF"/>
|
||||||
|
<constraint firstItem="Xba-Qs-SQo" firstAttribute="leading" secondItem="wRF-2R-NUG" secondAttribute="leading" id="5MO-c0-5rG"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="Xba-Qs-SQo" secondAttribute="trailing" id="DNL-Jj-3By"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="Xba-Qs-SQo" secondAttribute="bottom" id="Ecj-fN-hZv"/>
|
||||||
|
</constraints>
|
||||||
|
<connections>
|
||||||
|
<outlet property="captionLabel" destination="SHB-kk-YhL" id="zY3-qQ-9oY"/>
|
||||||
|
<outlet property="imageView" destination="l36-Bm-De0" id="3do-aQ-5r4"/>
|
||||||
|
<outlet property="titleLabel" destination="AkN-BE-I1a" id="hA2-3O-q5J"/>
|
||||||
|
</connections>
|
||||||
|
</collectionViewCell>
|
||||||
|
</objects>
|
||||||
|
</document>
|
||||||
459
AltStore/News/NewsViewController.swift
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
//
|
||||||
|
// NewsViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/29/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SafariServices
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
import Nuke
|
||||||
|
|
||||||
|
private class AppBannerFooterView: UICollectionReusableView
|
||||||
|
{
|
||||||
|
let bannerView = AppBannerView(frame: .zero)
|
||||||
|
let tapGestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
|
||||||
|
|
||||||
|
override init(frame: CGRect)
|
||||||
|
{
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.bannerView, pinningEdgesWith: UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20))
|
||||||
|
self.addGestureRecognizer(self.tapGestureRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewsViewController: UICollectionViewController
|
||||||
|
{
|
||||||
|
private lazy var dataSource = self.makeDataSource()
|
||||||
|
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
||||||
|
|
||||||
|
private var prototypeCell: NewsCollectionViewCell!
|
||||||
|
|
||||||
|
private var loadingState: LoadingState = .loading {
|
||||||
|
didSet {
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
private var cachedCellSizes = [String: CGSize]()
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.prototypeCell = NewsCollectionViewCell.instantiate(with: NewsCollectionViewCell.nib!)
|
||||||
|
self.prototypeCell.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
self.prototypeCell.contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
self.collectionView.contentInset.bottom = 20
|
||||||
|
|
||||||
|
self.collectionView.dataSource = self.dataSource
|
||||||
|
self.collectionView.prefetchDataSource = self.dataSource
|
||||||
|
|
||||||
|
self.collectionView.register(NewsCollectionViewCell.nib, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||||
|
self.collectionView.register(AppBannerFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "AppBanner")
|
||||||
|
|
||||||
|
self.registerForPreviewing(with: self, sourceView: self.collectionView)
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
self.fetchSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension NewsViewController
|
||||||
|
{
|
||||||
|
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<NewsItem, UIImage>
|
||||||
|
{
|
||||||
|
let fetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NewsItem>
|
||||||
|
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \NewsItem.sortIndex, ascending: false)]
|
||||||
|
|
||||||
|
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(NewsItem.date), cacheName: nil)
|
||||||
|
|
||||||
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<NewsItem, UIImage>(fetchedResultsController: fetchedResultsController)
|
||||||
|
dataSource.proxy = self
|
||||||
|
dataSource.cellConfigurationHandler = { (cell, newsItem, indexPath) in
|
||||||
|
let cell = cell as! NewsCollectionViewCell
|
||||||
|
cell.titleLabel.text = newsItem.title
|
||||||
|
cell.captionLabel.text = newsItem.caption
|
||||||
|
cell.contentView.backgroundColor = newsItem.tintColor
|
||||||
|
|
||||||
|
cell.imageView.image = nil
|
||||||
|
|
||||||
|
if newsItem.imageURL != nil
|
||||||
|
{
|
||||||
|
cell.imageView.isIndicatingActivity = true
|
||||||
|
cell.imageView.isHidden = false
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cell.imageView.isIndicatingActivity = false
|
||||||
|
cell.imageView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSource.prefetchHandler = { (newsItem, indexPath, completionHandler) in
|
||||||
|
guard let imageURL = newsItem.imageURL else { return nil }
|
||||||
|
|
||||||
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
|
ImagePipeline.shared.loadImage(with: imageURL, progress: nil, completion: { (response, error) in
|
||||||
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
|
if let image = response?.image
|
||||||
|
{
|
||||||
|
completionHandler(image, nil)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completionHandler(nil, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||||
|
let cell = cell as! NewsCollectionViewCell
|
||||||
|
cell.imageView.isIndicatingActivity = false
|
||||||
|
cell.imageView.image = image
|
||||||
|
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
print("Error loading image:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.placeholderView = self.placeholderView
|
||||||
|
|
||||||
|
return dataSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchSource()
|
||||||
|
{
|
||||||
|
self.loadingState = .loading
|
||||||
|
|
||||||
|
AppManager.shared.fetchSource() { (result) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let source = try result.get()
|
||||||
|
try source.managedObjectContext?.save()
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.loadingState = .finished(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if self.dataSource.itemCount > 0
|
||||||
|
{
|
||||||
|
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
||||||
|
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loadingState = .finished(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update()
|
||||||
|
{
|
||||||
|
switch self.loadingState
|
||||||
|
{
|
||||||
|
case .loading:
|
||||||
|
self.placeholderView.textLabel.isHidden = true
|
||||||
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
|
self.placeholderView.detailTextLabel.text = NSLocalizedString("Loading...", comment: "")
|
||||||
|
|
||||||
|
self.placeholderView.activityIndicatorView.startAnimating()
|
||||||
|
|
||||||
|
case .finished(.failure(let error)):
|
||||||
|
self.placeholderView.textLabel.isHidden = false
|
||||||
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
|
self.placeholderView.textLabel.text = NSLocalizedString("Unable to Fetch News", comment: "")
|
||||||
|
self.placeholderView.detailTextLabel.text = error.localizedDescription
|
||||||
|
|
||||||
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
|
||||||
|
case .finished(.success):
|
||||||
|
self.placeholderView.textLabel.isHidden = true
|
||||||
|
self.placeholderView.detailTextLabel.isHidden = true
|
||||||
|
|
||||||
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension NewsViewController
|
||||||
|
{
|
||||||
|
@objc func handleTapGesture(_ gestureRecognizer: UITapGestureRecognizer)
|
||||||
|
{
|
||||||
|
guard let footerView = gestureRecognizer.view as? UICollectionReusableView else { return }
|
||||||
|
|
||||||
|
let indexPaths = self.collectionView.indexPathsForVisibleSupplementaryElements(ofKind: UICollectionView.elementKindSectionFooter)
|
||||||
|
|
||||||
|
guard let indexPath = indexPaths.first(where: { (indexPath) -> Bool in
|
||||||
|
let supplementaryView = self.collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionFooter, at: indexPath)
|
||||||
|
return supplementaryView == footerView
|
||||||
|
}) else { return }
|
||||||
|
|
||||||
|
let item = self.dataSource.item(at: indexPath)
|
||||||
|
guard let storeApp = item.storeApp else { return }
|
||||||
|
|
||||||
|
let appViewController = AppViewController.makeAppViewController(app: storeApp)
|
||||||
|
self.navigationController?.pushViewController(appViewController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func performAppAction(_ sender: PillButton)
|
||||||
|
{
|
||||||
|
let point = self.collectionView.convert(sender.center, from: sender.superview)
|
||||||
|
let indexPaths = self.collectionView.indexPathsForVisibleSupplementaryElements(ofKind: UICollectionView.elementKindSectionFooter)
|
||||||
|
|
||||||
|
guard let indexPath = indexPaths.first(where: { (indexPath) -> Bool in
|
||||||
|
let supplementaryView = self.collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionFooter, at: indexPath)
|
||||||
|
return supplementaryView?.frame.contains(point) ?? false
|
||||||
|
}) else { return }
|
||||||
|
|
||||||
|
let app = self.dataSource.item(at: indexPath)
|
||||||
|
guard let storeApp = app.storeApp else { return }
|
||||||
|
|
||||||
|
if let installedApp = app.storeApp?.installedApp
|
||||||
|
{
|
||||||
|
self.open(installedApp)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.install(storeApp, at: indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func install(_ storeApp: StoreApp, at indexPath: IndexPath)
|
||||||
|
{
|
||||||
|
let previousProgress = AppManager.shared.installationProgress(for: storeApp)
|
||||||
|
guard previousProgress == nil else {
|
||||||
|
previousProgress?.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = AppManager.shared.install(storeApp, presentingViewController: self) { (result) in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(OperationError.cancelled): break // Ignore
|
||||||
|
case .failure(let error):
|
||||||
|
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
||||||
|
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2)
|
||||||
|
|
||||||
|
case .success: print("Installed app:", storeApp.bundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.collectionView.reloadSections(IndexSet(integer: indexPath.section))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.collectionView.reloadSections(IndexSet(integer: indexPath.section))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func open(_ installedApp: InstalledApp)
|
||||||
|
{
|
||||||
|
UIApplication.shared.open(installedApp.openAppURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NewsViewController
|
||||||
|
{
|
||||||
|
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||||
|
{
|
||||||
|
let newsItem = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
if let externalURL = newsItem.externalURL
|
||||||
|
{
|
||||||
|
let safariViewController = SFSafariViewController(url: externalURL)
|
||||||
|
safariViewController.preferredControlTintColor = newsItem.tintColor
|
||||||
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
else if let storeApp = newsItem.storeApp
|
||||||
|
{
|
||||||
|
let appViewController = AppViewController.makeAppViewController(app: storeApp)
|
||||||
|
self.navigationController?.pushViewController(appViewController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
|
||||||
|
{
|
||||||
|
let item = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "AppBanner", for: indexPath) as! AppBannerFooterView
|
||||||
|
guard let storeApp = item.storeApp else { return footerView }
|
||||||
|
|
||||||
|
footerView.bannerView.titleLabel.text = storeApp.name
|
||||||
|
footerView.bannerView.subtitleLabel.text = storeApp.developerName
|
||||||
|
footerView.bannerView.tintColor = storeApp.tintColor
|
||||||
|
footerView.bannerView.betaBadgeView.isHidden = !storeApp.isBeta
|
||||||
|
footerView.bannerView.button.addTarget(self, action: #selector(NewsViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
|
footerView.tapGestureRecognizer.addTarget(self, action: #selector(NewsViewController.handleTapGesture(_:)))
|
||||||
|
|
||||||
|
footerView.bannerView.button.isIndicatingActivity = false
|
||||||
|
|
||||||
|
if storeApp.installedApp == nil
|
||||||
|
{
|
||||||
|
footerView.bannerView.button.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal)
|
||||||
|
|
||||||
|
let progress = AppManager.shared.installationProgress(for: storeApp)
|
||||||
|
footerView.bannerView.button.progress = progress
|
||||||
|
footerView.bannerView.button.isInverted = false
|
||||||
|
|
||||||
|
if Date() < storeApp.versionDate
|
||||||
|
{
|
||||||
|
footerView.bannerView.button.countdownDate = storeApp.versionDate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
footerView.bannerView.button.countdownDate = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
footerView.bannerView.button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
||||||
|
footerView.bannerView.button.progress = nil
|
||||||
|
footerView.bannerView.button.isInverted = true
|
||||||
|
footerView.bannerView.button.countdownDate = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Nuke.loadImage(with: storeApp.iconURL, into: footerView.bannerView.iconImageView)
|
||||||
|
|
||||||
|
return footerView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NewsViewController: UICollectionViewDelegateFlowLayout
|
||||||
|
{
|
||||||
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||||
|
{
|
||||||
|
let padding = 40 as CGFloat
|
||||||
|
let width = collectionView.bounds.width - padding
|
||||||
|
|
||||||
|
let item = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
if let previousSize = self.cachedCellSizes[item.identifier]
|
||||||
|
{
|
||||||
|
return previousSize
|
||||||
|
}
|
||||||
|
|
||||||
|
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: width)
|
||||||
|
NSLayoutConstraint.activate([widthConstraint])
|
||||||
|
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||||
|
|
||||||
|
self.dataSource.cellConfigurationHandler(self.prototypeCell, item, indexPath)
|
||||||
|
|
||||||
|
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||||
|
self.cachedCellSizes[item.identifier] = size
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
|
||||||
|
{
|
||||||
|
let item = self.dataSource.item(at: IndexPath(row: 0, section: section))
|
||||||
|
|
||||||
|
if item.storeApp != nil
|
||||||
|
{
|
||||||
|
return CGSize(width: 88, height: 88)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
|
||||||
|
{
|
||||||
|
var insets = UIEdgeInsets(top: 30, left: 20, bottom: 13, right: 20)
|
||||||
|
|
||||||
|
if section == 0
|
||||||
|
{
|
||||||
|
insets.top = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
return insets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NewsViewController: UIViewControllerPreviewingDelegate
|
||||||
|
{
|
||||||
|
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?
|
||||||
|
{
|
||||||
|
if let indexPath = self.collectionView.indexPathForItem(at: location), let cell = self.collectionView.cellForItem(at: indexPath)
|
||||||
|
{
|
||||||
|
// Previewing news item.
|
||||||
|
|
||||||
|
previewingContext.sourceRect = cell.frame
|
||||||
|
|
||||||
|
let newsItem = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
if let externalURL = newsItem.externalURL
|
||||||
|
{
|
||||||
|
let safariViewController = SFSafariViewController(url: externalURL)
|
||||||
|
safariViewController.preferredControlTintColor = newsItem.tintColor
|
||||||
|
return safariViewController
|
||||||
|
}
|
||||||
|
else if let storeApp = newsItem.storeApp
|
||||||
|
{
|
||||||
|
let appViewController = AppViewController.makeAppViewController(app: storeApp)
|
||||||
|
return appViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Previewing app banner (or nothing).
|
||||||
|
|
||||||
|
let indexPaths = self.collectionView.indexPathsForVisibleSupplementaryElements(ofKind: UICollectionView.elementKindSectionFooter)
|
||||||
|
|
||||||
|
guard let indexPath = indexPaths.first(where: { (indexPath) -> Bool in
|
||||||
|
let layoutAttributes = self.collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionFooter, at: indexPath)
|
||||||
|
return layoutAttributes?.frame.contains(location) ?? false
|
||||||
|
}) else { return nil }
|
||||||
|
|
||||||
|
guard let layoutAttributes = self.collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionFooter, at: indexPath) else { return nil }
|
||||||
|
previewingContext.sourceRect = layoutAttributes.frame
|
||||||
|
|
||||||
|
let item = self.dataSource.item(at: indexPath)
|
||||||
|
guard let storeApp = item.storeApp else { return nil }
|
||||||
|
|
||||||
|
let appViewController = AppViewController.makeAppViewController(app: storeApp)
|
||||||
|
return appViewController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController)
|
||||||
|
{
|
||||||
|
if let safariViewController = viewControllerToCommit as? SFSafariViewController
|
||||||
|
{
|
||||||
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.navigationController?.pushViewController(viewControllerToCommit, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,10 +34,15 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
|
|||||||
{
|
{
|
||||||
private weak var presentingViewController: UIViewController?
|
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 lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
|
||||||
|
|
||||||
private var appleIDPassword: String?
|
private var appleIDPassword: String?
|
||||||
|
private var shouldShowInstructions = false
|
||||||
|
|
||||||
init(presentingViewController: UIViewController?)
|
init(presentingViewController: UIViewController?)
|
||||||
{
|
{
|
||||||
@@ -82,6 +87,7 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
|
|||||||
case .success(let certificate):
|
case .success(let certificate):
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
|
self.showInstructionsIfNecessary() { (didShowInstructions) in
|
||||||
let signer = ALTSigner(team: team, certificate: certificate)
|
let signer = ALTSigner(team: team, certificate: certificate)
|
||||||
self.finish(.success(signer))
|
self.finish(.success(signer))
|
||||||
}
|
}
|
||||||
@@ -91,6 +97,7 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func finish(_ result: Result<ALTSigner, Error>)
|
override func finish(_ result: Result<ALTSigner, Error>)
|
||||||
{
|
{
|
||||||
@@ -161,7 +168,7 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
guard let presentingViewController = self.presentingViewController else { return false }
|
guard let presentingViewController = self.presentingViewController else { return false }
|
||||||
|
|
||||||
self.navigationController.view.tintColor = .altPurple
|
self.navigationController.view.tintColor = .white
|
||||||
|
|
||||||
if self.navigationController.viewControllers.isEmpty
|
if self.navigationController.viewControllers.isEmpty
|
||||||
{
|
{
|
||||||
@@ -191,8 +198,11 @@ private extension AuthenticationOperation
|
|||||||
authenticationViewController.authenticationHandler = { (result) in
|
authenticationViewController.authenticationHandler = { (result) in
|
||||||
if let (account, password) = result
|
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))
|
completionHandler(.success(account))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -242,29 +252,21 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
func selectTeam(from teams: [ALTTeam])
|
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))
|
return completionHandler(.success(team))
|
||||||
}
|
}
|
||||||
|
else if let team = teams.first(where: { $0.type == .individual })
|
||||||
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))
|
return completionHandler(.success(team))
|
||||||
|
}
|
||||||
|
else if let team = teams.first
|
||||||
|
{
|
||||||
|
return completionHandler(.success(team))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
completionHandler(.failure(OperationError.cancelled))
|
return completionHandler(.failure(AuthenticationError.noTeam))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.present(selectTeamViewController)
|
|
||||||
{
|
|
||||||
completionHandler(.failure(AuthenticationError.noTeam))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +275,6 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): completionHandler(.failure(error))
|
||||||
case .success(let teams):
|
case .success(let teams):
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
|
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
|
||||||
{
|
{
|
||||||
@@ -326,8 +327,8 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func replaceCertificate(from certificates: [ALTCertificate])
|
func replaceCertificate(from certificates: [ALTCertificate])
|
||||||
{
|
{
|
||||||
if let certificate = certificates.first, certificates.count == 1
|
guard let certificate = certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
|
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
|
||||||
if let error = error, !success
|
if let error = error, !success
|
||||||
{
|
{
|
||||||
@@ -338,30 +339,6 @@ private extension AuthenticationOperation
|
|||||||
requestCertificate()
|
requestCertificate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
completionHandler(.failure(AuthenticationError.noCertificate))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
|
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import AltSign
|
|||||||
class DownloadAppOperation: ResultOperation<ALTApplication>
|
class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||||
{
|
{
|
||||||
let app: AppProtocol
|
let app: AppProtocol
|
||||||
|
let context: AppOperationContext
|
||||||
|
|
||||||
private let bundleIdentifier: String
|
private let bundleIdentifier: String
|
||||||
private let sourceURL: URL
|
private let sourceURL: URL
|
||||||
@@ -22,9 +23,11 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
|
|
||||||
private let session = URLSession(configuration: .default)
|
private let session = URLSession(configuration: .default)
|
||||||
|
|
||||||
init(app: AppProtocol)
|
init(app: AppProtocol, context: AppOperationContext)
|
||||||
{
|
{
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.context = context
|
||||||
|
|
||||||
self.bundleIdentifier = app.bundleIdentifier
|
self.bundleIdentifier = app.bundleIdentifier
|
||||||
self.sourceURL = app.url
|
self.sourceURL = app.url
|
||||||
self.destinationURL = InstalledApp.fileURL(for: app)
|
self.destinationURL = InstalledApp.fileURL(for: app)
|
||||||
@@ -38,6 +41,12 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
{
|
{
|
||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
|
if let error = self.context.error
|
||||||
|
{
|
||||||
|
self.finish(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
print("Downloading App:", self.bundleIdentifier)
|
print("Downloading App:", self.bundleIdentifier)
|
||||||
|
|
||||||
func finishOperation(_ result: Result<URL, Error>)
|
func finishOperation(_ result: Result<URL, Error>)
|
||||||
|
|||||||
@@ -14,17 +14,22 @@ class FetchSourceOperation: ResultOperation<Source>
|
|||||||
{
|
{
|
||||||
let sourceURL: URL
|
let sourceURL: URL
|
||||||
|
|
||||||
private let session = URLSession(configuration: .default)
|
private let session: URLSession
|
||||||
|
|
||||||
private lazy var dateFormatter: DateFormatter = {
|
private lazy var dateFormatter: ISO8601DateFormatter = {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = ISO8601DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd"
|
|
||||||
return dateFormatter
|
return dateFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(sourceURL: URL)
|
init(sourceURL: URL)
|
||||||
{
|
{
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
|
|
||||||
|
let configuration = URLSessionConfiguration.default
|
||||||
|
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||||
|
configuration.urlCache = nil
|
||||||
|
|
||||||
|
self.session = URLSession(configuration: configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main()
|
override func main()
|
||||||
@@ -38,7 +43,27 @@ 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 = JSONDecoder()
|
||||||
decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
|
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
let text = try container.decode(String.self)
|
||||||
|
|
||||||
|
// Full ISO8601 Format.
|
||||||
|
self.dateFormatter.formatOptions = [.withFullDate, .withFullTime, .withTimeZone]
|
||||||
|
if let date = self.dateFormatter.date(from: text)
|
||||||
|
{
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just date portion of ISO8601.
|
||||||
|
self.dateFormatter.formatOptions = [.withFullDate]
|
||||||
|
if let date = self.dateFormatter.date(from: text)
|
||||||
|
{
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date is in invalid format.")
|
||||||
|
})
|
||||||
|
|
||||||
decoder.managedObjectContext = context
|
decoder.managedObjectContext = context
|
||||||
|
|
||||||
let source = try decoder.decode(Source.self, from: data)
|
let source = try decoder.decode(Source.self, from: data)
|
||||||
|
|||||||
51
AltStore/Operations/FindServerOperation.swift
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// FindServerOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/8/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
@objc(FindServerOperation)
|
||||||
|
class FindServerOperation: ResultOperation<Server>
|
||||||
|
{
|
||||||
|
let group: OperationGroup
|
||||||
|
|
||||||
|
init(group: OperationGroup)
|
||||||
|
{
|
||||||
|
self.group = group
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
if let error = self.group.error
|
||||||
|
{
|
||||||
|
self.finish(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
|
||||||
|
{
|
||||||
|
// Preferred server.
|
||||||
|
self.finish(.success(server))
|
||||||
|
}
|
||||||
|
else if let server = ServerManager.shared.discoveredServers.first
|
||||||
|
{
|
||||||
|
// Any available server.
|
||||||
|
self.finish(.success(server))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No servers.
|
||||||
|
self.finish(.failure(ConnectionError.serverNotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -395,10 +395,11 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
|
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
|
||||||
|
|
||||||
if self.context.bundleIdentifier == StoreApp.altstoreAppID
|
if self.context.bundleIdentifier == StoreApp.altstoreAppID || self.context.bundleIdentifier == StoreApp.alternativeAltStoreAppID
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
|
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare app
|
// Prepare app
|
||||||
|
|||||||
27
AltStore/Patreon/Benefit.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Benefit.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/21/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
struct BenefitResponse: Decodable
|
||||||
|
{
|
||||||
|
var id: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Benefit: Hashable
|
||||||
|
{
|
||||||
|
var type: ALTPatreonBenefitType
|
||||||
|
|
||||||
|
init(response: PatreonAPI.BenefitResponse)
|
||||||
|
{
|
||||||
|
self.type = ALTPatreonBenefitType(response.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
AltStore/Patreon/Campaign.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Campaign.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/21/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
struct CampaignResponse: Decodable
|
||||||
|
{
|
||||||
|
var id: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Campaign
|
||||||
|
{
|
||||||
|
var identifier: String
|
||||||
|
|
||||||
|
init(response: PatreonAPI.CampaignResponse)
|
||||||
|
{
|
||||||
|
self.identifier = response.id
|
||||||
|
}
|
||||||
|
}
|
||||||
385
AltStore/Patreon/PatreonAPI.swift
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
//
|
||||||
|
// PatreonAPI.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/20/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AuthenticationServices
|
||||||
|
|
||||||
|
private let clientID = "ZMx0EGUWe4TVWYXNZZwK_fbIK5jHFVWoUf1Qb-sqNXmT-YzAGwDPxxq7ak3_W5Q2"
|
||||||
|
private let clientSecret = "1hktsZB89QyN69cB4R0tu55R4TCPQGXxvebYUUh7Y-5TLSnRswuxs6OUjdJ74IJt"
|
||||||
|
private let creatorAccessToken = "mBh0yyK40Ibjzwb_cYeKIuzq8nNFBdEIlNPfgAQlhcU"
|
||||||
|
|
||||||
|
private let campaignID = "2863968"
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
enum Error: LocalizedError
|
||||||
|
{
|
||||||
|
case unknown
|
||||||
|
case notAuthenticated
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .unknown: return NSLocalizedString("An unknown error occurred.", comment: "")
|
||||||
|
case .notAuthenticated: return NSLocalizedString("No connected Patreon account.", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthorizationType
|
||||||
|
{
|
||||||
|
case none
|
||||||
|
case user
|
||||||
|
case creator
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnyResponse: Decodable
|
||||||
|
{
|
||||||
|
case tier(TierResponse)
|
||||||
|
case benefit(BenefitResponse)
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case type
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
let type = try container.decode(String.self, forKey: .type)
|
||||||
|
switch type
|
||||||
|
{
|
||||||
|
case "tier":
|
||||||
|
let tier = try TierResponse(from: decoder)
|
||||||
|
self = .tier(tier)
|
||||||
|
|
||||||
|
case "benefit":
|
||||||
|
let benefit = try BenefitResponse(from: decoder)
|
||||||
|
self = .benefit(benefit)
|
||||||
|
|
||||||
|
default: throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unrecognized Patreon response type.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PatreonAPI
|
||||||
|
{
|
||||||
|
static let shared = PatreonAPI()
|
||||||
|
|
||||||
|
var isAuthenticated: Bool {
|
||||||
|
return Keychain.shared.patreonAccessToken != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private var authenticationSession: ASWebAuthenticationSession?
|
||||||
|
|
||||||
|
private let session = URLSession(configuration: .ephemeral)
|
||||||
|
private let baseURL = URL(string: "https://www.patreon.com/")!
|
||||||
|
|
||||||
|
private init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
func authenticate(completion: @escaping (Result<PatreonAccount, Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
var components = URLComponents(string: "/oauth2/authorize")!
|
||||||
|
components.queryItems = [URLQueryItem(name: "response_type", value: "code"),
|
||||||
|
URLQueryItem(name: "client_id", value: clientID),
|
||||||
|
URLQueryItem(name: "redirect_uri", value: "https://rileytestut.com/patreon/altstore")]
|
||||||
|
|
||||||
|
let requestURL = components.url(relativeTo: self.baseURL)!
|
||||||
|
|
||||||
|
self.authenticationSession = ASWebAuthenticationSession(url: requestURL, callbackURLScheme: "altstore") { (callbackURL, error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let callbackURL = try Result(callbackURL, error).get()
|
||||||
|
|
||||||
|
guard
|
||||||
|
let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
|
||||||
|
let codeQueryItem = components.queryItems?.first(where: { $0.name == "code" }),
|
||||||
|
let code = codeQueryItem.value
|
||||||
|
else { throw Error.unknown }
|
||||||
|
|
||||||
|
self.fetchAccessToken(oauthCode: code) { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success(let accessToken, let refreshToken):
|
||||||
|
Keychain.shared.patreonAccessToken = accessToken
|
||||||
|
Keychain.shared.patreonRefreshToken = refreshToken
|
||||||
|
|
||||||
|
self.fetchAccount(completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.authenticationSession?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAccount(completion: @escaping (Result<PatreonAccount, Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
var components = URLComponents(string: "/api/oauth2/v2/identity")!
|
||||||
|
components.queryItems = [URLQueryItem(name: "include", value: "memberships"),
|
||||||
|
URLQueryItem(name: "fields[user]", value: "first_name,full_name"),
|
||||||
|
URLQueryItem(name: "fields[member]", value: "full_name,patron_status")]
|
||||||
|
|
||||||
|
let requestURL = components.url(relativeTo: self.baseURL)!
|
||||||
|
let request = URLRequest(url: requestURL)
|
||||||
|
|
||||||
|
self.send(request, authorizationType: .user) { (result: Result<AccountResponse, Swift.Error>) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(Error.notAuthenticated):
|
||||||
|
self.signOut() { (result) in
|
||||||
|
completion(.failure(Error.notAuthenticated))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success(let response):
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
let account = PatreonAccount(response: response, context: context)
|
||||||
|
completion(.success(account))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchPatrons(completion: @escaping (Result<[Patron], Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
var components = URLComponents(string: "/api/oauth2/v2/campaigns/\(campaignID)/members")!
|
||||||
|
components.queryItems = [URLQueryItem(name: "include", value: "currently_entitled_tiers,currently_entitled_tiers.benefits"),
|
||||||
|
URLQueryItem(name: "fields[tier]", value: "title"),
|
||||||
|
URLQueryItem(name: "fields[member]", value: "full_name,patron_status")]
|
||||||
|
|
||||||
|
let requestURL = components.url(relativeTo: self.baseURL)!
|
||||||
|
|
||||||
|
struct Response: Decodable
|
||||||
|
{
|
||||||
|
var data: [PatronResponse]
|
||||||
|
var included: [AnyResponse]
|
||||||
|
var links: [String: URL]?
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPatrons = [Patron]()
|
||||||
|
|
||||||
|
func fetchPatrons(url: URL)
|
||||||
|
{
|
||||||
|
let request = URLRequest(url: url)
|
||||||
|
|
||||||
|
self.send(request, authorizationType: .creator) { (result: Result<Response, Swift.Error>) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success(let response):
|
||||||
|
let tiers = response.included.compactMap { (response) -> Tier? in
|
||||||
|
switch response
|
||||||
|
{
|
||||||
|
case .tier(let tierResponse): return Tier(response: tierResponse)
|
||||||
|
case .benefit: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tiersByIdentifier = Dictionary(tiers.map { ($0.identifier, $0) }, uniquingKeysWith: { (a, b) in return a })
|
||||||
|
|
||||||
|
let patrons = response.data.map { (response) -> Patron in
|
||||||
|
let patron = Patron(response: response)
|
||||||
|
|
||||||
|
for tierID in response.relationships?.currently_entitled_tiers.data ?? []
|
||||||
|
{
|
||||||
|
guard let tier = tiersByIdentifier[tierID.id] else { continue }
|
||||||
|
patron.benefits.formUnion(tier.benefits)
|
||||||
|
}
|
||||||
|
|
||||||
|
return patron
|
||||||
|
}.filter { $0.benefits.contains(where: { $0.type == .credits }) }
|
||||||
|
|
||||||
|
allPatrons.append(contentsOf: patrons)
|
||||||
|
|
||||||
|
if let nextURL = response.links?["next"]
|
||||||
|
{
|
||||||
|
fetchPatrons(url: nextURL)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(allPatrons))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPatrons(url: requestURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func signOut(completion: @escaping (Result<Void, Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
let accounts = PatreonAccount.all(in: context)
|
||||||
|
accounts.forEach(context.delete(_:))
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try context.save()
|
||||||
|
|
||||||
|
Keychain.shared.patreonAccessToken = nil
|
||||||
|
Keychain.shared.patreonRefreshToken = nil
|
||||||
|
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
func refreshPatreonAccount()
|
||||||
|
{
|
||||||
|
guard PatreonAPI.shared.isAuthenticated else { return }
|
||||||
|
|
||||||
|
PatreonAPI.shared.fetchAccount { (result: Result<PatreonAccount, Swift.Error>) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let account = try result.get()
|
||||||
|
try account.managedObjectContext?.save()
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to fetch Patreon account.", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension PatreonAPI
|
||||||
|
{
|
||||||
|
func fetchAccessToken(oauthCode: String, completion: @escaping (Result<(String, String), Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
let encodedRedirectURI = ("https://rileytestut.com/patreon/altstore" as NSString).addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
|
||||||
|
let encodedOauthCode = (oauthCode as NSString).addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
|
||||||
|
|
||||||
|
let body = "code=\(encodedOauthCode)&grant_type=authorization_code&client_id=\(clientID)&client_secret=\(clientSecret)&redirect_uri=\(encodedRedirectURI)"
|
||||||
|
|
||||||
|
let requestURL = URL(string: "/api/oauth2/token", relativeTo: self.baseURL)!
|
||||||
|
|
||||||
|
var request = URLRequest(url: requestURL)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.httpBody = body.data(using: .utf8)
|
||||||
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
|
struct Response: Decodable
|
||||||
|
{
|
||||||
|
var access_token: String
|
||||||
|
var refresh_token: String
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send(request, authorizationType: .none) { (result: Result<Response, Swift.Error>) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success(let response): completion(.success((response.access_token, response.refresh_token)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshAccessToken(completion: @escaping (Result<Void, Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
guard let refreshToken = Keychain.shared.patreonRefreshToken else { return }
|
||||||
|
|
||||||
|
var components = URLComponents(string: "/api/oauth2/token")!
|
||||||
|
components.queryItems = [URLQueryItem(name: "grant_type", value: "refresh_token"),
|
||||||
|
URLQueryItem(name: "refresh_token", value: refreshToken),
|
||||||
|
URLQueryItem(name: "client_id", value: clientID),
|
||||||
|
URLQueryItem(name: "client_secret", value: clientSecret)]
|
||||||
|
|
||||||
|
let requestURL = components.url(relativeTo: self.baseURL)!
|
||||||
|
|
||||||
|
var request = URLRequest(url: requestURL)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
|
||||||
|
struct Response: Decodable
|
||||||
|
{
|
||||||
|
var access_token: String
|
||||||
|
var refresh_token: String
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send(request, authorizationType: .none) { (result: Result<Response, Swift.Error>) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success(let response):
|
||||||
|
Keychain.shared.patreonAccessToken = response.access_token
|
||||||
|
Keychain.shared.patreonRefreshToken = response.refresh_token
|
||||||
|
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func send<ResponseType: Decodable>(_ request: URLRequest, authorizationType: AuthorizationType, completion: @escaping (Result<ResponseType, Swift.Error>) -> Void)
|
||||||
|
{
|
||||||
|
var request = request
|
||||||
|
|
||||||
|
switch authorizationType
|
||||||
|
{
|
||||||
|
case .none: break
|
||||||
|
case .creator:
|
||||||
|
request.setValue("Bearer " + creatorAccessToken, forHTTPHeaderField: "Authorization")
|
||||||
|
case .user:
|
||||||
|
guard let accessToken = Keychain.shared.patreonAccessToken else { return completion(.failure(Error.notAuthenticated)) }
|
||||||
|
request.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = self.session.dataTask(with: request) { (data, response, error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let data = try Result(data, error).get()
|
||||||
|
|
||||||
|
if let response = response as? HTTPURLResponse, response.statusCode == 401
|
||||||
|
{
|
||||||
|
if authorizationType == .user
|
||||||
|
{
|
||||||
|
self.refreshAccessToken() { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success: self.send(request, authorizationType: authorizationType, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.failure(Error.notAuthenticated))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = try JSONDecoder().decode(ResponseType.self, from: data)
|
||||||
|
completion(.success(response))
|
||||||
|
}
|
||||||
|
catch let error
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
78
AltStore/Patreon/Patron.swift
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// Patron.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/21/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
struct PatronResponse: Decodable
|
||||||
|
{
|
||||||
|
struct Attributes: Decodable
|
||||||
|
{
|
||||||
|
var full_name: String
|
||||||
|
var patron_status: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Relationships: Decodable
|
||||||
|
{
|
||||||
|
struct Tiers: Decodable
|
||||||
|
{
|
||||||
|
struct TierID: Decodable
|
||||||
|
{
|
||||||
|
var id: String
|
||||||
|
var type: String
|
||||||
|
}
|
||||||
|
|
||||||
|
var data: [TierID]
|
||||||
|
}
|
||||||
|
|
||||||
|
var currently_entitled_tiers: Tiers
|
||||||
|
}
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var attributes: Attributes
|
||||||
|
|
||||||
|
var relationships: Relationships?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Patron
|
||||||
|
{
|
||||||
|
enum Status: String, Decodable
|
||||||
|
{
|
||||||
|
case active = "active_patron"
|
||||||
|
case declined = "declined_patron"
|
||||||
|
case former = "former_patron"
|
||||||
|
case unknown = "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Patron
|
||||||
|
{
|
||||||
|
var name: String
|
||||||
|
var identifier: String
|
||||||
|
|
||||||
|
var status: Status
|
||||||
|
|
||||||
|
var benefits: Set<Benefit> = []
|
||||||
|
|
||||||
|
init(response: PatreonAPI.PatronResponse)
|
||||||
|
{
|
||||||
|
self.name = response.attributes.full_name
|
||||||
|
self.identifier = response.id
|
||||||
|
|
||||||
|
if let status = response.attributes.patron_status
|
||||||
|
{
|
||||||
|
self.status = Status(rawValue: status) ?? .unknown
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.status = .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
AltStore/Patreon/Tier.swift
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// Tier.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/21/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
struct TierResponse: Decodable
|
||||||
|
{
|
||||||
|
struct Attributes: Decodable
|
||||||
|
{
|
||||||
|
var title: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Relationships: Decodable
|
||||||
|
{
|
||||||
|
struct Benefits: Decodable
|
||||||
|
{
|
||||||
|
var data: [BenefitResponse]
|
||||||
|
}
|
||||||
|
|
||||||
|
var benefits: Benefits
|
||||||
|
}
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var attributes: Attributes
|
||||||
|
|
||||||
|
var relationships: Relationships
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Tier
|
||||||
|
{
|
||||||
|
var name: String
|
||||||
|
var identifier: String
|
||||||
|
|
||||||
|
var benefits: [Benefit] = []
|
||||||
|
|
||||||
|
init(response: PatreonAPI.TierResponse)
|
||||||
|
{
|
||||||
|
self.name = response.attributes.title
|
||||||
|
self.identifier = response.id
|
||||||
|
self.benefits = response.relationships.benefits.data.map(Benefit.init(response:))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "AltStore",
|
|
||||||
"identifier": "com.rileytestut.AltStore",
|
|
||||||
"sourceURL": "https://www.dropbox.com/s/6qi1vt6hsi88lv6/Apps-Dev.json?dl=1",
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"name": "AltStore",
|
|
||||||
"bundleIdentifier": "com.rileytestut.AltStore",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"version": "0.2",
|
|
||||||
"versionDate": "2019-07-31",
|
|
||||||
"versionDescription": "AltStore has been updated with bug fixes and improvements and other nice goodies for you to enjoy.",
|
|
||||||
"downloadURL": "https://www.dropbox.com/s/w1gn9iztlqvltyp/AltStore.ipa?dl=1",
|
|
||||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
|
||||||
"iconName": "AppIcon",
|
|
||||||
"size": 10010524,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "background-fetch",
|
|
||||||
"usageDescription": "AltStore periodically refreshes apps in the background to prevent them from expiring."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "background-audio",
|
|
||||||
"usageDescription": "Allows AltStore to run longer than 30 seconds when refreshing apps in background."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Delta",
|
|
||||||
"bundleIdentifier": "com.rileytestut.Delta",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"subtitle": "Classic games in your pocket.",
|
|
||||||
"version": "0.82",
|
|
||||||
"versionDate": "2019-07-31",
|
|
||||||
"versionDescription": "Finally, after almost 5 years of waiting, Delta is out of beta and ready for everyone to enjoy!\n\nCurrently supports NES, SNES, N64, GB(C), and GBA games, with more to come in the future.",
|
|
||||||
"downloadURL": "https://www.dropbox.com/s/31i4hcqnorucrxi/Delta.ipa?dl=1",
|
|
||||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
|
||||||
"iconName": "DeltaIcon",
|
|
||||||
"tintColor": "8A28F7",
|
|
||||||
"size": 26908804,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "photos",
|
|
||||||
"usageDescription": "Allows Delta to use images from your Photo Library as game artwork."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"screenshotNames": [
|
|
||||||
"Delta1",
|
|
||||||
"Delta2",
|
|
||||||
"Delta3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Clip",
|
|
||||||
"bundleIdentifier": "com.rileytestut.Clip",
|
|
||||||
"subtitle": "Manage your clipboard history with ease.",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"version": "0.2",
|
|
||||||
"versionDate": "2019-07-31",
|
|
||||||
"versionDescription": "Bug fixes and improvements.",
|
|
||||||
"downloadURL": "https://www.dropbox.com/s/x11b4m8jvmz6tpl/Clip.ipa?dl=1",
|
|
||||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
|
||||||
"iconName": "ClipboardIcon",
|
|
||||||
"tintColor": "EC008C",
|
|
||||||
"size": 438855,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "background-audio",
|
|
||||||
"usageDescription": "Allows Clip to continuously monitor your clipboard in the background."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"screenshotNames": [
|
|
||||||
"Clip1",
|
|
||||||
"Clip2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 110 KiB |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "DeltaIcon.png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 65 KiB |
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "IMG_4222.png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.9 MiB |
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "IMG_4221.png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.2 MiB |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -33,13 +33,13 @@
|
|||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size" : "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Asset 1@120.png",
|
"filename" : "Group 23_120.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size" : "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Asset 1@180.png",
|
"filename" : "Group 23_180.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
{
|
{
|
||||||
"size" : "1024x1024",
|
"size" : "1024x1024",
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"filename" : "Asset 1@1024.png",
|
"filename" : "Group 23.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 21 KiB |
BIN
AltStore/Resources/Assets.xcassets/BetaBadge.imageset/BETA.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |