mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-18 19:23:43 +01:00
Limits updating sources to app launch and manually via pull-to-refresh
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
import minimuxer
|
import minimuxer
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
@@ -24,11 +25,7 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
private let prototypeCell = AppCardCollectionViewCell(frame: .zero)
|
private let prototypeCell = AppCardCollectionViewCell(frame: .zero)
|
||||||
|
|
||||||
private var loadingState: LoadingState = .loading {
|
private var cancellables = Set<AnyCancellable>()
|
||||||
didSet {
|
|
||||||
self.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(source: Source?, coder: NSCoder)
|
init?(source: Source?, coder: NSCoder)
|
||||||
{
|
{
|
||||||
@@ -51,6 +48,7 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
self.collectionView.backgroundColor = .altBackground
|
self.collectionView.backgroundColor = .altBackground
|
||||||
|
self.collectionView.alwaysBounceVertical = true
|
||||||
|
|
||||||
#if BETA
|
#if BETA
|
||||||
self.dataSource.searchController.searchableKeyPaths = [#keyPath(InstalledApp.name)]
|
self.dataSource.searchController.searchableKeyPaths = [#keyPath(InstalledApp.name)]
|
||||||
@@ -69,6 +67,11 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
(self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView)
|
(self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView)
|
||||||
|
|
||||||
|
let refreshControl = UIRefreshControl(frame: .zero, primaryAction: UIAction { [weak self] _ in
|
||||||
|
self?.updateSources()
|
||||||
|
})
|
||||||
|
self.collectionView.refreshControl = refreshControl
|
||||||
|
|
||||||
if let source = self.source
|
if let source = self.source
|
||||||
{
|
{
|
||||||
let tintColor = source.effectiveTintColor ?? .altPrimary
|
let tintColor = source.effectiveTintColor ?? .altPrimary
|
||||||
@@ -85,6 +88,9 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
self.navigationItem.scrollEdgeAppearance = edgeAppearance
|
self.navigationItem.scrollEdgeAppearance = edgeAppearance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.preparePipeline()
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,14 +98,22 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
{
|
{
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
self.fetchSource()
|
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension BrowseViewController
|
private extension BrowseViewController
|
||||||
{
|
{
|
||||||
|
func preparePipeline()
|
||||||
|
{
|
||||||
|
AppManager.shared.$updateSourcesResult
|
||||||
|
.receive(on: RunLoop.main) // Delay to next run loop so we receive _current_ value (not previous value).
|
||||||
|
.sink { [weak self] result in
|
||||||
|
self?.update()
|
||||||
|
}
|
||||||
|
.store(in: &self.cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>
|
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>
|
||||||
{
|
{
|
||||||
let fetchRequest = StoreApp.fetchRequest() as NSFetchRequest<StoreApp>
|
let fetchRequest = StoreApp.fetchRequest() as NSFetchRequest<StoreApp>
|
||||||
@@ -176,75 +190,27 @@ private extension BrowseViewController
|
|||||||
self.dataSource.predicate = nil
|
self.dataSource.predicate = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchSource()
|
func updateSources()
|
||||||
{
|
{
|
||||||
self.loadingState = .loading
|
AppManager.shared.updateAllSources { result in
|
||||||
|
self.collectionView.refreshControl?.endRefreshing()
|
||||||
AppManager.shared.fetchSources() { (result) in
|
|
||||||
do
|
guard case .failure(let error) = result else { return }
|
||||||
|
|
||||||
|
if self.dataSource.itemCount > 0
|
||||||
{
|
{
|
||||||
do
|
let toastView = ToastView(error: error)
|
||||||
{
|
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
||||||
let (_, context) = try result.get()
|
toastView.show(in: self)
|
||||||
try context.save()
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.loadingState = .finished(.success(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch let error as AppManager.FetchSourcesError
|
|
||||||
{
|
|
||||||
try error.managedObjectContext?.save()
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
catch let mergeError as MergeError
|
|
||||||
{
|
|
||||||
guard let sourceID = mergeError.sourceID else { throw mergeError }
|
|
||||||
|
|
||||||
let sanitizedError = (mergeError as NSError).sanitizedForSerialization()
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
guard let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), sourceID), in: context) else { return }
|
|
||||||
|
|
||||||
source.error = sanitizedError
|
|
||||||
try context.save()
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("[ALTLog] Failed to assign error \(sanitizedError.localizedErrorCode) to source \(sourceID).", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mergeError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch var error as NSError
|
|
||||||
{
|
|
||||||
if error.localizedTitle == nil
|
|
||||||
{
|
|
||||||
error = error.withLocalizedTitle(NSLocalizedString("Unable to Refresh Store", comment: ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if self.dataSource.itemCount > 0
|
|
||||||
{
|
|
||||||
let toastView = ToastView(error: error)
|
|
||||||
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.loadingState = .finished(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update()
|
func update()
|
||||||
{
|
{
|
||||||
switch self.loadingState
|
switch AppManager.shared.updateSourcesResult
|
||||||
{
|
{
|
||||||
case .loading:
|
case nil:
|
||||||
self.placeholderView.textLabel.isHidden = true
|
self.placeholderView.textLabel.isHidden = true
|
||||||
self.placeholderView.detailTextLabel.isHidden = false
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
@@ -252,7 +218,7 @@ private extension BrowseViewController
|
|||||||
|
|
||||||
self.placeholderView.activityIndicatorView.startAnimating()
|
self.placeholderView.activityIndicatorView.startAnimating()
|
||||||
|
|
||||||
case .finished(.failure(let error)):
|
case .failure(let error):
|
||||||
self.placeholderView.textLabel.isHidden = false
|
self.placeholderView.textLabel.isHidden = false
|
||||||
self.placeholderView.detailTextLabel.isHidden = false
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
@@ -261,8 +227,9 @@ private extension BrowseViewController
|
|||||||
|
|
||||||
self.placeholderView.activityIndicatorView.stopAnimating()
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
|
||||||
case .finished(.success):
|
case .success:
|
||||||
self.placeholderView.textLabel.isHidden = true
|
self.placeholderView.textLabel.text = NSLocalizedString("No Apps", comment: "")
|
||||||
|
self.placeholderView.textLabel.isHidden = false
|
||||||
self.placeholderView.detailTextLabel.isHidden = true
|
self.placeholderView.detailTextLabel.isHidden = true
|
||||||
|
|
||||||
self.placeholderView.activityIndicatorView.stopAnimating()
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
|||||||
@@ -304,6 +304,11 @@ extension LaunchViewController
|
|||||||
AppManager.shared.updatePatronsIfNeeded()
|
AppManager.shared.updatePatronsIfNeeded()
|
||||||
PatreonAPI.shared.refreshPatreonAccount()
|
PatreonAPI.shared.refreshPatreonAccount()
|
||||||
|
|
||||||
|
AppManager.shared.updateAllSources { result in
|
||||||
|
guard case .failure(let error) = result else { return }
|
||||||
|
Logger.main.error("Failed to update sources on launch. \(error.localizedDescription, privacy: .public)")
|
||||||
|
}
|
||||||
|
|
||||||
self.updateKnownSources()
|
self.updateKnownSources()
|
||||||
|
|
||||||
WidgetCenter.shared.reloadAllTimelines()
|
WidgetCenter.shared.reloadAllTimelines()
|
||||||
|
|||||||
@@ -41,25 +41,21 @@ final class AppManagerPublisher: ObservableObject
|
|||||||
fileprivate(set) var refreshProgress = [String: Progress]()
|
fileprivate(set) var refreshProgress = [String: Progress]()
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AppManager
|
class AppManager: ObservableObject
|
||||||
{
|
{
|
||||||
static let shared = AppManager()
|
static let shared = AppManager()
|
||||||
|
|
||||||
private(set) var updatePatronsResult: Result<Void, Error>?
|
private(set) var updatePatronsResult: Result<Void, Error>?
|
||||||
|
|
||||||
|
@Published
|
||||||
|
private(set) var updateSourcesResult: Result<Void, Error>? // nil == loading
|
||||||
|
|
||||||
private let operationQueue = OperationQueue()
|
private let operationQueue = OperationQueue()
|
||||||
private let serialOperationQueue = OperationQueue()
|
private let serialOperationQueue = OperationQueue()
|
||||||
|
|
||||||
private var installationProgress = [String: Progress]() {
|
@Published private var installationProgress = [String: Progress]()
|
||||||
didSet {
|
@Published private var refreshProgress = [String: Progress]()
|
||||||
self.publisher.installationProgress = self.installationProgress
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
}
|
|
||||||
}
|
|
||||||
private var refreshProgress = [String: Progress]() {
|
|
||||||
didSet {
|
|
||||||
self.publisher.refreshProgress = self.refreshProgress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private lazy var progressLock: UnsafeMutablePointer<os_unfair_lock> = {
|
private lazy var progressLock: UnsafeMutablePointer<os_unfair_lock> = {
|
||||||
// Can't safely pass &os_unfair_lock to os_unfair_lock functions in Swift,
|
// Can't safely pass &os_unfair_lock to os_unfair_lock functions in Swift,
|
||||||
@@ -70,9 +66,6 @@ final class AppManager
|
|||||||
return lock
|
return lock
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let publisher = AppManagerPublisher()
|
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
self.operationQueue.name = "com.altstore.AppManager.operationQueue"
|
self.operationQueue.name = "com.altstore.AppManager.operationQueue"
|
||||||
@@ -95,7 +88,7 @@ final class AppManager
|
|||||||
/// Every time refreshProgress is changed, update all InstalledApps in memory
|
/// Every time refreshProgress is changed, update all InstalledApps in memory
|
||||||
/// so that app.isRefreshing == refreshProgress.keys.contains(app.bundleID)
|
/// so that app.isRefreshing == refreshProgress.keys.contains(app.bundleID)
|
||||||
|
|
||||||
self.publisher.$refreshProgress
|
self.$refreshProgress
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: RunLoop.main)
|
||||||
.map(\.keys)
|
.map(\.keys)
|
||||||
.flatMap { (bundleIDs) in
|
.flatMap { (bundleIDs) in
|
||||||
@@ -547,6 +540,68 @@ extension AppManager
|
|||||||
self.run([updatePatronsOperation], context: nil)
|
self.run([updatePatronsOperation], context: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAllSources(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
self.updateSourcesResult = nil
|
||||||
|
|
||||||
|
self.fetchSources() { (result) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let (_, context) = try result.get()
|
||||||
|
try context.save()
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.updateSourcesResult = .success(())
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch let error as AppManager.FetchSourcesError
|
||||||
|
{
|
||||||
|
try error.managedObjectContext?.save()
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
catch let mergeError as MergeError
|
||||||
|
{
|
||||||
|
guard let sourceID = mergeError.sourceID else { throw mergeError }
|
||||||
|
|
||||||
|
let sanitizedError = (mergeError as NSError).sanitizedForSerialization()
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
guard let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), sourceID), in: context) else { return }
|
||||||
|
|
||||||
|
source.error = sanitizedError
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.main.error("Failed to assign error \(sanitizedError.localizedErrorCode) to source \(sourceID, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw mergeError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch var error as NSError
|
||||||
|
{
|
||||||
|
if error.localizedTitle == nil
|
||||||
|
{
|
||||||
|
error = error.withLocalizedTitle(NSLocalizedString("Unable to Refresh Store", comment: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.updateSourcesResult = .failure(error)
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppManager
|
||||||
|
{
|
||||||
@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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
import Combine
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
@@ -48,17 +49,13 @@ class NewsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
||||||
|
private var retryButton: UIButton!
|
||||||
|
|
||||||
private var prototypeCell: NewsCollectionViewCell!
|
private var prototypeCell: NewsCollectionViewCell!
|
||||||
|
|
||||||
private var loadingState: LoadingState = .loading {
|
|
||||||
didSet {
|
|
||||||
self.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
private var cachedCellSizes = [String: CGSize]()
|
private var cachedCellSizes = [String: CGSize]()
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init?(source: Source?, coder: NSCoder)
|
init?(source: Source?, coder: NSCoder)
|
||||||
{
|
{
|
||||||
@@ -108,6 +105,16 @@ class NewsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
(self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView)
|
(self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView)
|
||||||
|
|
||||||
|
let refreshControl = UIRefreshControl(frame: .zero)
|
||||||
|
refreshControl.addTarget(self, action: #selector(NewsViewController.updateSources), for: .primaryActionTriggered)
|
||||||
|
self.collectionView.refreshControl = refreshControl
|
||||||
|
|
||||||
|
self.retryButton = UIButton(type: .system)
|
||||||
|
self.retryButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
self.retryButton.setTitle(NSLocalizedString("Try Again", comment: ""), for: .normal)
|
||||||
|
self.retryButton.addTarget(self, action: #selector(NewsViewController.updateSources), for: .primaryActionTriggered)
|
||||||
|
self.placeholderView.stackView.addArrangedSubview(self.retryButton)
|
||||||
|
|
||||||
if let source = self.source
|
if let source = self.source
|
||||||
{
|
{
|
||||||
let tintColor = source.effectiveTintColor ?? .altPrimary
|
let tintColor = source.effectiveTintColor ?? .altPrimary
|
||||||
@@ -124,16 +131,10 @@ class NewsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
self.navigationItem.scrollEdgeAppearance = edgeAppearance
|
self.navigationItem.scrollEdgeAppearance = edgeAppearance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.preparePipeline()
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool)
|
|
||||||
{
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
|
|
||||||
self.fetchSource()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillLayoutSubviews()
|
override func viewWillLayoutSubviews()
|
||||||
{
|
{
|
||||||
super.viewWillLayoutSubviews()
|
super.viewWillLayoutSubviews()
|
||||||
@@ -149,6 +150,16 @@ class NewsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
private extension NewsViewController
|
private extension NewsViewController
|
||||||
{
|
{
|
||||||
|
func preparePipeline()
|
||||||
|
{
|
||||||
|
AppManager.shared.$updateSourcesResult
|
||||||
|
.receive(on: RunLoop.main) // Delay to next run loop so we receive _current_ value (not previous value).
|
||||||
|
.sink { result in
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
.store(in: &self.cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<NewsItem, UIImage>
|
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<NewsItem, UIImage>
|
||||||
{
|
{
|
||||||
let fetchRequest = NewsItem.sortedFetchRequest(for: self.source)
|
let fetchRequest = NewsItem.sortedFetchRequest(for: self.source)
|
||||||
@@ -225,96 +236,51 @@ private extension NewsViewController
|
|||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchSource()
|
@objc func updateSources()
|
||||||
{
|
{
|
||||||
self.loadingState = .loading
|
AppManager.shared.updateAllSources() { result in
|
||||||
|
self.collectionView.refreshControl?.endRefreshing()
|
||||||
AppManager.shared.fetchSources() { (result) in
|
|
||||||
do
|
guard case .failure(let error) = result else { return }
|
||||||
|
|
||||||
|
if self.dataSource.itemCount > 0
|
||||||
{
|
{
|
||||||
do
|
let toastView = ToastView(error: error)
|
||||||
{
|
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
||||||
let (_, context) = try result.get()
|
toastView.show(in: self)
|
||||||
try context.save()
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.loadingState = .finished(.success(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch let error as AppManager.FetchSourcesError
|
|
||||||
{
|
|
||||||
try error.managedObjectContext?.save()
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
catch let mergeError as MergeError
|
|
||||||
{
|
|
||||||
guard let sourceID = mergeError.sourceID else { throw mergeError }
|
|
||||||
|
|
||||||
let sanitizedError = (mergeError as NSError).sanitizedForSerialization()
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
guard let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), sourceID), in: context) else { return }
|
|
||||||
|
|
||||||
source.error = sanitizedError
|
|
||||||
try context.save()
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("[ALTLog] Failed to assign error \(sanitizedError.localizedErrorCode) to source \(sourceID).", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw mergeError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch var error as NSError
|
|
||||||
{
|
|
||||||
if error.localizedTitle == nil
|
|
||||||
{
|
|
||||||
error = error.withLocalizedTitle(NSLocalizedString("Unable to Refresh Store", comment: ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if self.dataSource.itemCount > 0
|
|
||||||
{
|
|
||||||
let toastView = ToastView(error: error)
|
|
||||||
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.loadingState = .finished(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update()
|
func update()
|
||||||
{
|
{
|
||||||
switch self.loadingState
|
switch AppManager.shared.updateSourcesResult
|
||||||
{
|
{
|
||||||
case .loading:
|
case nil:
|
||||||
self.placeholderView.textLabel.isHidden = true
|
self.placeholderView.textLabel.isHidden = true
|
||||||
self.placeholderView.detailTextLabel.isHidden = false
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("Loading...", comment: "")
|
self.placeholderView.detailTextLabel.text = NSLocalizedString("Loading...", comment: "")
|
||||||
|
|
||||||
|
self.retryButton.isHidden = true
|
||||||
self.placeholderView.activityIndicatorView.startAnimating()
|
self.placeholderView.activityIndicatorView.startAnimating()
|
||||||
|
|
||||||
case .finished(.failure(let error)):
|
case .failure(let error):
|
||||||
self.placeholderView.textLabel.isHidden = false
|
self.placeholderView.textLabel.isHidden = false
|
||||||
self.placeholderView.detailTextLabel.isHidden = false
|
self.placeholderView.detailTextLabel.isHidden = false
|
||||||
|
|
||||||
self.placeholderView.textLabel.text = NSLocalizedString("Unable to Fetch News", comment: "")
|
self.placeholderView.textLabel.text = NSLocalizedString("Unable to Fetch News", comment: "")
|
||||||
self.placeholderView.detailTextLabel.text = error.localizedDescription
|
self.placeholderView.detailTextLabel.text = error.localizedDescription
|
||||||
|
|
||||||
|
self.retryButton.isHidden = false
|
||||||
self.placeholderView.activityIndicatorView.stopAnimating()
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
|
|
||||||
case .finished(.success):
|
case .success:
|
||||||
self.placeholderView.textLabel.isHidden = true
|
self.placeholderView.textLabel.isHidden = true
|
||||||
self.placeholderView.detailTextLabel.isHidden = true
|
self.placeholderView.detailTextLabel.isHidden = true
|
||||||
|
|
||||||
|
self.retryButton.isHidden = true
|
||||||
self.placeholderView.activityIndicatorView.stopAnimating()
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,7 +419,9 @@ extension NewsViewController
|
|||||||
footerView.bannerView.button.addTarget(self, action: #selector(NewsViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
footerView.bannerView.button.addTarget(self, action: #selector(NewsViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
footerView.tapGestureRecognizer.addTarget(self, action: #selector(NewsViewController.handleTapGesture(_:)))
|
footerView.tapGestureRecognizer.addTarget(self, action: #selector(NewsViewController.handleTapGesture(_:)))
|
||||||
|
|
||||||
Nuke.loadImage(with: storeApp.iconURL, into: footerView.bannerView.iconImageView)
|
Nuke.loadImage(with: storeApp.iconURL, into: footerView.bannerView.iconImageView) { result in
|
||||||
|
footerView.bannerView.iconImageView.isIndicatingActivity = false
|
||||||
|
}
|
||||||
|
|
||||||
return footerView
|
return footerView
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user