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 = [] }