From 5dfb36ca48fca93ab9f49b0f62f964f9b4870d13 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 4 Apr 2023 16:17:38 -0500 Subject: [PATCH] Supports viewing all NewsItems and StoreApps for a source --- AltStore.xcodeproj/project.pbxproj | 4 ++ AltStore/Browse/BrowseViewController.swift | 46 +++++++++++++- .../HeaderContentViewController.swift | 8 +-- .../UINavigationBarAppearance+TintColor.swift | 22 +++++++ AltStore/News/NewsViewController.swift | 42 +++++++++++-- .../SourceDetailContentViewController.swift | 62 +++++++++++++++++++ AltStore/Sources/Sources.storyboard | 56 +++++++++++++++++ 7 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 AltStore/Extensions/UINavigationBarAppearance+TintColor.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 025c0cff..c80d2f7c 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -366,6 +366,7 @@ D58D5F2E26DFE68E00E55E38 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D58D5F2D26DFE68E00E55E38 /* LaunchAtLogin */; }; D59162AB29BA60A9005CBF47 /* SourceHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59162AA29BA60A9005CBF47 /* SourceHeaderView.swift */; }; D59162AD29BA616A005CBF47 /* SourceHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D59162AC29BA616A005CBF47 /* SourceHeaderView.xib */; }; + D5927D6629DCC89000D6898E /* UINavigationBarAppearance+TintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5927D6529DCC89000D6898E /* UINavigationBarAppearance+TintColor.swift */; }; D5935AED29C39DE300C157EF /* SourceDetailsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5935AEC29C39DE300C157EF /* SourceDetailsComponents.swift */; }; D5935AEF29C3B23600C157EF /* Sources.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D5935AEE29C3B23600C157EF /* Sources.storyboard */; }; D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; }; @@ -919,6 +920,7 @@ D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = ""; }; D59162AA29BA60A9005CBF47 /* SourceHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceHeaderView.swift; sourceTree = ""; }; D59162AC29BA616A005CBF47 /* SourceHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SourceHeaderView.xib; sourceTree = ""; }; + D5927D6529DCC89000D6898E /* UINavigationBarAppearance+TintColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBarAppearance+TintColor.swift"; sourceTree = ""; }; D5935AEC29C39DE300C157EF /* SourceDetailsComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceDetailsComponents.swift; sourceTree = ""; }; D5935AEE29C3B23600C157EF /* Sources.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sources.storyboard; sourceTree = ""; }; D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = ""; }; @@ -1784,6 +1786,7 @@ D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */, B376FE3D29258C8900E18883 /* OSLog+SideStore.swift */, 0E13E5852CC8F55900E9C0DF /* ProcessInfo+SideStore.swift */, + D5927D6529DCC89000D6898E /* UINavigationBarAppearance+TintColor.swift */, ); path = Extensions; sourceTree = ""; @@ -2715,6 +2718,7 @@ BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */, BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */, B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */, + D5927D6629DCC89000D6898E /* UINavigationBarAppearance+TintColor.swift in Sources */, BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */, B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */, BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */, diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index a6fac8a1..87fa93d3 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -16,6 +16,9 @@ import Nuke class BrowseViewController: UICollectionViewController, PeekPopPreviewing { + // Nil == Show apps from all sources. + var source: Source? + private lazy var dataSource = self.makeDataSource() private lazy var placeholderView = RSTPlaceholderView(frame: .zero) @@ -27,6 +30,18 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing } } + init?(source: Source?, coder: NSCoder) + { + self.source = source + + super.init(coder: coder) + } + + required init?(coder: NSCoder) + { + super.init(coder: coder) + } + private var cachedItemSizes = [String: CGSize]() @IBOutlet private var sourcesBarButtonItem: UIBarButtonItem! @@ -49,6 +64,22 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing (self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView) + if let source = self.source + { + let tintColor = source.effectiveTintColor ?? .altPrimary + self.view.tintColor = tintColor + + let appearance = NavigationBarAppearance() + appearance.configureWithTintColor(tintColor) + appearance.configureWithDefaultBackground() + + let edgeAppearance = appearance.copy() + edgeAppearance.configureWithTransparentBackground() + + self.navigationItem.standardAppearance = appearance + self.navigationItem.scrollEdgeAppearance = edgeAppearance + } + self.update() } @@ -78,9 +109,20 @@ private extension BrowseViewController NSSortDescriptor(keyPath: \StoreApp.name, ascending: true), NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true)] fetchRequest.returnsObjectsAsFaults = false - fetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID) - let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) + let predicate = NSPredicate(format: "%K != %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID) + if let source = self.source + { + let filterPredicate = NSPredicate(format: "%K == %@", #keyPath(StoreApp._source), source) + fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filterPredicate, predicate]) + } + else + { + fetchRequest.predicate = predicate + } + + let context = self.source?.managedObjectContext ?? DatabaseManager.shared.viewContext + let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource(fetchRequest: fetchRequest, managedObjectContext: context) dataSource.cellConfigurationHandler = { (cell, app, indexPath) in let cell = cell as! BrowseCollectionViewCell cell.layoutMargins.left = self.view.layoutMargins.left diff --git a/AltStore/Components/HeaderContentViewController.swift b/AltStore/Components/HeaderContentViewController.swift index 91c691cf..3b8de9d5 100644 --- a/AltStore/Components/HeaderContentViewController.swift +++ b/AltStore/Components/HeaderContentViewController.swift @@ -473,13 +473,7 @@ private extension HeaderContentViewController barAppearance.titleTextAttributes = [.foregroundColor: UIColor.clear] let tintColor = isHidden ? UIColor.clear : self.tintColor ?? .altPrimary - - let buttonAppearance = UIBarButtonItemAppearance(style: .plain) - buttonAppearance.normal.titleTextAttributes = [.foregroundColor: tintColor] - barAppearance.buttonAppearance = buttonAppearance - - let backButtonImage = UIImage(systemName: "chevron.backward")?.withTintColor(tintColor, renderingMode: .alwaysOriginal) - barAppearance.setBackIndicatorImage(backButtonImage, transitionMaskImage: backButtonImage) + barAppearance.configureWithTintColor(tintColor) self.navigationItem.standardAppearance = barAppearance self.navigationItem.scrollEdgeAppearance = barAppearance diff --git a/AltStore/Extensions/UINavigationBarAppearance+TintColor.swift b/AltStore/Extensions/UINavigationBarAppearance+TintColor.swift new file mode 100644 index 00000000..f49dd387 --- /dev/null +++ b/AltStore/Extensions/UINavigationBarAppearance+TintColor.swift @@ -0,0 +1,22 @@ +// +// UINavigationBarAppearance+TintColor.swift +// AltStore +// +// Created by Riley Testut on 4/4/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import UIKit + +extension UINavigationBarAppearance +{ + func configureWithTintColor(_ tintColor: UIColor) + { + let buttonAppearance = UIBarButtonItemAppearance(style: .plain) + buttonAppearance.normal.titleTextAttributes = [.foregroundColor: tintColor] + self.buttonAppearance = buttonAppearance + + let backButtonImage = UIImage(systemName: "chevron.backward")?.withTintColor(tintColor, renderingMode: .alwaysOriginal) + self.setBackIndicatorImage(backButtonImage, transitionMaskImage: backButtonImage) + } +} diff --git a/AltStore/News/NewsViewController.swift b/AltStore/News/NewsViewController.swift index b12bf0bb..726afb3d 100644 --- a/AltStore/News/NewsViewController.swift +++ b/AltStore/News/NewsViewController.swift @@ -43,6 +43,9 @@ private final class AppBannerFooterView: UICollectionReusableView class NewsViewController: UICollectionViewController, PeekPopPreviewing { + // Nil == Show news from all sources. + var source: Source? + private lazy var dataSource = self.makeDataSource() private lazy var placeholderView = RSTPlaceholderView(frame: .zero) @@ -57,10 +60,24 @@ class NewsViewController: UICollectionViewController, PeekPopPreviewing // Cache private var cachedCellSizes = [String: CGSize]() + init?(source: Source?, coder: NSCoder) + { + self.source = source + + super.init(coder: coder) + + self.initialize() + } + required init?(coder: NSCoder) { super.init(coder: coder) + self.initialize() + } + + private func initialize() + { NotificationCenter.default.addObserver(self, selector: #selector(NewsViewController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil) } @@ -79,6 +96,22 @@ class NewsViewController: UICollectionViewController, PeekPopPreviewing (self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView) + if let source = self.source + { + let tintColor = source.effectiveTintColor ?? .altPrimary + self.view.tintColor = tintColor + + let appearance = NavigationBarAppearance() + appearance.configureWithTintColor(tintColor) + appearance.configureWithDefaultBackground() + + let edgeAppearance = appearance.copy() + edgeAppearance.configureWithTransparentBackground() + + self.navigationItem.standardAppearance = appearance + self.navigationItem.scrollEdgeAppearance = edgeAppearance + } + self.update() } @@ -111,12 +144,11 @@ private extension NewsViewController { func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource { - let fetchRequest = NewsItem.fetchRequest() as NSFetchRequest - fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \NewsItem.date, ascending: false), - NSSortDescriptor(keyPath: \NewsItem.sortIndex, ascending: true), - NSSortDescriptor(keyPath: \NewsItem.sourceIdentifier, ascending: true)] + let fetchRequest = NewsItem.sortedFetchRequest(for: self.source) + let context = self.source?.managedObjectContext ?? DatabaseManager.shared.viewContext - let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(NewsItem.objectID), cacheName: nil) + // Use fetchedResultsController to split NewsItems up into sections. + let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: #keyPath(NewsItem.objectID), cacheName: nil) let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource(fetchedResultsController: fetchedResultsController) dataSource.proxy = self diff --git a/AltStore/Sources/SourceDetailContentViewController.swift b/AltStore/Sources/SourceDetailContentViewController.swift index 5630cac5..3fea070c 100644 --- a/AltStore/Sources/SourceDetailContentViewController.swift +++ b/AltStore/Sources/SourceDetailContentViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import SafariServices import AltStoreCore import Roxas @@ -304,6 +305,33 @@ private extension SourceDetailContentViewController } } +private extension SourceDetailContentViewController +{ + @objc func viewAllNews() + { + self.performSegue(withIdentifier: "showAllNews", sender: nil) + } + + @objc func viewAllApps() + { + self.performSegue(withIdentifier: "showAllApps", sender: nil) + } + + @IBSegueAction + func makeNewsViewController(_ coder: NSCoder) -> UIViewController? + { + let newsViewController = NewsViewController(source: self.source, coder: coder) + return newsViewController + } + + @IBSegueAction + func makeBrowseViewController(_ coder: NSCoder) -> UIViewController? + { + let browseViewController = BrowseViewController(source: self.source, coder: coder) + return browseViewController + } +} + extension SourceDetailContentViewController { override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView @@ -318,6 +346,9 @@ extension SourceDetailContentViewController let buttonView = supplementaryView as! ButtonCollectionReusableView buttonView.button.setTitle(NSLocalizedString("View All", comment: ""), for: .normal) + buttonView.button.removeTarget(self, action: nil, for: .primaryActionTriggered) + buttonView.button.addTarget(self, action: #selector(SourceDetailContentViewController.viewAllNews), for: .primaryActionTriggered) + case (.featuredApps, .title): let titleView = supplementaryView as! TitleCollectionReusableView titleView.label.text = NSLocalizedString("Featured Apps", comment: "") @@ -326,6 +357,9 @@ extension SourceDetailContentViewController let buttonView = supplementaryView as! ButtonCollectionReusableView buttonView.button.setTitle(NSLocalizedString("View All Apps", comment: ""), for: .normal) + buttonView.button.removeTarget(self, action: nil, for: .primaryActionTriggered) + buttonView.button.addTarget(self, action: #selector(SourceDetailContentViewController.viewAllApps), for: .primaryActionTriggered) + case (.about, _): let titleView = supplementaryView as! TitleCollectionReusableView titleView.label.text = NSLocalizedString("About", comment: "") @@ -333,6 +367,34 @@ extension SourceDetailContentViewController return supplementaryView } + + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) + { + let section = Section(rawValue: indexPath.section)! + let item = self.dataSource.item(at: indexPath) + + switch (section, item) + { + case (.news, let newsItem as NewsItem): + 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) + } + + case (.featuredApps, let storeApp as StoreApp): + let appViewController = AppViewController.makeAppViewController(app: storeApp) + self.navigationController?.pushViewController(appViewController, animated: true) + + default: break + } + } } extension SourceDetailContentViewController: ScrollableContentViewController diff --git a/AltStore/Sources/Sources.storyboard b/AltStore/Sources/Sources.storyboard index d6e973c4..d3e81888 100644 --- a/AltStore/Sources/Sources.storyboard +++ b/AltStore/Sources/Sources.storyboard @@ -138,6 +138,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -192,10 +218,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +