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 availableSources: [SourceJson] = []
@Published var availableActions: [ActionJson] = [] @Published var availableActions: [ActionJson] = []
@Published var filteredInstalledSources: [Source] = []
@Published var showActionErrorAlert = false @Published var showActionErrorAlert = false
@Published var actionErrorAlertMessage: String = "" @Published var actionErrorAlertMessage: String = ""
@ -263,17 +265,6 @@ public class PluginManager: ObservableObject {
return Application.shared.appVersion >= minVersion 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 @MainActor
public func runDefaultAction(urlString: String?, navModel: NavigationViewModel) { public func runDefaultAction(urlString: String?, navModel: NavigationViewModel) {
let context = PersistenceController.shared.backgroundContext let context = PersistenceController.shared.backgroundContext

View file

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

View file

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

View file

@ -13,12 +13,13 @@ struct SearchResultsView: View {
@EnvironmentObject var scrapingModel: ScrapingViewModel @EnvironmentObject var scrapingModel: ScrapingViewModel
@EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var navModel: NavigationViewModel
@EnvironmentObject var pluginManager: PluginManager
@AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false @AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false
var body: some View { var body: some View {
ForEach(scrapingModel.searchResults, id: \.self) { result in 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) SearchResultButtonView(result: result)
} }
} }
@ -49,14 +50,5 @@ struct SearchResultsView: View {
scrapingModel.runningSearchTask = nil 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 @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch: Bool = false
@FetchRequest(
entity: Source.entity(),
sortDescriptors: []
) var sources: FetchedResults<Source>
@State private var isSearching = false @State private var isSearching = false
@State private var isEditingSearch = false
@State private var dismissAction: () -> Void = {} @State private var dismissAction: () -> Void = {}
var body: some View { var body: some View {
@ -27,30 +33,40 @@ struct ContentView: View {
.listStyle(.insetGrouped) .listStyle(.insetGrouped)
.inlinedList(inset: 20) .inlinedList(inset: 20)
.navigationTitle("Search") .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( .expandedSearchable(
text: $scrapingModel.searchText, text: $scrapingModel.searchText,
isSearching: $isSearching, isSearching: $isSearching,
isEditingSearch: $isEditingSearch,
prompt: navModel.searchPrompt, prompt: navModel.searchPrompt,
dismiss: $dismissAction, dismiss: $dismissAction,
scopeBarContent: { scopeBarContent: {
SearchFilterHeaderView() SearchFilterHeaderView(sources: sources)
}, },
onSubmit: { onSubmit: {
if let runningSearchTask = scrapingModel.runningSearchTask, runningSearchTask.isCancelled { if
let runningSearchTask = scrapingModel.runningSearchTask,
runningSearchTask.isCancelled
{
scrapingModel.runningSearchTask = nil scrapingModel.runningSearchTask = nil
return return
} }
scrapingModel.runningSearchTask = Task { executeSearch()
let sources = pluginManager.fetchInstalledSources()
await scrapingModel.scanSources(
sources: sources,
debridManager: debridManager
)
logManager.hideIndeterminateToast()
scrapingModel.runningSearchTask = nil
}
} }
) )
.autocorrectionDisabled(!autocorrectSearch) .autocorrectionDisabled(!autocorrectSearch)
@ -58,6 +74,26 @@ struct ContentView: View {
.onAppear { .onAppear {
navModel.getSearchPrompt() 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 // A dismissAction must be added in the parent view struct due to lifecycle issues
func expandedSearchable(text: Binding<String>, func expandedSearchable(text: Binding<String>,
isSearching: Binding<Bool>? = nil, isSearching: Binding<Bool>? = nil,
isEditingSearch: Binding<Bool>? = nil,
prompt: String? = nil, prompt: String? = nil,
dismiss: Binding<() -> Void>? = nil, dismiss: Binding<() -> Void>? = nil,
scopeBarContent: @escaping () -> some View = { scopeBarContent: @escaping () -> some View = {
@ -23,6 +24,7 @@ public extension View {
SearchBar( SearchBar(
searchText: text, searchText: text,
isSearching: isSearching ?? Binding(get: { true }, set: { _, _ in }), isSearching: isSearching ?? Binding(get: { true }, set: { _, _ in }),
isEditingSearch: isEditingSearch ?? Binding(get: { true }, set: { _, _ in }),
prompt: prompt ?? "Search", prompt: prompt ?? "Search",
dismiss: dismiss ?? Binding(get: { {} }, set: { _, _ in }), dismiss: dismiss ?? Binding(get: { {} }, set: { _, _ in }),
scopeBarContent: scopeBarContent, scopeBarContent: scopeBarContent,
@ -84,6 +86,7 @@ struct SearchBar<ScopeContent: View>: UIViewControllerRepresentable {
// Passed in vars // Passed in vars
@Binding var searchText: String @Binding var searchText: String
@Binding var isSearching: Bool @Binding var isSearching: Bool
@Binding var isEditingSearch: Bool
var prompt: String var prompt: String
@Binding var dismiss: () -> Void @Binding var dismiss: () -> Void
let scopeBarContent: () -> ScopeContent let scopeBarContent: () -> ScopeContent
@ -101,6 +104,14 @@ struct SearchBar<ScopeContent: View>: UIViewControllerRepresentable {
parent.searchText = searchText parent.searchText = searchText
} }
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
parent.isEditingSearch = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
parent.isEditingSearch = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let onSubmit = parent.onSubmit { if let onSubmit = parent.onSubmit {
onSubmit() onSubmit()