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:
parent
9427ca271b
commit
4f303e1c1e
6 changed files with 78 additions and 45 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue