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 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
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue