mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-08 22:33:26 +01:00
Shows source/category icon next to BrowseViewController’s title
Also tints all navigation bar buttons to match source/category tint color. # Conflicts: # AltStore/Browse/BrowseViewController.swift
This commit is contained in:
@@ -39,10 +39,17 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
private let prototypeCell = AppCardCollectionViewCell(frame: .zero)
|
private let prototypeCell = AppCardCollectionViewCell(frame: .zero)
|
||||||
|
|
||||||
private var sortButton: UIBarButtonItem?
|
private var sortButton: UIBarButtonItem?
|
||||||
|
private var categoriesMenu: UIMenu?
|
||||||
|
|
||||||
private var preferredAppSorting: AppSorting = UserDefaults.shared.preferredAppSorting
|
private var preferredAppSorting: AppSorting = UserDefaults.shared.preferredAppSorting
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private var titleStackView: UIStackView!
|
||||||
|
private var titleSourceIconView: AppIconImageView!
|
||||||
|
private var titleCategoryIconView: UIImageView!
|
||||||
|
private var titleLabel: UILabel!
|
||||||
|
|
||||||
init?(source: Source?, coder: NSCoder)
|
init?(source: Source?, coder: NSCoder)
|
||||||
{
|
{
|
||||||
self.source = source
|
self.source = source
|
||||||
@@ -78,6 +85,7 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
self.collectionView.backgroundColor = .altBackground
|
self.collectionView.backgroundColor = .altBackground
|
||||||
self.collectionView.alwaysBounceVertical = true
|
self.collectionView.alwaysBounceVertical = true
|
||||||
|
|
||||||
|
self.dataSource.searchController.delegate = self
|
||||||
self.dataSource.searchController.searchableKeyPaths = [#keyPath(StoreApp.name),
|
self.dataSource.searchController.searchableKeyPaths = [#keyPath(StoreApp.name),
|
||||||
#keyPath(StoreApp.subtitle),
|
#keyPath(StoreApp.subtitle),
|
||||||
#keyPath(StoreApp.developerName),
|
#keyPath(StoreApp.developerName),
|
||||||
@@ -101,33 +109,31 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
})
|
})
|
||||||
self.collectionView.refreshControl = refreshControl
|
self.collectionView.refreshControl = refreshControl
|
||||||
|
|
||||||
if let source = self.source
|
if self.category != nil, #available(iOS 16, *)
|
||||||
{
|
{
|
||||||
let tintColor = source.effectiveTintColor ?? .altPrimary
|
let categoriesMenu = UIMenu(children: [
|
||||||
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
|
|
||||||
}
|
|
||||||
else if self.category != nil, #available(iOS 16, *)
|
|
||||||
{
|
|
||||||
let menu = UIMenu(children: [
|
|
||||||
UIDeferredMenuElement.uncached { [weak self] completion in
|
UIDeferredMenuElement.uncached { [weak self] completion in
|
||||||
let actions = self?.makeCategoryActions() ?? []
|
let actions = self?.makeCategoryActions() ?? []
|
||||||
completion(actions)
|
completion(actions)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
self.navigationItem.titleMenuProvider = { _ in menu }
|
self.navigationItem.titleMenuProvider = { _ in categoriesMenu }
|
||||||
|
|
||||||
|
self.categoriesMenu = categoriesMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.titleSourceIconView = AppIconImageView(style: .circular)
|
||||||
|
|
||||||
|
self.titleCategoryIconView = UIImageView(frame: .zero)
|
||||||
|
self.titleCategoryIconView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
|
self.titleLabel = UILabel()
|
||||||
|
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
||||||
|
|
||||||
|
self.titleStackView = UIStackView(arrangedSubviews: [self.titleSourceIconView, self.titleCategoryIconView, self.titleLabel])
|
||||||
|
self.titleStackView.spacing = 4
|
||||||
|
|
||||||
self.navigationItem.largeTitleDisplayMode = .never
|
self.navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
if #available(iOS 16, *)
|
if #available(iOS 16, *)
|
||||||
@@ -142,6 +148,15 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
self.preparePipeline()
|
self.preparePipeline()
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
// Source icon = equal width and height
|
||||||
|
self.titleSourceIconView.heightAnchor.constraint(equalToConstant: 26),
|
||||||
|
self.titleSourceIconView.widthAnchor.constraint(equalTo: self.titleSourceIconView.heightAnchor),
|
||||||
|
|
||||||
|
// Category icon = constant height, variable widths
|
||||||
|
self.titleCategoryIconView.heightAnchor.constraint(equalToConstant: 26)
|
||||||
|
])
|
||||||
|
|
||||||
self.updateDataSource()
|
self.updateDataSource()
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
@@ -152,6 +167,13 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
|
self.navigationController?.navigationBar.tintColor = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension BrowseViewController
|
private extension BrowseViewController
|
||||||
@@ -346,13 +368,74 @@ private extension BrowseViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let category = self.category
|
let tintColor: UIColor
|
||||||
|
|
||||||
|
if let source = self.source
|
||||||
{
|
{
|
||||||
|
tintColor = source.effectiveTintColor?.adjustedForDisplay ?? .altPrimary
|
||||||
|
|
||||||
|
self.title = source.name
|
||||||
|
|
||||||
|
self.titleSourceIconView.backgroundColor = tintColor
|
||||||
|
self.titleSourceIconView.isHidden = false
|
||||||
|
|
||||||
|
self.titleCategoryIconView.isHidden = true
|
||||||
|
|
||||||
|
if let iconURL = source.effectiveIconURL
|
||||||
|
{
|
||||||
|
Nuke.loadImage(with: iconURL, into: self.titleSourceIconView) { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): Logger.main.error("Failed to fetch source icon at \(iconURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||||
|
case .success: self.titleSourceIconView.backgroundColor = .white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if let category = self.category
|
||||||
|
{
|
||||||
|
tintColor = category.tintColor
|
||||||
|
|
||||||
self.title = category.localizedName
|
self.title = category.localizedName
|
||||||
|
|
||||||
|
let image = UIImage(systemName: category.filledSymbolName)?.withTintColor(tintColor, renderingMode: .alwaysOriginal)
|
||||||
|
self.titleCategoryIconView.image = image
|
||||||
|
self.titleCategoryIconView.isHidden = false
|
||||||
|
|
||||||
|
self.titleSourceIconView.isHidden = true
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
tintColor = .altPrimary
|
||||||
|
|
||||||
self.title = NSLocalizedString("Browse", comment: "")
|
self.title = NSLocalizedString("Browse", comment: "")
|
||||||
|
|
||||||
|
self.titleSourceIconView.isHidden = true
|
||||||
|
self.titleCategoryIconView.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.titleLabel.text = self.title
|
||||||
|
self.titleStackView.sizeToFit()
|
||||||
|
self.navigationItem.titleView = self.titleStackView
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// Necessary to tint UISearchController's inline bar button.
|
||||||
|
self.navigationController?.navigationBar.tintColor = tintColor
|
||||||
|
|
||||||
|
if let sortButton
|
||||||
|
{
|
||||||
|
sortButton.image = sortButton.image?.withTintColor(tintColor, renderingMode: .alwaysOriginal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,11 +461,17 @@ private extension BrowseViewController
|
|||||||
return StoreCategory(rawValue: rawCategory) ?? .other
|
return StoreCategory(rawValue: rawCategory) ?? .other
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortedCategories = Set(categories).sorted(by: { $0.localizedName.localizedStandardCompare($1.localizedName) == .orderedAscending })
|
var sortedCategories = Set(categories).sorted(by: { $0.localizedName.localizedStandardCompare($1.localizedName) == .orderedAscending })
|
||||||
|
if let otherIndex = sortedCategories.firstIndex(of: .other)
|
||||||
|
{
|
||||||
|
// Ensure "Other" is always last
|
||||||
|
sortedCategories.move(fromOffsets: [otherIndex], toOffset: sortedCategories.count)
|
||||||
|
}
|
||||||
|
|
||||||
let actions = sortedCategories.map { category in
|
let actions = sortedCategories.map { category in
|
||||||
let state: UIAction.State = (category == self.category) ? .on : .off
|
let state: UIAction.State = (category == self.category) ? .on : .off
|
||||||
return UIAction(title: category.localizedName, image: UIImage(systemName: category.symbolName), state: state) { _ in
|
let image = UIImage(systemName: category.symbolName)?.withTintColor(category.tintColor, renderingMode: .alwaysOriginal)
|
||||||
|
return UIAction(title: category.localizedName, image: image, state: state) { _ in
|
||||||
handler(category)
|
handler(category)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,6 +678,30 @@ extension BrowseViewController: UIViewControllerPreviewingDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension BrowseViewController: UISearchControllerDelegate
|
||||||
|
{
|
||||||
|
func willPresentSearchController(_ searchController: UISearchController)
|
||||||
|
{
|
||||||
|
// Hide titleView + menu to ensure search bar is as large as possible.
|
||||||
|
self.navigationItem.titleView = nil
|
||||||
|
|
||||||
|
if #available(iOS 16, *)
|
||||||
|
{
|
||||||
|
self.navigationItem.titleMenuProvider = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func willDismissSearchController(_ searchController: UISearchController)
|
||||||
|
{
|
||||||
|
self.navigationItem.titleView = self.titleStackView
|
||||||
|
|
||||||
|
if let categoriesMenu, #available(iOS 16, *)
|
||||||
|
{
|
||||||
|
self.navigationItem.titleMenuProvider = { _ in categoriesMenu }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 17, *)
|
@available(iOS 17, *)
|
||||||
#Preview(traits: .portrait) {
|
#Preview(traits: .portrait) {
|
||||||
DatabaseManager.shared.startForPreview()
|
DatabaseManager.shared.startForPreview()
|
||||||
|
|||||||
@@ -25,10 +25,24 @@ class AppIconImageView: UIImageView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func awakeFromNib()
|
init(style: Style)
|
||||||
{
|
{
|
||||||
super.awakeFromNib()
|
self.style = style
|
||||||
|
|
||||||
|
super.init(image: nil)
|
||||||
|
|
||||||
|
self.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder)
|
||||||
|
{
|
||||||
|
super.init(coder: coder)
|
||||||
|
|
||||||
|
self.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize()
|
||||||
|
{
|
||||||
self.contentMode = .scaleAspectFill
|
self.contentMode = .scaleAspectFill
|
||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
self.backgroundColor = .white
|
self.backgroundColor = .white
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ public enum StoreCategory: String, CaseIterable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var filledSymbolName: String {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .utilities: return self.symbolName
|
||||||
|
default: return self.symbolName + ".fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var tintColor: UIColor {
|
public var tintColor: UIColor {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user