From fb77ceddf2bea9a8f17c52c831247cf44a44268c Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 10 May 2022 17:15:42 -0700 Subject: [PATCH] use menu to display item filters --- .../App/Views/Home/HomeFeedViewIOS.swift | 21 +++--- .../App/Views/Home/HomeFeedViewModel.swift | 58 +++-------------- .../Sources/Models/LinkedItemFilter.swift | 65 +++++++++++++++++++ .../OmnivoreKit/Sources/Views/TextChip.swift | 6 +- 4 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index c70f0c646..69b5028c7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -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 } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 1d46f3235..82d595cdb 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -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 = 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:") diff --git a/apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift b/apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift new file mode 100644 index 000000000..cc58226ce --- /dev/null +++ b/apple/OmnivoreKit/Sources/Models/LinkedItemFilter.swift @@ -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]) + } + } +} diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index 4e0ada9c4..20a6a4d13 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -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