Searching: Cleanup existing searches
If a user searched after cancelling the search the first time, the first search would still continue. Assign the search task to navigation view and automatically cancel it and dismiss the searchbar when the user switches to a different tab. Also add a ProgressView to show which source is being parsed. Signed-off-by: kingbri <bdashore3@gmail.com>
This commit is contained in:
parent
159f648762
commit
44e4f74258
7 changed files with 89 additions and 20 deletions
|
|
@ -16,6 +16,7 @@
|
||||||
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */; };
|
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */; };
|
||||||
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */; };
|
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */; };
|
||||||
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */; };
|
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */; };
|
||||||
|
0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */; };
|
||||||
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
||||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
|
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
|
||||||
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; };
|
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; };
|
||||||
|
|
@ -75,6 +76,7 @@
|
||||||
0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = "<group>"; };
|
0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = "<group>"; };
|
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = "<group>"; };
|
||||||
|
0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchProgressView.swift; sourceTree = "<group>"; };
|
||||||
0C733286289C4C820058D1FE /* SourceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsView.swift; sourceTree = "<group>"; };
|
0C733286289C4C820058D1FE /* SourceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsView.swift; sourceTree = "<group>"; };
|
||||||
0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
|
|
@ -251,6 +253,7 @@
|
||||||
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
|
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
|
||||||
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
|
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
|
||||||
0C32FB522890D19D002BD219 /* AboutView.swift */,
|
0C32FB522890D19D002BD219 /* AboutView.swift */,
|
||||||
|
0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -395,6 +398,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
|
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
|
||||||
|
0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */,
|
||||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
|
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
|
||||||
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
|
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
|
||||||
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
|
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
enum ViewTab {
|
||||||
|
case search
|
||||||
|
case sources
|
||||||
|
case settings
|
||||||
|
}
|
||||||
|
|
||||||
class NavigationViewModel: ObservableObject {
|
class NavigationViewModel: ObservableObject {
|
||||||
// Used between SearchResultsView and MagnetChoiceView
|
// Used between SearchResultsView and MagnetChoiceView
|
||||||
enum ChoiceSheetType: Identifiable {
|
enum ChoiceSheetType: Identifiable {
|
||||||
|
|
@ -20,6 +26,9 @@ class NavigationViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var currentChoiceSheet: ChoiceSheetType?
|
@Published var currentChoiceSheet: ChoiceSheetType?
|
||||||
|
|
||||||
|
@Published var selectedTab: ViewTab = .search
|
||||||
|
@Published var showSearchProgress: Bool = false
|
||||||
|
|
||||||
// Used between SourceListView and SourceSettingsView
|
// Used between SourceListView and SourceSettingsView
|
||||||
@Published var showSourceSettings: Bool = false
|
@Published var showSourceSettings: Bool = false
|
||||||
@Published var selectedSource: Source?
|
@Published var selectedSource: Source?
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,21 @@ class ScrapingViewModel: ObservableObject {
|
||||||
var toastModel: ToastViewModel?
|
var toastModel: ToastViewModel?
|
||||||
let byteCountFormatter: ByteCountFormatter = .init()
|
let byteCountFormatter: ByteCountFormatter = .init()
|
||||||
|
|
||||||
|
@Published var runningSearchTask: Task<Void, Error>?
|
||||||
@Published var searchResults: [SearchResult] = []
|
@Published var searchResults: [SearchResult] = []
|
||||||
@Published var searchText: String = ""
|
@Published var searchText: String = ""
|
||||||
@Published var selectedSearchResult: SearchResult?
|
@Published var selectedSearchResult: SearchResult?
|
||||||
@Published var filteredSource: Source?
|
@Published var filteredSource: Source?
|
||||||
|
@Published var currentSourceName: String?
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public func scanSources(sources: [Source]) async {
|
public func scanSources(sources: [Source]) async {
|
||||||
if sources.isEmpty {
|
if sources.isEmpty {
|
||||||
|
Task { @MainActor in
|
||||||
|
toastModel?.toastType = .info
|
||||||
|
toastModel?.toastDescription = "There are no sources to search!"
|
||||||
|
}
|
||||||
|
|
||||||
print("Sources empty")
|
print("Sources empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -43,6 +50,8 @@ class ScrapingViewModel: ObservableObject {
|
||||||
|
|
||||||
for source in sources {
|
for source in sources {
|
||||||
if source.enabled {
|
if source.enabled {
|
||||||
|
currentSourceName = source.name
|
||||||
|
|
||||||
// Default to HTML scraping
|
// Default to HTML scraping
|
||||||
let preferredParser = SourcePreferredParser(rawValue: source.preferredParser) ?? .none
|
let preferredParser = SourcePreferredParser(rawValue: source.preferredParser) ?? .none
|
||||||
|
|
||||||
|
|
@ -97,6 +106,11 @@ class ScrapingViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the task is cancelled, return
|
||||||
|
if let searchTask = runningSearchTask, searchTask.isCancelled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
searchResults = tempResults
|
searchResults = tempResults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +129,15 @@ class ScrapingViewModel: ObservableObject {
|
||||||
let html = String(data: data, encoding: .ascii)
|
let html = String(data: data, encoding: .ascii)
|
||||||
return html
|
return html
|
||||||
} catch {
|
} catch {
|
||||||
toastModel?.toastDescription = "Error in fetching data \(error)"
|
let error = error as NSError
|
||||||
|
|
||||||
|
switch error.code {
|
||||||
|
case -999:
|
||||||
|
toastModel?.toastType = .info
|
||||||
|
toastModel?.toastDescription = "Search cancelled"
|
||||||
|
default:
|
||||||
|
toastModel?.toastDescription = "Error in fetching data \(error)"
|
||||||
|
}
|
||||||
print("Error in fetching data \(error)")
|
print("Error in fetching data \(error)")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
@EnvironmentObject var navigationModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
||||||
|
|
||||||
|
|
@ -71,17 +71,21 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
.searchable(text: $scrapingModel.searchText)
|
.searchable(text: $scrapingModel.searchText)
|
||||||
.onSubmit(of: .search) {
|
.onSubmit(of: .search) {
|
||||||
Task {
|
scrapingModel.runningSearchTask = Task {
|
||||||
|
navModel.showSearchProgress = true
|
||||||
|
|
||||||
await scrapingModel.scanSources(sources: sources.compactMap { $0 })
|
await scrapingModel.scanSources(sources: sources.compactMap { $0 })
|
||||||
|
|
||||||
if realDebridEnabled {
|
if realDebridEnabled {
|
||||||
await debridManager.populateDebridHashes(scrapingModel.searchResults)
|
await debridManager.populateDebridHashes(scrapingModel.searchResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navModel.showSearchProgress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Search")
|
.navigationTitle("Search")
|
||||||
}
|
}
|
||||||
.sheet(item: $navigationModel.currentChoiceSheet) { item in
|
.sheet(item: $navModel.currentChoiceSheet) { item in
|
||||||
Group {
|
Group {
|
||||||
switch item {
|
switch item {
|
||||||
case .magnet:
|
case .magnet:
|
||||||
|
|
|
||||||
|
|
@ -7,36 +7,29 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum Tab {
|
|
||||||
case search
|
|
||||||
case sources
|
|
||||||
case settings
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
@EnvironmentObject var toastModel: ToastViewModel
|
@EnvironmentObject var toastModel: ToastViewModel
|
||||||
|
|
||||||
@State private var tabSelection: Tab = .search
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView(selection: $tabSelection) {
|
TabView(selection: $navModel.selectedTab) {
|
||||||
ContentView()
|
ContentView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Search", systemImage: "magnifyingglass")
|
Label("Search", systemImage: "magnifyingglass")
|
||||||
}
|
}
|
||||||
.tag(Tab.search)
|
.tag(ViewTab.search)
|
||||||
|
|
||||||
SourcesView()
|
SourcesView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Sources", systemImage: "doc.text")
|
Label("Sources", systemImage: "doc.text")
|
||||||
}
|
}
|
||||||
.tag(Tab.sources)
|
.tag(ViewTab.sources)
|
||||||
|
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Settings", systemImage: "gear")
|
Label("Settings", systemImage: "gear")
|
||||||
}
|
}
|
||||||
.tag(Tab.settings)
|
.tag(ViewTab.settings)
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
|
||||||
20
Ferrite/Views/SearchProgressView.swift
Normal file
20
Ferrite/Views/SearchProgressView.swift
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// SearchProgressView.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 8/8/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SearchProgressView: View {
|
||||||
|
var body: some View {
|
||||||
|
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchProgressView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
SearchProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,10 +9,11 @@ import SwiftUI
|
||||||
|
|
||||||
struct SearchResultsView: View {
|
struct SearchResultsView: View {
|
||||||
@Environment(\.isSearching) var isSearching
|
@Environment(\.isSearching) var isSearching
|
||||||
|
@Environment(\.dismissSearch) var dismissSearch
|
||||||
|
|
||||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
@EnvironmentObject var navigationModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
||||||
|
|
||||||
|
|
@ -28,14 +29,14 @@ struct SearchResultsView: View {
|
||||||
case .full:
|
case .full:
|
||||||
Task {
|
Task {
|
||||||
await debridManager.fetchRdDownload(searchResult: result)
|
await debridManager.fetchRdDownload(searchResult: result)
|
||||||
navigationModel.currentChoiceSheet = .magnet
|
navModel.currentChoiceSheet = .magnet
|
||||||
}
|
}
|
||||||
case .partial:
|
case .partial:
|
||||||
if debridManager.setSelectedRdResult(result: result) {
|
if debridManager.setSelectedRdResult(result: result) {
|
||||||
navigationModel.currentChoiceSheet = .batch
|
navModel.currentChoiceSheet = .batch
|
||||||
}
|
}
|
||||||
case .none:
|
case .none:
|
||||||
navigationModel.currentChoiceSheet = .magnet
|
navModel.currentChoiceSheet = .magnet
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text(result.title)
|
Text(result.title)
|
||||||
|
|
@ -50,7 +51,23 @@ struct SearchResultsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.overlay {
|
||||||
|
if scrapingModel.searchResults.isEmpty, navModel.showSearchProgress {
|
||||||
|
VStack(spacing: 5) {
|
||||||
|
ProgressView()
|
||||||
|
Text("Loading \(scrapingModel.currentSourceName ?? "")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: navModel.selectedTab) { tab in
|
||||||
|
// Cancel the search if tab is switched
|
||||||
|
if tab != .search, isSearching {
|
||||||
|
scrapingModel.runningSearchTask?.cancel()
|
||||||
|
dismissSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
.onChange(of: isSearching) { changed in
|
.onChange(of: isSearching) { changed in
|
||||||
|
// Clear the results array on cancel
|
||||||
if !changed {
|
if !changed {
|
||||||
scrapingModel.searchResults = []
|
scrapingModel.searchResults = []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue