Filters: Fix source filtering

Make it so that when a user chooses a source to filter, only filter
that specific source when a search occurs.

Also fix the "no results found" overlay text by checking if the
search bar textfield is being edited or not by modifiying ESSearchable.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-04-09 16:20:23 -04:00
parent 9427ca271b
commit 4f303e1c1e
6 changed files with 78 additions and 45 deletions

View file

@ -16,6 +16,8 @@ public class PluginManager: ObservableObject {
@Published var availableSources: [SourceJson] = []
@Published var availableActions: [ActionJson] = []
@Published var filteredInstalledSources: [Source] = []
@Published var showActionErrorAlert = false
@Published var actionErrorAlertMessage: String = ""
@ -263,17 +265,6 @@ public class PluginManager: ObservableObject {
return Application.shared.appVersion >= minVersion
}
// Fetches sources using the background context
public func fetchInstalledSources() -> [Source] {
let backgroundContext = PersistenceController.shared.backgroundContext
if let sources = try? backgroundContext.fetch(Source.fetchRequest()) {
return sources.compactMap { $0 }
} else {
return []
}
}
@MainActor
public func runDefaultAction(urlString: String?, navModel: NavigationViewModel) {
let context = PersistenceController.shared.backgroundContext

View file

@ -61,8 +61,6 @@ class ScrapingViewModel: ObservableObject {
logManager?.updateIndeterminateToast("Loading sources", cancelAction: nil)
}
@Published var filteredSource: Source?
// Utility function to print source specific errors
func sendSourceError(_ description: String) async {
await logManager?.error(description, showToast: false)
@ -84,6 +82,7 @@ class ScrapingViewModel: ObservableObject {
await debridManager.clearIAValues()
}
await clearCurrentSourceNames()
await clearSearchResults()
await logManager?.updateIndeterminateToast("Loading sources", cancelAction: {

View file

@ -10,32 +10,36 @@ import SwiftUI
struct SearchFilterHeaderView: View {
@Environment(\.verticalSizeClass) var verticalSizeClass
@EnvironmentObject var scrapingModel: ScrapingViewModel
@EnvironmentObject var debridManager: DebridManager
@EnvironmentObject var pluginManager: PluginManager
@FetchRequest(
entity: Source.entity(),
sortDescriptors: []
) var sources: FetchedResults<Source>
var sources: FetchedResults<Source>
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 6) {
// MARK: - Source filter picker
// TODO: Make this use multiple selections
Menu {
Picker("", selection: $scrapingModel.filteredSource) {
Text("None").tag(nil as Source?)
Picker("", selection: $pluginManager.filteredInstalledSources) {
Text("All").tag([] as [Source])
ForEach(sources, id: \.self) { source in
if source.enabled {
Text(source.name)
.tag(Source?.some(source))
.tag([source])
}
}
}
} label: {
FilterLabelView(name: scrapingModel.filteredSource?.name ?? "Source")
FilterLabelView(
name: pluginManager.filteredInstalledSources.first?.name ?? "Source"
)
}
.id(scrapingModel.filteredSource)
.id(pluginManager.filteredInstalledSources)
// MARK: - Selected debrid picker
DebridPickerView {
FilterLabelView(name: debridManager.selectedDebridType?.toString() ?? "Debrid")

View file

@ -13,12 +13,13 @@ struct SearchResultsView: View {
@EnvironmentObject var scrapingModel: ScrapingViewModel
@EnvironmentObject var navModel: NavigationViewModel
@EnvironmentObject var pluginManager: PluginManager
@AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false
var body: some View {
ForEach(scrapingModel.searchResults, id: \.self) { result in
if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil {
if pluginManager.filteredInstalledSources.isEmpty || pluginManager.filteredInstalledSources.contains(where: { result.source == $0.name }) {
SearchResultButtonView(result: result)
}
}
@ -49,14 +50,5 @@ struct SearchResultsView: View {
scrapingModel.runningSearchTask = nil
}
}
.overlay {
if
scrapingModel.searchResults.isEmpty,
isSearching,
scrapingModel.runningSearchTask == nil
{
Text("No results found")
}
}
}
}

View file

@ -16,7 +16,13 @@ struct ContentView: View {
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch: Bool = false
@FetchRequest(
entity: Source.entity(),
sortDescriptors: []
) var sources: FetchedResults<Source>
@State private var isSearching = false
@State private var isEditingSearch = false
@State private var dismissAction: () -> Void = {}
var body: some View {
@ -27,30 +33,40 @@ struct ContentView: View {
.listStyle(.insetGrouped)
.inlinedList(inset: 20)
.navigationTitle("Search")
.overlay {
if
scrapingModel.searchResults.isEmpty,
isSearching,
scrapingModel.runningSearchTask == nil,
!isEditingSearch
{
Text(
pluginManager.filteredInstalledSources.isEmpty ?
"No results found" :
"No results found. Check your source filter and redo your search."
)
.padding(.horizontal)
}
}
.expandedSearchable(
text: $scrapingModel.searchText,
isSearching: $isSearching,
isEditingSearch: $isEditingSearch,
prompt: navModel.searchPrompt,
dismiss: $dismissAction,
scopeBarContent: {
SearchFilterHeaderView()
SearchFilterHeaderView(sources: sources)
},
onSubmit: {
if let runningSearchTask = scrapingModel.runningSearchTask, runningSearchTask.isCancelled {
if
let runningSearchTask = scrapingModel.runningSearchTask,
runningSearchTask.isCancelled
{
scrapingModel.runningSearchTask = nil
return
}
scrapingModel.runningSearchTask = Task {
let sources = pluginManager.fetchInstalledSources()
await scrapingModel.scanSources(
sources: sources,
debridManager: debridManager
)
logManager.hideIndeterminateToast()
scrapingModel.runningSearchTask = nil
}
executeSearch()
}
)
.autocorrectionDisabled(!autocorrectSearch)
@ -58,6 +74,26 @@ struct ContentView: View {
.onAppear {
navModel.getSearchPrompt()
}
.onChange(of: isEditingSearch) { newVal in
print(newVal)
}
}
}
func executeSearch() {
scrapingModel.runningSearchTask = Task {
await scrapingModel.scanSources(
sources:
scrapingModel.searchResults.isEmpty ?
sources.compactMap { $0 } :
(pluginManager.filteredInstalledSources.isEmpty ?
sources.compactMap { $0 } :
pluginManager.filteredInstalledSources),
debridManager: debridManager
)
logManager.hideIndeterminateToast()
scrapingModel.runningSearchTask = nil
}
}
}

View file

@ -11,6 +11,7 @@ public extension View {
// A dismissAction must be added in the parent view struct due to lifecycle issues
func expandedSearchable(text: Binding<String>,
isSearching: Binding<Bool>? = nil,
isEditingSearch: Binding<Bool>? = nil,
prompt: String? = nil,
dismiss: Binding<() -> Void>? = nil,
scopeBarContent: @escaping () -> some View = {
@ -23,6 +24,7 @@ public extension View {
SearchBar(
searchText: text,
isSearching: isSearching ?? Binding(get: { true }, set: { _, _ in }),
isEditingSearch: isEditingSearch ?? Binding(get: { true }, set: { _, _ in }),
prompt: prompt ?? "Search",
dismiss: dismiss ?? Binding(get: { {} }, set: { _, _ in }),
scopeBarContent: scopeBarContent,
@ -84,6 +86,7 @@ struct SearchBar<ScopeContent: View>: UIViewControllerRepresentable {
// Passed in vars
@Binding var searchText: String
@Binding var isSearching: Bool
@Binding var isEditingSearch: Bool
var prompt: String
@Binding var dismiss: () -> Void
let scopeBarContent: () -> ScopeContent
@ -101,6 +104,14 @@ struct SearchBar<ScopeContent: View>: UIViewControllerRepresentable {
parent.searchText = searchText
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
parent.isEditingSearch = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
parent.isEditingSearch = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let onSubmit = parent.onSubmit {
onSubmit()