mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
use menu to display item filters
This commit is contained in:
parent
584aac6a3b
commit
fb77ceddf2
4 changed files with 89 additions and 61 deletions
|
|
@ -26,16 +26,6 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
|
|||
.refreshable {
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
// .searchable(
|
||||
// text: $viewModel.searchTerm
|
||||
// ) {
|
||||
// if viewModel.searchTerm.isEmpty {
|
||||
// Text("Inbox").searchCompletion("in:inbox ")
|
||||
// Text("All").searchCompletion("in:all ")
|
||||
// Text("Archived").searchCompletion("in:archive ")
|
||||
// Text("Files").searchCompletion("type:file ")
|
||||
// }
|
||||
// }
|
||||
.onChange(of: viewModel.searchTerm) { _ in
|
||||
// Maybe we should debounce this, but
|
||||
// it feels like it works ok without
|
||||
|
|
@ -44,6 +34,9 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
|
|||
.onChange(of: viewModel.selectedLabels) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onChange(of: viewModel.appliedFilter) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.sheet(item: $viewModel.itemUnderLabelEdit) { item in
|
||||
ApplyLabelsView(mode: .item(item), onSave: nil)
|
||||
}
|
||||
|
|
@ -138,6 +131,14 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
|
|||
ZStack(alignment: .bottom) {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
Menu(
|
||||
content: {
|
||||
ForEach(LinkedItemFilter.allCases, id: \.self) { filter in
|
||||
Button(filter.displayName, action: { viewModel.appliedFilter = filter })
|
||||
}
|
||||
},
|
||||
label: { TextChipButton.makeFilterButton(title: viewModel.appliedFilter.displayName) }
|
||||
)
|
||||
TextChipButton.makeAddLabelButton {
|
||||
showLabelsSheet = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Views
|
|||
@Published var itemToSnoozeID: String?
|
||||
@Published var selectedLinkItem: LinkedItem?
|
||||
@Published var showLoadingBar = false
|
||||
@Published var appliedFilter = LinkedItemFilter.all
|
||||
|
||||
var cursor: String?
|
||||
|
||||
|
|
@ -84,11 +85,11 @@ import Views
|
|||
cursor = queryResult.cursor
|
||||
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID))
|
||||
showLoadingBar = false
|
||||
} else if searchTermIsEmpty {
|
||||
} else if searchTerm.replacingOccurrences(of: " ", with: "").isEmpty {
|
||||
await dataService.viewContext.perform {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)]
|
||||
fetchRequest.predicate = self.itemRequestPredicate
|
||||
fetchRequest.predicate = self.appliedFilter.predicate
|
||||
// // TODO: Filter on label
|
||||
|
||||
if let fetchedItems = try? dataService.viewContext.fetch(fetchRequest) {
|
||||
|
|
@ -101,49 +102,6 @@ import Views
|
|||
}
|
||||
}
|
||||
|
||||
private var itemRequestPredicate: NSPredicate {
|
||||
let undeletedPredicate = NSPredicate(
|
||||
format: "%K != %i", #keyPath(LinkedItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue)
|
||||
)
|
||||
|
||||
if searchTerm.contains("in:all") {
|
||||
// include everything undeleted
|
||||
return undeletedPredicate
|
||||
}
|
||||
|
||||
if searchTerm.contains("in:archive") {
|
||||
let inArchivePredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: true) as NSNumber
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, inArchivePredicate])
|
||||
}
|
||||
|
||||
if searchTerm.contains("type:file") {
|
||||
// include pdf only
|
||||
let isPDFPredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(LinkedItem.contentReader), "PDF"
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, isPDFPredicate])
|
||||
}
|
||||
|
||||
// default to "in:inbox" (non-archived items)
|
||||
let notInArchivePredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: false) as NSNumber
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate])
|
||||
}
|
||||
|
||||
// Exclude filters when testing if user has enetered a search term
|
||||
private var searchTermIsEmpty: Bool {
|
||||
searchTerm
|
||||
.replacingOccurrences(of: "in:inbox", with: "")
|
||||
.replacingOccurrences(of: "in:all", with: "")
|
||||
.replacingOccurrences(of: "in:archive", with: "")
|
||||
.replacingOccurrences(of: "type:file", with: "")
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.isEmpty
|
||||
}
|
||||
|
||||
func setLinkArchived(dataService: DataService, objectID: NSManagedObjectID, archived: Bool) {
|
||||
// TODO: remove this by making list always fetch from Coredata
|
||||
guard let itemIndex = items.firstIndex(where: { $0.objectID == objectID }) else { return }
|
||||
|
|
@ -182,12 +140,12 @@ import Views
|
|||
isLoading = false
|
||||
}
|
||||
|
||||
private var searchQuery: String? {
|
||||
if searchTerm.isEmpty, selectedLabels.isEmpty {
|
||||
return nil
|
||||
}
|
||||
private var searchQuery: String {
|
||||
var query = "\(appliedFilter.queryString)"
|
||||
|
||||
var query = searchTerm
|
||||
if !searchTerm.isEmpty {
|
||||
query.append(" \(searchTerm)")
|
||||
}
|
||||
|
||||
if !selectedLabels.isEmpty {
|
||||
query.append(" label:")
|
||||
|
|
|
|||
65
apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift
Normal file
65
apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import Foundation
|
||||
|
||||
public enum LinkedItemFilter: CaseIterable {
|
||||
case inbox
|
||||
case all
|
||||
case archived
|
||||
case files
|
||||
}
|
||||
|
||||
public extension LinkedItemFilter {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .inbox:
|
||||
return "Inbox"
|
||||
case .all:
|
||||
return "All"
|
||||
case .archived:
|
||||
return "Archived"
|
||||
case .files:
|
||||
return "Files"
|
||||
}
|
||||
}
|
||||
|
||||
var queryString: String {
|
||||
switch self {
|
||||
case .inbox:
|
||||
return "in:inbox"
|
||||
case .all:
|
||||
return "in:all"
|
||||
case .archived:
|
||||
return "in:archive"
|
||||
case .files:
|
||||
return "type:file"
|
||||
}
|
||||
}
|
||||
|
||||
var predicate: NSPredicate {
|
||||
let undeletedPredicate = NSPredicate(
|
||||
format: "%K != %i", #keyPath(LinkedItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue)
|
||||
)
|
||||
|
||||
switch self {
|
||||
case .inbox:
|
||||
// non-archived items
|
||||
let notInArchivePredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: false) as NSNumber
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate])
|
||||
case .all:
|
||||
// include everything undeleted
|
||||
return undeletedPredicate
|
||||
case .archived:
|
||||
let inArchivePredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(LinkedItem.isArchived), Int(truncating: true) as NSNumber
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, inArchivePredicate])
|
||||
case .files:
|
||||
// include pdf only
|
||||
let isPDFPredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(LinkedItem.contentReader), "PDF"
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, isPDFPredicate])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,10 @@ public struct TextChipButton: View {
|
|||
TextChipButton(title: "Labels", color: .systemGray6, actionType: .show, onTap: onTap)
|
||||
}
|
||||
|
||||
public static func makeFilterButton(title: String) -> TextChipButton {
|
||||
TextChipButton(title: title, color: .systemGray6, actionType: .show, onTap: {})
|
||||
}
|
||||
|
||||
public static func makeShowOptionsButton(title: String, onTap: @escaping () -> Void) -> TextChipButton {
|
||||
TextChipButton(title: title, color: .appButtonBackground, actionType: .add, onTap: onTap)
|
||||
}
|
||||
|
|
@ -67,7 +71,7 @@ public struct TextChipButton: View {
|
|||
}
|
||||
}
|
||||
|
||||
init(title: String, color: Color, actionType: ActionType, onTap: @escaping () -> Void) {
|
||||
public init(title: String, color: Color, actionType: ActionType, onTap: @escaping () -> Void) {
|
||||
self.text = title
|
||||
self.color = color
|
||||
self.onTap = onTap
|
||||
|
|
|
|||
Loading…
Reference in a new issue