mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-01-11 20:10:27 +00:00
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 <bdashore3@proton.me>
This commit is contained in:
parent
4a87d86e76
commit
cbe3d17be1
10 changed files with 166 additions and 160 deletions
|
|
@ -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 = "<group>"; };
|
||||
0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealDebridWrapper.swift; sourceTree = "<group>"; };
|
||||
0CA148D1288903F000DE2211 /* MainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
||||
0CA148D3288903F000DE2211 /* SearchResultsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
|
||||
0CA148D4288903F000DE2211 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
0CA3B23328C2658700616D3A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
||||
0CA3B23628C2660700616D3A /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -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" */;
|
||||
|
|
|
|||
|
|
@ -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<DebridType> = [] {
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -18,13 +18,23 @@ class ScrapingViewModel: ObservableObject {
|
|||
|
||||
var runningSearchTask: Task<Void, Error>?
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue