mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Caches Friend Zone patrons to offset slow loading time
The Patreon API doesn’t have a way to fetch just the patrons belonging to our Friend Zone tier. Instead, we need to fetch ALL patrons (including inactive ones) and filter out those not in the tier. This is very inefficient, and takes over a minute to complete as of April 14, 2022, due to the number of patrons we have. We can’t do much to change this, but AltStore will now at least cache the fetched patrons with Core Data. Additionally, AltStore will only perform this long fetch whenever the Friend Zone list actually changes, rather than every time the Patreon screen appears.
This commit is contained in:
@@ -358,6 +358,7 @@
|
|||||||
D58D5F2E26DFE68E00E55E38 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D58D5F2D26DFE68E00E55E38 /* LaunchAtLogin */; };
|
D58D5F2E26DFE68E00E55E38 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D58D5F2D26DFE68E00E55E38 /* LaunchAtLogin */; };
|
||||||
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
||||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
||||||
|
D5DAE0962804DF430034D8D4 /* UpdatePatronsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */; };
|
||||||
D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */; };
|
D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */; };
|
||||||
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F2F6A82720B7C20081CCF5 /* PatchViewController.swift */; };
|
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F2F6A82720B7C20081CCF5 /* PatchViewController.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@@ -819,6 +820,7 @@
|
|||||||
D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Vibration.swift"; sourceTree = "<group>"; };
|
D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Vibration.swift"; sourceTree = "<group>"; };
|
||||||
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
||||||
D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotProcessor.swift; sourceTree = "<group>"; };
|
D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatePatronsOperation.swift; sourceTree = "<group>"; };
|
||||||
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchTrustedSourcesOperation.swift; sourceTree = "<group>"; };
|
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchTrustedSourcesOperation.swift; sourceTree = "<group>"; };
|
||||||
D5F2F6A82720B7C20081CCF5 /* PatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchViewController.swift; sourceTree = "<group>"; };
|
D5F2F6A82720B7C20081CCF5 /* PatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchViewController.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>"; };
|
||||||
@@ -1677,6 +1679,7 @@
|
|||||||
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */,
|
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */,
|
||||||
BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */,
|
BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */,
|
||||||
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
||||||
|
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */,
|
||||||
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
||||||
BF7B44062725A4B8005288A4 /* Patch App */,
|
BF7B44062725A4B8005288A4 /* Patch App */,
|
||||||
);
|
);
|
||||||
@@ -2612,6 +2615,7 @@
|
|||||||
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
||||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
||||||
|
D5DAE0962804DF430034D8D4 /* UpdatePatronsOperation.swift in Sources */,
|
||||||
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
|
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
|
||||||
BFF435D8255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift in Sources */,
|
BFF435D8255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift in Sources */,
|
||||||
BF4B78FE24B3D1DB008AB4AC /* SceneDelegate.swift in Sources */,
|
BF4B78FE24B3D1DB008AB4AC /* SceneDelegate.swift in Sources */,
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ extension LaunchViewController
|
|||||||
guard !self.didFinishLaunching else { return }
|
guard !self.didFinishLaunching else { return }
|
||||||
|
|
||||||
AppManager.shared.update()
|
AppManager.shared.update()
|
||||||
|
AppManager.shared.updatePatronsIfNeeded()
|
||||||
PatreonAPI.shared.refreshPatreonAccount()
|
PatreonAPI.shared.refreshPatreonAccount()
|
||||||
|
|
||||||
// Add view controller as child (rather than presenting modally)
|
// Add view controller as child (rather than presenting modally)
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import Roxas
|
|||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
{
|
{
|
||||||
static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource")
|
static let didFetchSourceNotification = Notification.Name("io.altstore.AppManager.didFetchSource")
|
||||||
|
static let didUpdatePatronsNotification = Notification.Name("io.altstore.AppManager.didUpdatePatrons")
|
||||||
|
|
||||||
static let expirationWarningNotificationID = "altstore-expiration-warning"
|
static let expirationWarningNotificationID = "altstore-expiration-warning"
|
||||||
static let enableJITResultNotificationID = "altstore-enable-jit"
|
static let enableJITResultNotificationID = "altstore-enable-jit"
|
||||||
@@ -48,6 +49,8 @@ class AppManager
|
|||||||
@available(iOS 13, *)
|
@available(iOS 13, *)
|
||||||
private(set) lazy var publisher: AppManagerPublisher = AppManagerPublisher()
|
private(set) lazy var publisher: AppManagerPublisher = AppManagerPublisher()
|
||||||
|
|
||||||
|
private(set) var updatePatronsResult: Result<Void, Error>?
|
||||||
|
|
||||||
private let operationQueue = OperationQueue()
|
private let operationQueue = OperationQueue()
|
||||||
private let serialOperationQueue = OperationQueue()
|
private let serialOperationQueue = OperationQueue()
|
||||||
|
|
||||||
@@ -412,6 +415,34 @@ extension AppManager
|
|||||||
return fetchTrustedSourcesOperation
|
return fetchTrustedSourcesOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updatePatronsIfNeeded()
|
||||||
|
{
|
||||||
|
guard self.operationQueue.operations.allSatisfy({ !($0 is UpdatePatronsOperation) }) else {
|
||||||
|
// There's already an UpdatePatronsOperation running.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updatePatronsResult = nil
|
||||||
|
|
||||||
|
let updatePatronsOperation = UpdatePatronsOperation()
|
||||||
|
updatePatronsOperation.resultHandler = { (result) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try result.get()
|
||||||
|
self.updatePatronsResult = .success(())
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Error updating Friend Zone Patrons:", error)
|
||||||
|
self.updatePatronsResult = .failure(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: AppManager.didUpdatePatronsNotification, object: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run([updatePatronsOperation], context: nil)
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func install<T: AppProtocol>(_ app: T, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> RefreshGroup
|
func install<T: AppProtocol>(_ app: T, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> RefreshGroup
|
||||||
{
|
{
|
||||||
|
|||||||
107
AltStore/Operations/UpdatePatronsOperation.swift
Normal file
107
AltStore/Operations/UpdatePatronsOperation.swift
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// UpdatePatronsOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 4/11/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
private extension URL
|
||||||
|
{
|
||||||
|
#if STAGING
|
||||||
|
static let patreonInfo = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore/patreon.json")!
|
||||||
|
#else
|
||||||
|
static let patreonInfo = URL(string: "https://cdn.altstore.io/file/altstore/altstore/patreon.json")!
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UpdatePatronsOperation
|
||||||
|
{
|
||||||
|
private struct Response: Decodable
|
||||||
|
{
|
||||||
|
var version: Int
|
||||||
|
var accessToken: String
|
||||||
|
var refreshID: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdatePatronsOperation: ResultOperation<Void>
|
||||||
|
{
|
||||||
|
let context: NSManagedObjectContext
|
||||||
|
|
||||||
|
init(context: NSManagedObjectContext = DatabaseManager.shared.persistentContainer.newBackgroundContext())
|
||||||
|
{
|
||||||
|
self.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
let dataTask = URLSession.shared.dataTask(with: .patreonInfo) { (data, response, error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
guard let data = data else { throw error! }
|
||||||
|
|
||||||
|
let response = try AltStoreCore.JSONDecoder().decode(Response.self, from: data)
|
||||||
|
|
||||||
|
let previousRefreshID = UserDefaults.shared.patronsRefreshID
|
||||||
|
guard response.refreshID != previousRefreshID else {
|
||||||
|
self.finish(.success(()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PatreonAPI.shared.fetchPatrons { (result) in
|
||||||
|
self.context.perform {
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let patrons = try result.get()
|
||||||
|
let managedPatrons = patrons.map { (patron) -> PatreonAccount in
|
||||||
|
let account = PatreonAccount(patron: patron, context: self.context)
|
||||||
|
account.isFriendZonePatron = true
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
var patronIDs = Set(managedPatrons.map { $0.identifier })
|
||||||
|
if let userAccountID = Keychain.shared.patreonAccountID
|
||||||
|
{
|
||||||
|
// Insert userAccountID into patronIDs to prevent it from being deleted.
|
||||||
|
patronIDs.insert(userAccountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
let removedPredicate = NSPredicate(format: "NOT (%K IN %@)", #keyPath(PatreonAccount.identifier), patronIDs)
|
||||||
|
let removedPatrons = PatreonAccount.all(satisfying: removedPredicate, in: self.context)
|
||||||
|
for patreonAccount in removedPatrons
|
||||||
|
{
|
||||||
|
self.context.delete(patreonAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.context.save()
|
||||||
|
|
||||||
|
UserDefaults.shared.patronsRefreshID = response.refreshID
|
||||||
|
|
||||||
|
self.finish(.success(()))
|
||||||
|
|
||||||
|
print("Updated Friend Zone Patrons!")
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTask.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,8 +29,6 @@ class PatreonViewController: UICollectionViewController
|
|||||||
|
|
||||||
private var prototypeAboutHeader: AboutPatreonHeaderView!
|
private var prototypeAboutHeader: AboutPatreonHeaderView!
|
||||||
|
|
||||||
private var patronsResult: Result<[Patron], Error>?
|
|
||||||
|
|
||||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
return .lightContent
|
return .lightContent
|
||||||
}
|
}
|
||||||
@@ -48,6 +46,8 @@ class PatreonViewController: UICollectionViewController
|
|||||||
self.collectionView.register(PatronsHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "PatronsHeader")
|
self.collectionView.register(PatronsHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "PatronsHeader")
|
||||||
self.collectionView.register(PatronsFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "PatronsFooter")
|
self.collectionView.register(PatronsFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "PatronsFooter")
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(PatreonViewController.didUpdatePatrons(_:)), name: AppManager.didUpdatePatronsNotification, object: nil)
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,20 +75,24 @@ class PatreonViewController: UICollectionViewController
|
|||||||
|
|
||||||
private extension PatreonViewController
|
private extension PatreonViewController
|
||||||
{
|
{
|
||||||
func makeDataSource() -> RSTCompositeCollectionViewDataSource<Patron>
|
func makeDataSource() -> RSTCompositeCollectionViewDataSource<PatreonAccount>
|
||||||
{
|
{
|
||||||
let aboutDataSource = RSTDynamicCollectionViewDataSource<Patron>()
|
let aboutDataSource = RSTDynamicCollectionViewDataSource<PatreonAccount>()
|
||||||
aboutDataSource.numberOfSectionsHandler = { 1 }
|
aboutDataSource.numberOfSectionsHandler = { 1 }
|
||||||
aboutDataSource.numberOfItemsHandler = { _ in 0 }
|
aboutDataSource.numberOfItemsHandler = { _ in 0 }
|
||||||
|
|
||||||
let dataSource = RSTCompositeCollectionViewDataSource<Patron>(dataSources: [aboutDataSource, self.patronsDataSource])
|
let dataSource = RSTCompositeCollectionViewDataSource<PatreonAccount>(dataSources: [aboutDataSource, self.patronsDataSource])
|
||||||
dataSource.proxy = self
|
dataSource.proxy = self
|
||||||
return dataSource
|
return dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePatronsDataSource() -> RSTArrayCollectionViewDataSource<Patron>
|
func makePatronsDataSource() -> RSTFetchedResultsCollectionViewDataSource<PatreonAccount>
|
||||||
{
|
{
|
||||||
let patronsDataSource = RSTArrayCollectionViewDataSource<Patron>(items: [])
|
let fetchRequest: NSFetchRequest<PatreonAccount> = PatreonAccount.fetchRequest()
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "%K == YES", #keyPath(PatreonAccount.isFriendZonePatron))
|
||||||
|
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(PatreonAccount.name), ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))]
|
||||||
|
|
||||||
|
let patronsDataSource = RSTFetchedResultsCollectionViewDataSource<PatreonAccount>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
patronsDataSource.cellConfigurationHandler = { (cell, patron, indexPath) in
|
patronsDataSource.cellConfigurationHandler = { (cell, patron, indexPath) in
|
||||||
let cell = cell as! PatronCollectionViewCell
|
let cell = cell as! PatronCollectionViewCell
|
||||||
cell.textLabel.text = patron.name
|
cell.textLabel.text = patron.name
|
||||||
@@ -173,31 +177,8 @@ private extension PatreonViewController
|
|||||||
{
|
{
|
||||||
@objc func fetchPatrons()
|
@objc func fetchPatrons()
|
||||||
{
|
{
|
||||||
if let result = self.patronsResult, case .failure = result
|
AppManager.shared.updatePatronsIfNeeded()
|
||||||
{
|
self.update()
|
||||||
self.patronsResult = nil
|
|
||||||
self.collectionView.reloadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
PatreonAPI.shared.fetchPatrons { (result) in
|
|
||||||
self.patronsResult = result
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let patrons = try result.get()
|
|
||||||
let sortedPatrons = patrons.sorted { $0.name < $1.name }
|
|
||||||
|
|
||||||
self.patronsDataSource.items = sortedPatrons
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to fetch patrons:", error)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.collectionView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openPatreonURL(_ sender: UIButton)
|
@objc func openPatreonURL(_ sender: UIButton)
|
||||||
@@ -263,6 +244,13 @@ private extension PatreonViewController
|
|||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
self.present(alertController, animated: true, completion: nil)
|
self.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func didUpdatePatrons(_ notification: Notification)
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PatreonViewController
|
extension PatreonViewController
|
||||||
@@ -291,11 +279,18 @@ extension PatreonViewController
|
|||||||
footerView.button.isHidden = false
|
footerView.button.isHidden = false
|
||||||
footerView.button.addTarget(self, action: #selector(PatreonViewController.fetchPatrons), for: .primaryActionTriggered)
|
footerView.button.addTarget(self, action: #selector(PatreonViewController.fetchPatrons), for: .primaryActionTriggered)
|
||||||
|
|
||||||
switch self.patronsResult
|
if self.patronsDataSource.itemCount > 0
|
||||||
{
|
{
|
||||||
case .none: footerView.button.isIndicatingActivity = true
|
footerView.button.isHidden = true
|
||||||
case .success?: footerView.button.isHidden = true
|
}
|
||||||
case .failure?: footerView.button.setTitle(NSLocalizedString("Error Loading Patrons", comment: ""), for: .normal)
|
else
|
||||||
|
{
|
||||||
|
switch AppManager.shared.updatePatronsResult
|
||||||
|
{
|
||||||
|
case .none: footerView.button.isIndicatingActivity = true
|
||||||
|
case .success?: footerView.button.isHidden = true
|
||||||
|
case .failure?: footerView.button.setTitle(NSLocalizedString("Error Loading Patrons", comment: ""), for: .normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return footerView
|
return footerView
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public extension UserDefaults
|
|||||||
|
|
||||||
@NSManaged var patchedApps: [String]?
|
@NSManaged var patchedApps: [String]?
|
||||||
|
|
||||||
|
@NSManaged var patronsRefreshID: String?
|
||||||
|
|
||||||
@NSManaged var trustedSourceIDs: [String]?
|
@NSManaged var trustedSourceIDs: [String]?
|
||||||
|
|
||||||
var activeAppsLimit: Int? {
|
var activeAppsLimit: Int? {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17505" systemVersion="19G2021" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21E230" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Account" representedClassName="Account" syncable="YES">
|
<entity name="Account" representedClassName="Account" syncable="YES">
|
||||||
<attribute name="appleID" attributeType="String"/>
|
<attribute name="appleID" attributeType="String"/>
|
||||||
<attribute name="firstName" attributeType="String"/>
|
<attribute name="firstName" attributeType="String"/>
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
|
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
|
||||||
<attribute name="firstName" optional="YES" attributeType="String"/>
|
<attribute name="firstName" optional="YES" attributeType="String"/>
|
||||||
<attribute name="identifier" attributeType="String"/>
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
<attribute name="isFriendZonePatron" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="isPatron" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="isPatron" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="name" attributeType="String"/>
|
<attribute name="name" attributeType="String"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
@@ -168,7 +169,7 @@
|
|||||||
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="268"/>
|
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="268"/>
|
||||||
<element name="InstalledExtension" positionX="-45" positionY="135" width="128" height="163"/>
|
<element name="InstalledExtension" positionX="-45" positionY="135" width="128" height="163"/>
|
||||||
<element name="NewsItem" positionX="-45" positionY="126" width="128" height="238"/>
|
<element name="NewsItem" positionX="-45" positionY="126" width="128" height="238"/>
|
||||||
<element name="PatreonAccount" positionX="-45" positionY="117" width="128" height="105"/>
|
<element name="PatreonAccount" positionX="-45" positionY="117" width="128" height="104"/>
|
||||||
<element name="RefreshAttempt" positionX="-45" positionY="117" width="128" height="105"/>
|
<element name="RefreshAttempt" positionX="-45" positionY="117" width="128" height="105"/>
|
||||||
<element name="Source" positionX="-45" positionY="99" width="128" height="133"/>
|
<element name="Source" positionX="-45" positionY="99" width="128" height="133"/>
|
||||||
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="343"/>
|
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="343"/>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class PatreonAccount: NSManagedObject, Fetchable
|
|||||||
@NSManaged public var firstName: String?
|
@NSManaged public var firstName: String?
|
||||||
|
|
||||||
@NSManaged public var isPatron: Bool
|
@NSManaged public var isPatron: Bool
|
||||||
|
@NSManaged public var isFriendZonePatron: NSNumber?
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
@@ -62,6 +63,16 @@ public class PatreonAccount: NSManagedObject, Fetchable
|
|||||||
self.isPatron = false
|
self.isPatron = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(patron: Patron, context: NSManagedObjectContext)
|
||||||
|
{
|
||||||
|
super.init(entity: PatreonAccount.entity(), insertInto: context)
|
||||||
|
|
||||||
|
self.identifier = patron.identifier
|
||||||
|
self.name = patron.name
|
||||||
|
self.firstName = nil
|
||||||
|
self.isPatron = (patron.status == .active)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension PatreonAccount
|
public extension PatreonAccount
|
||||||
|
|||||||
Reference in New Issue
Block a user