mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-04-21 00:42:07 +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 */; };
|
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; };
|
||||||
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; };
|
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; };
|
||||||
0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.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 */; };
|
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 */; };
|
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; };
|
||||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.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 */; };
|
0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */; };
|
||||||
0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */; };
|
0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */; };
|
||||||
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.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 */; };
|
0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */; };
|
||||||
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */; };
|
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 */; };
|
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 */; };
|
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148CF288903F000DE2211 /* ToastViewModel.swift */; };
|
||||||
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */; };
|
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */; };
|
||||||
0CA148E9288903F000DE2211 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D1288903F000DE2211 /* MainView.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 */; };
|
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D4288903F000DE2211 /* ContentView.swift */; };
|
||||||
0CA3B23428C2658700616D3A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23328C2658700616D3A /* LibraryView.swift */; };
|
0CA3B23428C2658700616D3A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23328C2658700616D3A /* LibraryView.swift */; };
|
||||||
0CA3B23728C2660700616D3A /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23628C2660700616D3A /* HistoryView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0CA3B23628C2660700616D3A /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -267,8 +265,8 @@
|
||||||
0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */,
|
0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */,
|
||||||
0C448BE929A135F100F4E266 /* Introspect-Static in Frameworks */,
|
0C448BE929A135F100F4E266 /* Introspect-Static in Frameworks */,
|
||||||
0C64A4B4288903680079976D /* Base32 in Frameworks */,
|
0C64A4B4288903680079976D /* Base32 in Frameworks */,
|
||||||
|
0C2DD4CF29A6D47400293FC3 /* SwiftUIX in Frameworks */,
|
||||||
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */,
|
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */,
|
||||||
0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */,
|
|
||||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
|
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
|
||||||
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
|
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
|
||||||
0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */,
|
0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */,
|
||||||
|
|
@ -521,7 +519,6 @@
|
||||||
0C0755C22934241F00ECA142 /* SheetViews */,
|
0C0755C22934241F00ECA142 /* SheetViews */,
|
||||||
0CA148D1288903F000DE2211 /* MainView.swift */,
|
0CA148D1288903F000DE2211 /* MainView.swift */,
|
||||||
0CA148D4288903F000DE2211 /* ContentView.swift */,
|
0CA148D4288903F000DE2211 /* ContentView.swift */,
|
||||||
0CA148D3288903F000DE2211 /* SearchResultsView.swift */,
|
|
||||||
0CA3B23328C2658700616D3A /* LibraryView.swift */,
|
0CA3B23328C2658700616D3A /* LibraryView.swift */,
|
||||||
0C3E00D1296F4FD200ECECB2 /* PluginsView.swift */,
|
0C3E00D1296F4FD200ECECB2 /* PluginsView.swift */,
|
||||||
0CA148BB288903F000DE2211 /* SettingsView.swift */,
|
0CA148BB288903F000DE2211 /* SettingsView.swift */,
|
||||||
|
|
@ -626,10 +623,10 @@
|
||||||
0C64A4B3288903680079976D /* Base32 */,
|
0C64A4B3288903680079976D /* Base32 */,
|
||||||
0C64A4B6288903880079976D /* KeychainSwift */,
|
0C64A4B6288903880079976D /* KeychainSwift */,
|
||||||
0C4CFC452897030D00AD9FAD /* Regex */,
|
0C4CFC452897030D00AD9FAD /* Regex */,
|
||||||
0C7376EF28A97D1400D60918 /* SwiftUIX */,
|
|
||||||
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */,
|
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */,
|
||||||
0CDDDE042935235E006810B1 /* BetterSafariView */,
|
0CDDDE042935235E006810B1 /* BetterSafariView */,
|
||||||
0C448BE829A135F100F4E266 /* Introspect-Static */,
|
0C448BE829A135F100F4E266 /* Introspect-Static */,
|
||||||
|
0C2DD4CE29A6D47400293FC3 /* SwiftUIX */,
|
||||||
);
|
);
|
||||||
productName = Torrenter;
|
productName = Torrenter;
|
||||||
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
|
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
|
||||||
|
|
@ -664,10 +661,10 @@
|
||||||
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */,
|
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */,
|
||||||
0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||||
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */,
|
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */,
|
||||||
0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */,
|
|
||||||
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
|
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
|
||||||
0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */,
|
0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */,
|
||||||
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
|
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
|
||||||
|
0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
|
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
|
@ -831,7 +828,6 @@
|
||||||
0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */,
|
0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */,
|
||||||
0CD5F1FD299C083B00476DDB /* PluginPickerView.swift in Sources */,
|
0CD5F1FD299C083B00476DDB /* PluginPickerView.swift in Sources */,
|
||||||
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */,
|
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */,
|
||||||
0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */,
|
|
||||||
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
||||||
0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */,
|
0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
@ -1048,6 +1044,14 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference 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" */ = {
|
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect/";
|
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect/";
|
||||||
|
|
@ -1080,14 +1084,6 @@
|
||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/SwiftUIX/SwiftUIX";
|
|
||||||
requirement = {
|
|
||||||
branch = master;
|
|
||||||
kind = branch;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
|
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
|
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
|
||||||
|
|
@ -1115,6 +1111,11 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
0C2DD4CE29A6D47400293FC3 /* SwiftUIX */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */;
|
||||||
|
productName = SwiftUIX;
|
||||||
|
};
|
||||||
0C448BE829A135F100F4E266 /* Introspect-Static */ = {
|
0C448BE829A135F100F4E266 /* Introspect-Static */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
package = 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
||||||
|
|
@ -1135,11 +1136,6 @@
|
||||||
package = 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
package = 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||||
productName = KeychainSwift;
|
productName = KeychainSwift;
|
||||||
};
|
};
|
||||||
0C7376EF28A97D1400D60918 /* SwiftUIX */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */;
|
|
||||||
productName = SwiftUIX;
|
|
||||||
};
|
|
||||||
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */ = {
|
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
|
package = 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ public class DebridManager: ObservableObject {
|
||||||
// UI Variables
|
// UI Variables
|
||||||
@Published var showWebView: Bool = false
|
@Published var showWebView: Bool = false
|
||||||
@Published var showAuthSession: Bool = false
|
@Published var showAuthSession: Bool = false
|
||||||
@Published var showLoadingProgress: Bool = false
|
|
||||||
|
|
||||||
// Service agnostic variables
|
// Service agnostic variables
|
||||||
@Published var enabledDebrids: Set<DebridType> = [] {
|
@Published var enabledDebrids: Set<DebridType> = [] {
|
||||||
|
|
@ -50,6 +49,7 @@ public class DebridManager: ObservableObject {
|
||||||
var selectedRealDebridFile: RealDebrid.IAFile?
|
var selectedRealDebridFile: RealDebrid.IAFile?
|
||||||
var selectedRealDebridID: String?
|
var selectedRealDebridID: String?
|
||||||
|
|
||||||
|
// TODO: Maybe make these generic?
|
||||||
// RealDebrid cloud variables
|
// RealDebrid cloud variables
|
||||||
@Published var realDebridCloudTorrents: [RealDebrid.UserTorrentsResponse] = []
|
@Published var realDebridCloudTorrents: [RealDebrid.UserTorrentsResponse] = []
|
||||||
@Published var realDebridCloudDownloads: [RealDebrid.UserDownloadsResponse] = []
|
@Published var realDebridCloudDownloads: [RealDebrid.UserDownloadsResponse] = []
|
||||||
|
|
@ -479,10 +479,13 @@ public class DebridManager: ObservableObject {
|
||||||
public func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async {
|
public func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async {
|
||||||
defer {
|
defer {
|
||||||
currentDebridTask = nil
|
currentDebridTask = nil
|
||||||
showLoadingProgress = false
|
toastModel?.hideIndeterminateToast()
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoadingProgress = true
|
toastModel?.updateIndeterminateToast("Loading content", cancelAction: {
|
||||||
|
self.currentDebridTask?.cancel()
|
||||||
|
self.currentDebridTask = nil
|
||||||
|
})
|
||||||
|
|
||||||
switch selectedDebridType {
|
switch selectedDebridType {
|
||||||
case .realDebrid:
|
case .realDebrid:
|
||||||
|
|
@ -557,7 +560,7 @@ public class DebridManager: ObservableObject {
|
||||||
await deleteRdTorrent(torrentID: selectedRealDebridID, presentError: false)
|
await deleteRdTorrent(torrentID: selectedRealDebridID, presentError: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoadingProgress = false
|
toastModel?.hideIndeterminateToast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,6 @@ class NavigationViewModel: ObservableObject {
|
||||||
case actions
|
case actions
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var isEditingSearch: Bool = false
|
|
||||||
@Published var isSearching: Bool = false
|
|
||||||
|
|
||||||
@Published var selectedMagnet: Magnet?
|
@Published var selectedMagnet: Magnet?
|
||||||
@Published var selectedHistoryInfo: HistoryEntryJson?
|
@Published var selectedHistoryInfo: HistoryEntryJson?
|
||||||
@Published var resultFromCloud: Bool = false
|
@Published var resultFromCloud: Bool = false
|
||||||
|
|
@ -60,7 +57,6 @@ class NavigationViewModel: ObservableObject {
|
||||||
@Published var showLocalActivitySheet = false
|
@Published var showLocalActivitySheet = false
|
||||||
|
|
||||||
@Published var selectedTab: ViewTab = .search
|
@Published var selectedTab: ViewTab = .search
|
||||||
@Published var showSearchProgress: Bool = false
|
|
||||||
|
|
||||||
// Used between SourceListView and SourceSettingsView
|
// Used between SourceListView and SourceSettingsView
|
||||||
@Published var showSourceSettings: Bool = false
|
@Published var showSourceSettings: Bool = false
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,23 @@ class ScrapingViewModel: ObservableObject {
|
||||||
|
|
||||||
var runningSearchTask: Task<Void, Error>?
|
var runningSearchTask: Task<Void, Error>?
|
||||||
@Published var searchResults: [SearchResult] = []
|
@Published var searchResults: [SearchResult] = []
|
||||||
@Published var searchText: String = ""
|
|
||||||
@Published var filteredSource: Source?
|
@Published var filteredSource: Source?
|
||||||
@Published var currentSourceName: String?
|
@Published var currentSourceName: String?
|
||||||
|
|
||||||
|
// Only add results with valid magnet hashes to the search results array
|
||||||
@MainActor
|
@MainActor
|
||||||
func updateSearchResults(newResults: [SearchResult]) {
|
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
|
// Utility function to print source specific errors
|
||||||
|
|
@ -38,7 +48,7 @@ class ScrapingViewModel: ObservableObject {
|
||||||
print(newDescription)
|
print(newDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func scanSources(sources: [Source]) async {
|
public func scanSources(sources: [Source], searchText: String) async {
|
||||||
if sources.isEmpty {
|
if sources.isEmpty {
|
||||||
await toastModel?.updateToastDescription("There are no sources to search!", newToastType: .info)
|
await toastModel?.updateToastDescription("There are no sources to search!", newToastType: .info)
|
||||||
|
|
||||||
|
|
@ -46,13 +56,20 @@ class ScrapingViewModel: ObservableObject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tempResults: [SearchResult] = []
|
await clearSearchResults()
|
||||||
|
|
||||||
|
await toastModel?.updateIndeterminateToast("Loading", cancelAction: {
|
||||||
|
self.cancelCurrentTask()
|
||||||
|
})
|
||||||
|
|
||||||
for source in sources {
|
for source in sources {
|
||||||
|
// If the search is cancelled, return
|
||||||
|
if let runningSearchTask, runningSearchTask.isCancelled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if source.enabled {
|
if source.enabled {
|
||||||
Task { @MainActor in
|
await toastModel?.updateIndeterminateToast("Loading \(source.name)", cancelAction: nil)
|
||||||
currentSourceName = source.name
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let baseUrl = source.baseUrl else {
|
guard let baseUrl = source.baseUrl else {
|
||||||
await toastModel?.updateToastDescription("The base URL could not be found for source \(source.name)")
|
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 html = String(data: data, encoding: .utf8)
|
||||||
{
|
{
|
||||||
let sourceResults = await scrapeHtml(source: source, baseUrl: baseUrl, html: html)
|
let sourceResults = await scrapeHtml(source: source, baseUrl: baseUrl, html: html)
|
||||||
tempResults += sourceResults
|
await updateSearchResults(newResults: sourceResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .rss:
|
case .rss:
|
||||||
|
|
@ -111,7 +128,7 @@ class ScrapingViewModel: ObservableObject {
|
||||||
let rss = String(data: data, encoding: .utf8)
|
let rss = String(data: data, encoding: .utf8)
|
||||||
{
|
{
|
||||||
let sourceResults = await scrapeRss(source: source, rss: rss)
|
let sourceResults = await scrapeRss(source: source, rss: rss)
|
||||||
tempResults += sourceResults
|
await updateSearchResults(newResults: sourceResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .siteApi:
|
case .siteApi:
|
||||||
|
|
@ -155,7 +172,7 @@ class ScrapingViewModel: ObservableObject {
|
||||||
|
|
||||||
if let data {
|
if let data {
|
||||||
let sourceResults = await scrapeJson(source: source, jsonData: data)
|
let sourceResults = await scrapeJson(source: source, jsonData: data)
|
||||||
tempResults += sourceResults
|
await updateSearchResults(newResults: sourceResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .none:
|
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 {
|
if let searchTask = runningSearchTask, searchTask.isCancelled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateSearchResults(newResults: tempResults)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks the base URL for any website data then iterates through the fallback URLs
|
// 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 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) {
|
public func updateToastDescription(_ description: String, newToastType: ToastType? = nil) {
|
||||||
if let newToastType {
|
if let newToastType {
|
||||||
toastType = newToastType
|
toastType = newToastType
|
||||||
|
|
@ -43,6 +47,24 @@ class ToastViewModel: ObservableObject {
|
||||||
toastDescription = description
|
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
|
// Default the toast type to error since the majority of toasts are errors
|
||||||
@Published var toastType: ToastType = .error
|
@Published var toastType: ToastType = .error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,57 +13,89 @@ struct ContentView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
@EnvironmentObject var pluginManager: PluginManager
|
@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 {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
SearchResultsView()
|
List {
|
||||||
.listStyle(.insetGrouped)
|
ForEach(scrapingModel.searchResults, id: \.self) { result in
|
||||||
.navigationTitle("Search")
|
if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil {
|
||||||
.navigationSearchBar {
|
SearchResultButtonView(result: result)
|
||||||
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 = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.navigationSearchBarHiddenWhenScrolling(false)
|
}
|
||||||
.customScopeBar {
|
.listStyle(.insetGrouped)
|
||||||
SearchFilterHeaderView()
|
.inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 20 : -20)
|
||||||
.environmentObject(scrapingModel)
|
.overlay {
|
||||||
.environmentObject(debridManager)
|
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)
|
.navigationSearchBarHiddenWhenScrolling(false)
|
||||||
.customScopeBar {
|
|
||||||
LibraryPickerView()
|
|
||||||
.environmentObject(debridManager)
|
|
||||||
.environmentObject(navModel)
|
|
||||||
}
|
|
||||||
.environment(\.editMode, $editMode)
|
.environment(\.editMode, $editMode)
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
|
|
@ -99,6 +94,11 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.customScopeBar {
|
||||||
|
LibraryPickerView()
|
||||||
|
.environmentObject(debridManager)
|
||||||
|
.environmentObject(navModel)
|
||||||
|
}
|
||||||
.onChange(of: navModel.libraryPickerSelection) { _ in
|
.onChange(of: navModel.libraryPickerSelection) { _ in
|
||||||
editMode = .inactive
|
editMode = .inactive
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,17 +171,18 @@ struct MainView: View {
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
if debridManager.showLoadingProgress {
|
if toastModel.showIndeterminateToast {
|
||||||
VStack {
|
VStack {
|
||||||
Text("Loading content")
|
Text(toastModel.indeterminateToastDescription ?? "Loading...")
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
IndeterminateProgressView()
|
IndeterminateProgressView()
|
||||||
|
|
||||||
Button("Cancel") {
|
if let cancelAction = toastModel.indeterminateCancelAction {
|
||||||
debridManager.currentDebridTask?.cancel()
|
Button("Cancel") {
|
||||||
debridManager.currentDebridTask = nil
|
cancelAction()
|
||||||
debridManager.showLoadingProgress = false
|
toastModel.hideIndeterminateToast()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +199,7 @@ struct MainView: View {
|
||||||
.foregroundColor(.clear)
|
.foregroundColor(.clear)
|
||||||
.frame(height: 60)
|
.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
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
|
// TODO: Make this generic for IA(?) and add searchbar
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
List {
|
List {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue