Supports filtering apps in BrowseViewController by category

This commit is contained in:
Riley Testut
2023-12-07 18:11:25 -06:00
committed by Magesh K
parent 1b8daa59c0
commit 20b424c97c
2 changed files with 99 additions and 1 deletions

View File

@@ -18,7 +18,14 @@ import Nuke
class BrowseViewController: UICollectionViewController, PeekPopPreviewing
{
// Nil == Show apps from all sources.
var source: Source?
let source: Source?
private(set) var category: StoreCategory? {
didSet {
self.updateDataSource()
self.update()
}
}
private lazy var dataSource = self.makeDataSource()
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
@@ -33,12 +40,24 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
init?(source: Source?, coder: NSCoder)
{
self.source = source
self.category = nil
super.init(coder: coder)
}
init?(category: StoreCategory?, coder: NSCoder)
{
self.source = nil
self.category = category
super.init(coder: coder)
}
required init?(coder: NSCoder)
{
self.source = nil
self.category = nil
super.init(coder: coder)
}
@@ -91,6 +110,19 @@ class BrowseViewController: UICollectionViewController, PeekPopPreviewing
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
let actions = self?.makeCategoryActions() ?? []
completion(actions)
}
])
self.navigationItem.titleMenuProvider = { _ in menu }
}
self.navigationItem.largeTitleDisplayMode = .never
if #available(iOS 16, *)
{
@@ -140,6 +172,14 @@ private extension BrowseViewController
let filterPredicate = NSPredicate(format: "%K == %@", #keyPath(StoreApp._source), source)
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filterPredicate, predicate])
}
else if let category = self.category
{
let categoryPredicate = switch category {
case .other: StoreApp.otherCategoryPredicate
default: NSPredicate(format: "%K == %@", #keyPath(StoreApp._category), category.rawValue)
}
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [categoryPredicate, predicate])
}
else
{
fetchRequest.predicate = predicate
@@ -281,6 +321,57 @@ private extension BrowseViewController
self.placeholderView.activityIndicatorView.stopAnimating()
}
if let category = self.category
{
self.title = category.localizedName
}
else
{
self.title = NSLocalizedString("Browse", comment: "")
}
}
func makeCategoryActions() -> [UIAction]
{
let handler = { [weak self] (category: StoreCategory) in
self?.category = category
}
let fetchRequest = NSFetchRequest(entityName: StoreApp.entity().name!) as NSFetchRequest<NSDictionary>
fetchRequest.resultType = .dictionaryResultType
fetchRequest.returnsDistinctResults = true
fetchRequest.propertiesToFetch = [#keyPath(StoreApp._category)]
fetchRequest.predicate = StoreApp.visibleAppsPredicate
do
{
let dictionaries = try DatabaseManager.shared.viewContext.fetch(fetchRequest)
// Keep nil values
let categories = dictionaries.map { $0[#keyPath(StoreApp._category)] as? String? ?? nil }.map { rawCategory -> StoreCategory in
guard let rawCategory else { return .other }
return StoreCategory(rawValue: rawCategory) ?? .other
}
let sortedCategories = Set(categories).sorted(by: { $0.localizedName.localizedStandardCompare($1.localizedName) == .orderedAscending })
let actions = sortedCategories.map { category in
let state: UIAction.State = (category == self.category) ? .on : .off
return UIAction(title: category.localizedName, image: UIImage(systemName: category.symbolName), state: state) { _ in
handler(category)
}
}
return actions
}
catch
{
Logger.main.error("Failed to fetch categories. \(error.localizedDescription, privacy: .public)")
return []
}
}
@available(iOS 15, *)
func prepareAppSorting()

View File

@@ -594,6 +594,13 @@ public extension StoreApp
return predicate
}
class var otherCategoryPredicate: NSPredicate {
let knownCategories = StoreCategory.allCases.lazy.filter { $0 != .other }.map { $0.rawValue }
let predicate = NSPredicate(format: "%K == nil OR NOT (%K IN %@)", #keyPath(StoreApp._category), #keyPath(StoreApp._category), Array(knownCategories))
return predicate
}
@nonobjc class func fetchRequest() -> NSFetchRequest<StoreApp>
{
return NSFetchRequest<StoreApp>(entityName: "StoreApp")