From 44e4f74258d4cbdd43a4a7e806ebd15a98bb5131 Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 8 Aug 2022 15:46:43 -0400 Subject: [PATCH] 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 --- Ferrite.xcodeproj/project.pbxproj | 4 ++++ Ferrite/ViewModels/NavigationViewModel.swift | 9 +++++++ Ferrite/ViewModels/ScrapingViewModel.swift | 24 ++++++++++++++++++- Ferrite/Views/ContentView.swift | 10 +++++--- Ferrite/Views/MainView.swift | 17 ++++--------- Ferrite/Views/SearchProgressView.swift | 20 ++++++++++++++++ Ferrite/Views/SearchResultsView.swift | 25 ++++++++++++++++---- 7 files changed, 89 insertions(+), 20 deletions(-) create mode 100644 Ferrite/Views/SearchProgressView.swift diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index cd88957..6a4c11d 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -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 = ""; }; 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = ""; }; 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = ""; }; + 0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchProgressView.swift; sourceTree = ""; }; 0C733286289C4C820058D1FE /* SourceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsView.swift; sourceTree = ""; }; 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataClass.swift"; sourceTree = ""; }; 0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataProperties.swift"; sourceTree = ""; }; @@ -251,6 +253,7 @@ 0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */, 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */, 0C32FB522890D19D002BD219 /* AboutView.swift */, + 0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */, ); path = Views; sourceTree = ""; @@ -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 */, diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index 5b4e2f2..e7d8818 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -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? diff --git a/Ferrite/ViewModels/ScrapingViewModel.swift b/Ferrite/ViewModels/ScrapingViewModel.swift index 1406f86..f15a877 100644 --- a/Ferrite/ViewModels/ScrapingViewModel.swift +++ b/Ferrite/ViewModels/ScrapingViewModel.swift @@ -27,14 +27,21 @@ class ScrapingViewModel: ObservableObject { var toastModel: ToastViewModel? let byteCountFormatter: ByteCountFormatter = .init() + @Published var runningSearchTask: Task? @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 diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 1abab69..ad76303 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -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: diff --git a/Ferrite/Views/MainView.swift b/Ferrite/Views/MainView.swift index 489a807..e3b1c17 100644 --- a/Ferrite/Views/MainView.swift +++ b/Ferrite/Views/MainView.swift @@ -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 { diff --git a/Ferrite/Views/SearchProgressView.swift b/Ferrite/Views/SearchProgressView.swift new file mode 100644 index 0000000..0923ccb --- /dev/null +++ b/Ferrite/Views/SearchProgressView.swift @@ -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() + } +} diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift index 81a537d..775d107 100644 --- a/Ferrite/Views/SearchResultsView.swift +++ b/Ferrite/Views/SearchResultsView.swift @@ -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 = [] }