From cbe3d17be17d43c2c6b0ed8aa9fed7126eb12e33 Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 27 Feb 2023 12:22:36 -0500 Subject: [PATCH] Ferrite: Fix search and add progressive loading The searchbar had a lot of lag when scrolling down the search results view. This was due to a shared searchText variable which updated every time the searchbar text changed and caused UI blocking. Migrate searchText to a local variable and destroy the child SearchResultsView as it's not needed at this time (may come back with v0.7 due to searchable). Also sources now display results progressively without a ProgressView blocking when each source loads which allows the user to view media faster. Signed-off-by: kingbri --- Ferrite.xcodeproj/project.pbxproj | 38 +++--- Ferrite/ViewModels/DebridManager.swift | 11 +- Ferrite/ViewModels/NavigationViewModel.swift | 4 - Ferrite/ViewModels/ScrapingViewModel.swift | 41 ++++-- Ferrite/ViewModels/ToastViewModel.swift | 22 ++++ Ferrite/Views/ContentView.swift | 124 +++++++++++------- Ferrite/Views/LibraryView.swift | 10 +- Ferrite/Views/MainView.swift | 15 ++- Ferrite/Views/SearchResultsView.swift | 60 --------- .../Views/SheetViews/BatchChoiceView.swift | 1 + 10 files changed, 166 insertions(+), 160 deletions(-) delete mode 100644 Ferrite/Views/SearchResultsView.swift diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index d2f29cb..f6f5a18 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; }; 0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; }; 0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.swift */; }; + 0C2DD4CF29A6D47400293FC3 /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = 0C2DD4CE29A6D47400293FC3 /* SwiftUIX */; }; 0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */; }; 0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; }; 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; }; @@ -61,7 +62,6 @@ 0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */; }; 0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */; }; 0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; }; - 0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7376EF28A97D1400D60918 /* SwiftUIX */; }; 0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */; }; 0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */; }; 0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */; }; @@ -102,7 +102,6 @@ 0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148CF288903F000DE2211 /* ToastViewModel.swift */; }; 0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */; }; 0CA148E9288903F000DE2211 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D1288903F000DE2211 /* MainView.swift */; }; - 0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D3288903F000DE2211 /* SearchResultsView.swift */; }; 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D4288903F000DE2211 /* ContentView.swift */; }; 0CA3B23428C2658700616D3A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23328C2658700616D3A /* LibraryView.swift */; }; 0CA3B23728C2660700616D3A /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23628C2660700616D3A /* HistoryView.swift */; }; @@ -226,7 +225,6 @@ 0CA148CF288903F000DE2211 /* ToastViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastViewModel.swift; sourceTree = ""; }; 0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealDebridWrapper.swift; sourceTree = ""; }; 0CA148D1288903F000DE2211 /* MainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; - 0CA148D3288903F000DE2211 /* SearchResultsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = ""; }; 0CA148D4288903F000DE2211 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 0CA3B23328C2658700616D3A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; 0CA3B23628C2660700616D3A /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; @@ -267,8 +265,8 @@ 0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */, 0C448BE929A135F100F4E266 /* Introspect-Static in Frameworks */, 0C64A4B4288903680079976D /* Base32 in Frameworks */, + 0C2DD4CF29A6D47400293FC3 /* SwiftUIX in Frameworks */, 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */, - 0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */, 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */, 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */, 0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */, @@ -521,7 +519,6 @@ 0C0755C22934241F00ECA142 /* SheetViews */, 0CA148D1288903F000DE2211 /* MainView.swift */, 0CA148D4288903F000DE2211 /* ContentView.swift */, - 0CA148D3288903F000DE2211 /* SearchResultsView.swift */, 0CA3B23328C2658700616D3A /* LibraryView.swift */, 0C3E00D1296F4FD200ECECB2 /* PluginsView.swift */, 0CA148BB288903F000DE2211 /* SettingsView.swift */, @@ -626,10 +623,10 @@ 0C64A4B3288903680079976D /* Base32 */, 0C64A4B6288903880079976D /* KeychainSwift */, 0C4CFC452897030D00AD9FAD /* Regex */, - 0C7376EF28A97D1400D60918 /* SwiftUIX */, 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */, 0CDDDE042935235E006810B1 /* BetterSafariView */, 0C448BE829A135F100F4E266 /* Introspect-Static */, + 0C2DD4CE29A6D47400293FC3 /* SwiftUIX */, ); productName = Torrenter; productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */; @@ -664,10 +661,10 @@ 0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */, 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */, 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */, - 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */, 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, 0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */, 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, + 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */, ); productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */; projectDirPath = ""; @@ -831,7 +828,6 @@ 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */, 0CD5F1FD299C083B00476DDB /* PluginPickerView.swift in Sources */, 0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */, - 0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */, 0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */, 0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */, ); @@ -1048,6 +1044,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftUIX/SwiftUIX"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.1.3; + }; + }; 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/siteline/SwiftUI-Introspect/"; @@ -1080,14 +1084,6 @@ kind = branch; }; }; - 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SwiftUIX/SwiftUIX"; - requirement = { - branch = master; - kind = branch; - }; - }; 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON"; @@ -1115,6 +1111,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0C2DD4CE29A6D47400293FC3 /* SwiftUIX */ = { + isa = XCSwiftPackageProductDependency; + package = 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */; + productName = SwiftUIX; + }; 0C448BE829A135F100F4E266 /* Introspect-Static */ = { isa = XCSwiftPackageProductDependency; package = 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; @@ -1135,11 +1136,6 @@ package = 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */; productName = KeychainSwift; }; - 0C7376EF28A97D1400D60918 /* SwiftUIX */ = { - isa = XCSwiftPackageProductDependency; - package = 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */; - productName = SwiftUIX; - }; 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */ = { isa = XCSwiftPackageProductDependency; package = 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; diff --git a/Ferrite/ViewModels/DebridManager.swift b/Ferrite/ViewModels/DebridManager.swift index 8498868..b91e4a5 100644 --- a/Ferrite/ViewModels/DebridManager.swift +++ b/Ferrite/ViewModels/DebridManager.swift @@ -19,7 +19,6 @@ public class DebridManager: ObservableObject { // UI Variables @Published var showWebView: Bool = false @Published var showAuthSession: Bool = false - @Published var showLoadingProgress: Bool = false // Service agnostic variables @Published var enabledDebrids: Set = [] { @@ -50,6 +49,7 @@ public class DebridManager: ObservableObject { var selectedRealDebridFile: RealDebrid.IAFile? var selectedRealDebridID: String? + // TODO: Maybe make these generic? // RealDebrid cloud variables @Published var realDebridCloudTorrents: [RealDebrid.UserTorrentsResponse] = [] @Published var realDebridCloudDownloads: [RealDebrid.UserDownloadsResponse] = [] @@ -479,10 +479,13 @@ public class DebridManager: ObservableObject { public func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async { defer { currentDebridTask = nil - showLoadingProgress = false + toastModel?.hideIndeterminateToast() } - showLoadingProgress = true + toastModel?.updateIndeterminateToast("Loading content", cancelAction: { + self.currentDebridTask?.cancel() + self.currentDebridTask = nil + }) switch selectedDebridType { case .realDebrid: @@ -557,7 +560,7 @@ public class DebridManager: ObservableObject { await deleteRdTorrent(torrentID: selectedRealDebridID, presentError: false) } - showLoadingProgress = false + toastModel?.hideIndeterminateToast() } } diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index d3460e2..aed154a 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -40,9 +40,6 @@ class NavigationViewModel: ObservableObject { case actions } - @Published var isEditingSearch: Bool = false - @Published var isSearching: Bool = false - @Published var selectedMagnet: Magnet? @Published var selectedHistoryInfo: HistoryEntryJson? @Published var resultFromCloud: Bool = false @@ -60,7 +57,6 @@ class NavigationViewModel: ObservableObject { @Published var showLocalActivitySheet = false @Published var selectedTab: ViewTab = .search - @Published var showSearchProgress: Bool = false // Used between SourceListView and SourceSettingsView @Published var showSourceSettings: Bool = false diff --git a/Ferrite/ViewModels/ScrapingViewModel.swift b/Ferrite/ViewModels/ScrapingViewModel.swift index ab7ae86..9aff244 100644 --- a/Ferrite/ViewModels/ScrapingViewModel.swift +++ b/Ferrite/ViewModels/ScrapingViewModel.swift @@ -18,13 +18,23 @@ class ScrapingViewModel: ObservableObject { var runningSearchTask: Task? @Published var searchResults: [SearchResult] = [] - @Published var searchText: String = "" @Published var filteredSource: Source? @Published var currentSourceName: String? + // Only add results with valid magnet hashes to the search results array @MainActor func updateSearchResults(newResults: [SearchResult]) { - searchResults = newResults + searchResults += newResults.filter { $0.magnet.hash != nil } + } + + @MainActor + func clearSearchResults() { + searchResults = [] + } + + func cancelCurrentTask() { + runningSearchTask?.cancel() + runningSearchTask = nil } // Utility function to print source specific errors @@ -38,7 +48,7 @@ class ScrapingViewModel: ObservableObject { print(newDescription) } - public func scanSources(sources: [Source]) async { + public func scanSources(sources: [Source], searchText: String) async { if sources.isEmpty { await toastModel?.updateToastDescription("There are no sources to search!", newToastType: .info) @@ -46,13 +56,20 @@ class ScrapingViewModel: ObservableObject { return } - var tempResults: [SearchResult] = [] + await clearSearchResults() + + await toastModel?.updateIndeterminateToast("Loading", cancelAction: { + self.cancelCurrentTask() + }) for source in sources { + // If the search is cancelled, return + if let runningSearchTask, runningSearchTask.isCancelled { + return + } + if source.enabled { - Task { @MainActor in - currentSourceName = source.name - } + await toastModel?.updateIndeterminateToast("Loading \(source.name)", cancelAction: nil) guard let baseUrl = source.baseUrl else { await toastModel?.updateToastDescription("The base URL could not be found for source \(source.name)") @@ -86,7 +103,7 @@ class ScrapingViewModel: ObservableObject { let html = String(data: data, encoding: .utf8) { let sourceResults = await scrapeHtml(source: source, baseUrl: baseUrl, html: html) - tempResults += sourceResults + await updateSearchResults(newResults: sourceResults) } } case .rss: @@ -111,7 +128,7 @@ class ScrapingViewModel: ObservableObject { let rss = String(data: data, encoding: .utf8) { let sourceResults = await scrapeRss(source: source, rss: rss) - tempResults += sourceResults + await updateSearchResults(newResults: sourceResults) } } case .siteApi: @@ -155,7 +172,7 @@ class ScrapingViewModel: ObservableObject { if let data { let sourceResults = await scrapeJson(source: source, jsonData: data) - tempResults += sourceResults + await updateSearchResults(newResults: sourceResults) } } case .none: @@ -164,12 +181,10 @@ class ScrapingViewModel: ObservableObject { } } - // If the task is cancelled, return + // If the search is cancelled, return if let searchTask = runningSearchTask, searchTask.isCancelled { return } - - await updateSearchResults(newResults: tempResults) } // Checks the base URL for any website data then iterates through the fallback URLs diff --git a/Ferrite/ViewModels/ToastViewModel.swift b/Ferrite/ViewModels/ToastViewModel.swift index d3f1704..28045a4 100644 --- a/Ferrite/ViewModels/ToastViewModel.swift +++ b/Ferrite/ViewModels/ToastViewModel.swift @@ -35,6 +35,10 @@ class ToastViewModel: ObservableObject { @Published var showToast: Bool = false + @Published var indeterminateToastDescription: String? = nil + @Published var indeterminateCancelAction: (() -> ())? = nil + @Published var showIndeterminateToast: Bool = false + public func updateToastDescription(_ description: String, newToastType: ToastType? = nil) { if let newToastType { toastType = newToastType @@ -43,6 +47,24 @@ class ToastViewModel: ObservableObject { toastDescription = description } + public func updateIndeterminateToast(_ description: String, cancelAction: (() -> ())?) { + indeterminateToastDescription = description + + if let cancelAction { + indeterminateCancelAction = cancelAction + } + + if !showIndeterminateToast { + showIndeterminateToast = true + } + } + + public func hideIndeterminateToast() { + showIndeterminateToast = false + indeterminateToastDescription = "" + indeterminateCancelAction = nil + } + // Default the toast type to error since the majority of toasts are errors @Published var toastType: ToastType = .error } diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index aaa4787..d4e40c6 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -13,57 +13,89 @@ struct ContentView: View { @EnvironmentObject var debridManager: DebridManager @EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var pluginManager: PluginManager + @EnvironmentObject var toastModel: ToastViewModel + + @State private var isEditingSearch = false + @State private var isSearching = false + @State private var searchText: String = "" var body: some View { NavView { - SearchResultsView() - .listStyle(.insetGrouped) - .navigationTitle("Search") - .navigationSearchBar { - SearchBar("Search", - text: $scrapingModel.searchText, - isEditing: $navModel.isEditingSearch, - onCommit: { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask = Task { - navModel.isSearching = true - navModel.showSearchProgress = true - - let sources = pluginManager.fetchInstalledSources() - await scrapingModel.scanSources(sources: sources) - - if debridManager.enabledDebrids.count > 0, !scrapingModel.searchResults.isEmpty { - debridManager.clearIAValues() - - // Remove magnets that don't have a hash - let magnets = scrapingModel.searchResults.compactMap { - if let magnetHash = $0.magnet.hash { - return Magnet(hash: magnetHash, link: $0.magnet.link) - } else { - return nil - } - } - await debridManager.populateDebridIA(magnets) - } - - navModel.showSearchProgress = false - } - }) - .showsCancelButton(navModel.isEditingSearch || navModel.isSearching) - .onCancel { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask?.cancel() - scrapingModel.runningSearchTask = nil - navModel.isSearching = false - scrapingModel.searchText = "" - } + List { + ForEach(scrapingModel.searchResults, id: \.self) { result in + if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil { + SearchResultButtonView(result: result) + } } - .navigationSearchBarHiddenWhenScrolling(false) - .customScopeBar { - SearchFilterHeaderView() - .environmentObject(scrapingModel) - .environmentObject(debridManager) + } + .listStyle(.insetGrouped) + .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 20 : -20) + .overlay { + if scrapingModel.searchResults.isEmpty && isSearching && scrapingModel.runningSearchTask == nil { + Text("No results found") } + } + .onChange(of: scrapingModel.searchResults) { _ in + // Cleans up any leftover search results in the event of an abrupt cancellation + if !isSearching { + scrapingModel.searchResults = [] + } + } + .onChange(of: navModel.selectedTab) { tab in + // Cancel the search if tab is switched while search is in progress + if tab != .search, scrapingModel.runningSearchTask != nil { + scrapingModel.searchResults = [] + scrapingModel.runningSearchTask?.cancel() + scrapingModel.runningSearchTask = nil + isSearching = false + searchText = "" + } + } + .navigationTitle("Search") + .navigationSearchBar { + SearchBar( + "Search", + text: $searchText, + isEditing: $isEditingSearch, + onCommit: { + if let runningSearchTask = scrapingModel.runningSearchTask, runningSearchTask.isCancelled { + scrapingModel.runningSearchTask = nil + return + } + + scrapingModel.runningSearchTask = Task { + isSearching = true + + let sources = pluginManager.fetchInstalledSources() + await scrapingModel.scanSources(sources: sources, searchText: searchText) + + if debridManager.enabledDebrids.count > 0, !scrapingModel.searchResults.isEmpty { + debridManager.clearIAValues() + + let magnets = scrapingModel.searchResults.map(\.magnet) + await debridManager.populateDebridIA(magnets) + } + + toastModel.hideIndeterminateToast() + scrapingModel.runningSearchTask = nil + } + } + ) + .showsCancelButton(isEditingSearch || isSearching) + .onCancel { + scrapingModel.searchResults = [] + scrapingModel.runningSearchTask?.cancel() + scrapingModel.runningSearchTask = nil + isSearching = false + searchText = "" + } + } + .navigationSearchBarHiddenWhenScrolling(false) + } + .customScopeBar { + SearchFilterHeaderView() + .environmentObject(scrapingModel) + .environmentObject(debridManager) } } } diff --git a/Ferrite/Views/LibraryView.swift b/Ferrite/Views/LibraryView.swift index 6629c4b..89dfdd9 100644 --- a/Ferrite/Views/LibraryView.swift +++ b/Ferrite/Views/LibraryView.swift @@ -76,11 +76,6 @@ struct LibraryView: View { } } .navigationSearchBarHiddenWhenScrolling(false) - .customScopeBar { - LibraryPickerView() - .environmentObject(debridManager) - .environmentObject(navModel) - } .environment(\.editMode, $editMode) } .overlay { @@ -99,6 +94,11 @@ struct LibraryView: View { } } } + .customScopeBar { + LibraryPickerView() + .environmentObject(debridManager) + .environmentObject(navModel) + } .onChange(of: navModel.libraryPickerSelection) { _ in editMode = .inactive } diff --git a/Ferrite/Views/MainView.swift b/Ferrite/Views/MainView.swift index 69b2bd7..7403048 100644 --- a/Ferrite/Views/MainView.swift +++ b/Ferrite/Views/MainView.swift @@ -171,17 +171,18 @@ struct MainView: View { .cornerRadius(10) } - if debridManager.showLoadingProgress { + if toastModel.showIndeterminateToast { VStack { - Text("Loading content") + Text(toastModel.indeterminateToastDescription ?? "Loading...") HStack { IndeterminateProgressView() - Button("Cancel") { - debridManager.currentDebridTask?.cancel() - debridManager.currentDebridTask = nil - debridManager.showLoadingProgress = false + if let cancelAction = toastModel.indeterminateCancelAction { + Button("Cancel") { + cancelAction() + toastModel.hideIndeterminateToast() + } } } } @@ -198,7 +199,7 @@ struct MainView: View { .foregroundColor(.clear) .frame(height: 60) } - .animation(.easeInOut(duration: 0.3), value: toastModel.showToast || debridManager.showLoadingProgress) + .animation(.easeInOut(duration: 0.3), value: toastModel.showToast || toastModel.showIndeterminateToast) } } } diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift deleted file mode 100644 index dc2b699..0000000 --- a/Ferrite/Views/SearchResultsView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// SearchResultsView.swift -// Ferrite -// -// Created by Brian Dashore on 7/11/22. -// - -import SwiftUI - -struct SearchResultsView: View { - @EnvironmentObject var scrapingModel: ScrapingViewModel - @EnvironmentObject var navModel: NavigationViewModel - @EnvironmentObject var debridManager: DebridManager - - var body: some View { - List { - ForEach(scrapingModel.searchResults, id: \.self) { result in - if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil { - SearchResultButtonView(result: result) - } - } - } - .listStyle(.insetGrouped) - .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 20 : -20) - .overlay { - if scrapingModel.searchResults.isEmpty { - if navModel.showSearchProgress { - VStack(spacing: 5) { - ProgressView() - Text("Loading \(scrapingModel.currentSourceName ?? "")") - } - } else if navModel.isSearching, scrapingModel.runningSearchTask != nil { - Text("No results found") - } - } - } - .onChange(of: navModel.selectedTab) { tab in - // Cancel the search if tab is switched while search is in progress - if tab != .search, navModel.showSearchProgress { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask?.cancel() - scrapingModel.runningSearchTask = nil - navModel.isSearching = false - scrapingModel.searchText = "" - } - } - .onChange(of: scrapingModel.searchResults) { _ in - // Cleans up any leftover search results in the event of an abrupt cancellation - if !navModel.isSearching { - scrapingModel.searchResults = [] - } - } - } -} - -struct SearchResultsView_Previews: PreviewProvider { - static var previews: some View { - SearchResultsView() - } -} diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index b413b43..7497ff2 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -14,6 +14,7 @@ struct BatchChoiceView: View { let backgroundContext = PersistenceController.shared.backgroundContext + // TODO: Make this generic for IA(?) and add searchbar var body: some View { NavView { List {