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:
kingbri 2022-08-08 15:46:43 -04:00
parent 6a90dab386
commit c82cb1819d
7 changed files with 89 additions and 20 deletions

View file

@ -16,6 +16,7 @@
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 */; };
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 */; };
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -251,6 +253,7 @@
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
0C32FB522890D19D002BD219 /* AboutView.swift */,
0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -395,6 +398,7 @@
buildActionMask = 2147483647;
files = (
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */,
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,

View file

@ -7,6 +7,12 @@
import SwiftUI
enum ViewTab {
case search
case sources
case settings
}
class NavigationViewModel: ObservableObject {
// Used between SearchResultsView and MagnetChoiceView
enum ChoiceSheetType: Identifiable {
@ -20,6 +26,9 @@ class NavigationViewModel: ObservableObject {
@Published var currentChoiceSheet: ChoiceSheetType?
@Published var selectedTab: ViewTab = .search
@Published var showSearchProgress: Bool = false
// Used between SourceListView and SourceSettingsView
@Published var showSourceSettings: Bool = false
@Published var selectedSource: Source?

View file

@ -27,14 +27,21 @@ class ScrapingViewModel: ObservableObject {
var toastModel: ToastViewModel?
let byteCountFormatter: ByteCountFormatter = .init()
@Published var runningSearchTask: Task<Void, Error>?
@Published var searchResults: [SearchResult] = []
@Published var searchText: String = ""
@Published var selectedSearchResult: SearchResult?
@Published var filteredSource: Source?
@Published var currentSourceName: String?
@MainActor
public func scanSources(sources: [Source]) async {
if sources.isEmpty {
Task { @MainActor in
toastModel?.toastType = .info
toastModel?.toastDescription = "There are no sources to search!"
}
print("Sources empty")
return
}
@ -43,6 +50,8 @@ class ScrapingViewModel: ObservableObject {
for source in sources {
if source.enabled {
currentSourceName = source.name
// Default to HTML scraping
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
}
@ -115,7 +129,15 @@ class ScrapingViewModel: ObservableObject {
let html = String(data: data, encoding: .ascii)
return html
} 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)")
return nil

View file

@ -10,7 +10,7 @@ import SwiftUI
struct ContentView: View {
@EnvironmentObject var scrapingModel: ScrapingViewModel
@EnvironmentObject var debridManager: DebridManager
@EnvironmentObject var navigationModel: NavigationViewModel
@EnvironmentObject var navModel: NavigationViewModel
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
@ -71,17 +71,21 @@ struct ContentView: View {
}
.searchable(text: $scrapingModel.searchText)
.onSubmit(of: .search) {
Task {
scrapingModel.runningSearchTask = Task {
navModel.showSearchProgress = true
await scrapingModel.scanSources(sources: sources.compactMap { $0 })
if realDebridEnabled {
await debridManager.populateDebridHashes(scrapingModel.searchResults)
}
navModel.showSearchProgress = false
}
}
.navigationTitle("Search")
}
.sheet(item: $navigationModel.currentChoiceSheet) { item in
.sheet(item: $navModel.currentChoiceSheet) { item in
Group {
switch item {
case .magnet:

View file

@ -7,36 +7,29 @@
import SwiftUI
enum Tab {
case search
case sources
case settings
}
struct MainView: View {
@EnvironmentObject var navModel: NavigationViewModel
@EnvironmentObject var toastModel: ToastViewModel
@State private var tabSelection: Tab = .search
var body: some View {
TabView(selection: $tabSelection) {
TabView(selection: $navModel.selectedTab) {
ContentView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
.tag(Tab.search)
.tag(ViewTab.search)
SourcesView()
.tabItem {
Label("Sources", systemImage: "doc.text")
}
.tag(Tab.sources)
.tag(ViewTab.sources)
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag(Tab.settings)
.tag(ViewTab.settings)
}
.overlay {
VStack {

View 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()
}
}

View file

@ -9,10 +9,11 @@ import SwiftUI
struct SearchResultsView: View {
@Environment(\.isSearching) var isSearching
@Environment(\.dismissSearch) var dismissSearch
@EnvironmentObject var scrapingModel: ScrapingViewModel
@EnvironmentObject var debridManager: DebridManager
@EnvironmentObject var navigationModel: NavigationViewModel
@EnvironmentObject var navModel: NavigationViewModel
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
@ -28,14 +29,14 @@ struct SearchResultsView: View {
case .full:
Task {
await debridManager.fetchRdDownload(searchResult: result)
navigationModel.currentChoiceSheet = .magnet
navModel.currentChoiceSheet = .magnet
}
case .partial:
if debridManager.setSelectedRdResult(result: result) {
navigationModel.currentChoiceSheet = .batch
navModel.currentChoiceSheet = .batch
}
case .none:
navigationModel.currentChoiceSheet = .magnet
navModel.currentChoiceSheet = .magnet
}
} label: {
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
// Clear the results array on cancel
if !changed {
scrapingModel.searchResults = []
}