diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index 0bd3d04..497ae48 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB542890D1BF002BD219 /* UIApplication.swift */; }; 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB562890D1F2002BD219 /* ListRowViews.swift */; }; 0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */; }; + 0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */; }; + 0C41BC6528C2AEB900B47DD6 /* SearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */; }; 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */ = {isa = PBXBuildFile; productRef = 0C4CFC452897030D00AD9FAD /* Regex */; }; 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 */; }; @@ -25,6 +27,8 @@ 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; }; 0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */; }; 0C68135228BC1A7C00FAD890 /* GithubModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C68135128BC1A7C00FAD890 /* GithubModels.swift */; }; + 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 */; }; @@ -70,6 +74,11 @@ 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 */; }; + 0CA3B23928C2660D00616D3A /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23828C2660D00616D3A /* BookmarksView.swift */; }; + 0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */; }; + 0CA3B23D28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */; }; 0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */; }; 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; }; 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; }; @@ -93,12 +102,16 @@ 0C32FB542890D1BF002BD219 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = ""; }; 0C32FB562890D1F2002BD219 /* ListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViews.swift; sourceTree = ""; }; 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicFetchRequest.swift; sourceTree = ""; }; + 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultButtonView.swift; sourceTree = ""; }; + 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModels.swift; sourceTree = ""; }; 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 = ""; }; 0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubWrapper.swift; sourceTree = ""; }; 0C68135128BC1A7C00FAD890 /* GithubModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubModels.swift; sourceTree = ""; }; + 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalContextMenu.swift; sourceTree = ""; }; + 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenter.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 = ""; }; @@ -142,6 +155,11 @@ 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 = ""; }; + 0CA3B23828C2660D00616D3A /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = ""; }; + 0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+CoreDataClass.swift"; sourceTree = ""; }; + 0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+CoreDataProperties.swift"; sourceTree = ""; }; 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressView.swift; sourceTree = ""; }; 0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = ""; }; @@ -176,6 +194,8 @@ 0C0D50DE288DF72D0035ECC8 /* Classes */ = { isa = PBXGroup; children = ( + 0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */, + 0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */, 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */, 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */, 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */, @@ -201,6 +221,7 @@ 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */, 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */, 0C68135128BC1A7C00FAD890 /* GithubModels.swift */, + 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */, ); path = Models; sourceTree = ""; @@ -258,6 +279,7 @@ 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */, 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */, 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */, + 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */, ); path = CommonViews; sourceTree = ""; @@ -279,6 +301,7 @@ 0C32FB542890D1BF002BD219 /* UIApplication.swift */, 0C7D11FD28AA03FE00ED92DB /* View.swift */, 0C78041C28BFB3EA001E8CA3 /* String.swift */, + 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */, ); path = Extensions; sourceTree = ""; @@ -286,6 +309,7 @@ 0CA148EE2889061200DE2211 /* Views */ = { isa = PBXGroup; children = ( + 0CA3B23528C265FD00616D3A /* LibraryViews */, 0C794B65289DAC9F00DD1CC8 /* SourceViews */, 0CA148F02889062700DE2211 /* RepresentableViews */, 0CA148C0288903F000DE2211 /* CommonViews */, @@ -301,6 +325,8 @@ 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */, 0C32FB522890D19D002BD219 /* AboutView.swift */, 0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */, + 0CA3B23328C2658700616D3A /* LibraryView.swift */, + 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */, ); path = Views; sourceTree = ""; @@ -334,6 +360,15 @@ path = API; sourceTree = ""; }; + 0CA3B23528C265FD00616D3A /* LibraryViews */ = { + isa = PBXGroup; + children = ( + 0CA3B23828C2660D00616D3A /* BookmarksView.swift */, + 0CA3B23628C2660700616D3A /* HistoryView.swift */, + ); + path = LibraryViews; + sourceTree = ""; + }; 0CAF1C5F286F5C0D00296F86 = { isa = PBXGroup; children = ( @@ -454,8 +489,10 @@ 0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */, 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */, 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */, + 0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */, 0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */, 0CA148DB288903F000DE2211 /* NavView.swift in Sources */, + 0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */, 0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */, 0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */, 0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */, @@ -468,7 +505,10 @@ 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */, 0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */, 0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */, + 0CA3B23728C2660700616D3A /* HistoryView.swift in Sources */, + 0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */, 0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */, + 0CA3B23428C2658700616D3A /* LibraryView.swift in Sources */, 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */, 0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */, 0CA148E1288903F000DE2211 /* Collection.swift in Sources */, @@ -479,20 +519,24 @@ 0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */, 0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */, 0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */, + 0C41BC6528C2AEB900B47DD6 /* SearchModels.swift in Sources */, 0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */, 0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */, 0CA148E3288903F000DE2211 /* Task.swift in Sources */, 0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */, 0C68135228BC1A7C00FAD890 /* GithubModels.swift in Sources */, 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */, + 0CA3B23928C2660D00616D3A /* BookmarksView.swift in Sources */, 0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */, 0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */, 0CA148E6288903F000DE2211 /* WebView.swift in Sources */, + 0CA3B23D28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift in Sources */, 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */, 0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */, 0CA148E2288903F000DE2211 /* Data.swift in Sources */, 0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */, 0C7D11FC28AA01E900ED92DB /* DynamicAccentColor.swift in Sources */, + 0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */, 0CA05459288EE9E600850554 /* SourceManager.swift in Sources */, 0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */, 0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */, diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index fb4e8e3..ffd06f3 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -248,9 +248,21 @@ public class RealDebrid { } } - availableHashes.append(RealDebridIA(hash: hash, files: files, batches: batches)) + // TTL: 5 minutes + availableHashes.append( + RealDebridIA( + hash: hash, + expiryTimeStamp: Date().timeIntervalSince1970 + 300, + files: files, + batches: batches) + ) } else { - availableHashes.append(RealDebridIA(hash: hash)) + availableHashes.append( + RealDebridIA( + hash: hash, + expiryTimeStamp: Date().timeIntervalSince1970 + 300 + ) + ) } } diff --git a/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift b/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift new file mode 100644 index 0000000..dcd0f86 --- /dev/null +++ b/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift @@ -0,0 +1,25 @@ +// +// Bookmark+CoreDataClass.swift +// Ferrite +// +// Created by Brian Dashore on 9/2/22. +// +// + +import CoreData +import Foundation + +@objc(Bookmark) +public class Bookmark: NSManagedObject { + func toSearchResult() -> SearchResult { + SearchResult( + title: title, + source: source, + size: size, + magnetLink: magnetLink, + magnetHash: magnetHash, + seeders: seeders, + leechers: leechers + ) + } +} diff --git a/Ferrite/DataManagement/Classes/Bookmark+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/Bookmark+CoreDataProperties.swift new file mode 100644 index 0000000..6528695 --- /dev/null +++ b/Ferrite/DataManagement/Classes/Bookmark+CoreDataProperties.swift @@ -0,0 +1,32 @@ +// +// Bookmark+CoreDataProperties.swift +// Ferrite +// +// Created by Brian Dashore on 9/3/22. +// +// + +import Foundation +import CoreData + + +extension Bookmark { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Bookmark") + } + + @NSManaged public var leechers: String? + @NSManaged public var magnetHash: String? + @NSManaged public var magnetLink: String? + @NSManaged public var seeders: String? + @NSManaged public var size: String? + @NSManaged public var source: String + @NSManaged public var title: String? + @NSManaged public var orderNum: Int16 + +} + +extension Bookmark : Identifiable { + +} diff --git a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents index 99cfeeb..f2d6e3f 100644 --- a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents +++ b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents @@ -1,5 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Ferrite/Extensions/NotificationCenter.swift b/Ferrite/Extensions/NotificationCenter.swift new file mode 100644 index 0000000..be21d68 --- /dev/null +++ b/Ferrite/Extensions/NotificationCenter.swift @@ -0,0 +1,14 @@ +// +// NotificationCenter.swift +// Ferrite +// +// Created by Brian Dashore on 9/3/22. +// + +import Foundation + +extension Notification.Name { + static var didDeleteBookmark: Notification.Name { + return Notification.Name("Deleted bookmark") + } +} diff --git a/Ferrite/Extensions/View.swift b/Ferrite/Extensions/View.swift index d1dc471..e7debe2 100644 --- a/Ferrite/Extensions/View.swift +++ b/Ferrite/Extensions/View.swift @@ -36,4 +36,11 @@ extension View { func inlinedList() -> some View { modifier(InlinedList()) } + + func conditionalContextMenu( + id: ID, + @ViewBuilder _ internalContent: @escaping () -> InternalContent + ) -> some View { + modifier(ConditionalContextMenu(internalContent, id: id)) + } } diff --git a/Ferrite/Models/RealDebridModels.swift b/Ferrite/Models/RealDebridModels.swift index a9860ec..b070fd9 100644 --- a/Ferrite/Models/RealDebridModels.swift +++ b/Ferrite/Models/RealDebridModels.swift @@ -11,148 +11,149 @@ import Foundation // MARK: - device code endpoint public struct DeviceCodeResponse: Codable { - let deviceCode, userCode: String - let interval, expiresIn: Int - let verificationURL, directVerificationURL: String + let deviceCode, userCode: String + let interval, expiresIn: Int + let verificationURL, directVerificationURL: String - enum CodingKeys: String, CodingKey { - case deviceCode = "device_code" - case userCode = "user_code" - case interval - case expiresIn = "expires_in" - case verificationURL = "verification_url" - case directVerificationURL = "direct_verification_url" - } + enum CodingKeys: String, CodingKey { + case deviceCode = "device_code" + case userCode = "user_code" + case interval + case expiresIn = "expires_in" + case verificationURL = "verification_url" + case directVerificationURL = "direct_verification_url" + } } // MARK: - device credentials endpoint public struct DeviceCredentialsResponse: Codable { - let clientID, clientSecret: String? + let clientID, clientSecret: String? - enum CodingKeys: String, CodingKey { - case clientID = "client_id" - case clientSecret = "client_secret" - } + enum CodingKeys: String, CodingKey { + case clientID = "client_id" + case clientSecret = "client_secret" + } } // MARK: - token endpoint public struct TokenResponse: Codable { - let accessToken: String - let expiresIn: Int - let refreshToken, tokenType: String + let accessToken: String + let expiresIn: Int + let refreshToken, tokenType: String - enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case expiresIn = "expires_in" - case refreshToken = "refresh_token" - case tokenType = "token_type" - } + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case expiresIn = "expires_in" + case refreshToken = "refresh_token" + case tokenType = "token_type" + } } // MARK: - instantAvailability endpoint // Thanks Skitty! public struct InstantAvailabilityResponse: Codable { - var data: InstantAvailabilityData? + var data: InstantAvailabilityData? - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() - if let data = try? container.decode(InstantAvailabilityData.self) { - self.data = data - } - } + if let data = try? container.decode(InstantAvailabilityData.self) { + self.data = data + } + } } struct InstantAvailabilityData: Codable { - var rd: [[String: InstantAvailabilityInfo]] + var rd: [[String: InstantAvailabilityInfo]] } struct InstantAvailabilityInfo: Codable { - var filename: String - var filesize: Int + var filename: String + var filesize: Int } // MARK: - Instant Availability client side structures public struct RealDebridIA: Codable, Hashable { - let hash: String - var files: [RealDebridIAFile] = [] - var batches: [RealDebridIABatch] = [] + let hash: String + let expiryTimeStamp: Double + var files: [RealDebridIAFile] = [] + var batches: [RealDebridIABatch] = [] } public struct RealDebridIABatch: Codable, Hashable { - let files: [RealDebridIABatchFile] + let files: [RealDebridIABatchFile] } public struct RealDebridIABatchFile: Codable, Hashable { - let id: Int - let fileName: String + let id: Int + let fileName: String } public struct RealDebridIAFile: Codable, Hashable { - let name: String - let batchIndex: Int - let batchFileIndex: Int + let name: String + let batchIndex: Int + let batchFileIndex: Int } public enum RealDebridIAStatus: Codable, Hashable { - case full - case partial - case none + case full + case partial + case none } // MARK: - addMagnet endpoint public struct AddMagnetResponse: Codable { - let id: String - let uri: String + let id: String + let uri: String } // MARK: - torrentInfo endpoint struct TorrentInfoResponse: Codable { - let id, filename, originalFilename, hash: String - let bytes, originalBytes: Int - let host: String - let split, progress: Int - let status, added: String - let files: [TorrentInfoFile] - let links: [String] - let ended: String + let id, filename, originalFilename, hash: String + let bytes, originalBytes: Int + let host: String + let split, progress: Int + let status, added: String + let files: [TorrentInfoFile] + let links: [String] + let ended: String - enum CodingKeys: String, CodingKey { - case id, filename - case originalFilename = "original_filename" - case hash, bytes - case originalBytes = "original_bytes" - case host, split, progress, status, added, files, links, ended - } + enum CodingKeys: String, CodingKey { + case id, filename + case originalFilename = "original_filename" + case hash, bytes + case originalBytes = "original_bytes" + case host, split, progress, status, added, files, links, ended + } } struct TorrentInfoFile: Codable { - let id: Int - let path: String - let bytes, selected: Int + let id: Int + let path: String + let bytes, selected: Int } // MARK: - unrestrictLink endpoint struct UnrestrictLinkResponse: Codable { - let id, filename, mimeType: String - let filesize: Int - let link: String - let host: String - let hostIcon: String - let chunks, crc: Int - let download: String - let streamable: Int + let id, filename, mimeType: String + let filesize: Int + let link: String + let host: String + let hostIcon: String + let chunks, crc: Int + let download: String + let streamable: Int - enum CodingKeys: String, CodingKey { - case id, filename, mimeType, filesize, link, host - case hostIcon = "host_icon" - case chunks, crc, download, streamable - } + enum CodingKeys: String, CodingKey { + case id, filename, mimeType, filesize, link, host + case hostIcon = "host_icon" + case chunks, crc, download, streamable + } } diff --git a/Ferrite/Models/SearchModels.swift b/Ferrite/Models/SearchModels.swift new file mode 100644 index 0000000..2751a82 --- /dev/null +++ b/Ferrite/Models/SearchModels.swift @@ -0,0 +1,18 @@ +// +// SearchModels.swift +// Ferrite +// +// Created by Brian Dashore on 9/2/22. +// + +import Foundation + +public struct SearchResult: Hashable, Codable { + let title: String? + let source: String + let size: String? + let magnetLink: String? + let magnetHash: String? + let seeders: String? + let leechers: String? +} diff --git a/Ferrite/ViewModels/DebridManager.swift b/Ferrite/ViewModels/DebridManager.swift index b1a05a9..a579d21 100644 --- a/Ferrite/ViewModels/DebridManager.swift +++ b/Ferrite/ViewModels/DebridManager.swift @@ -10,8 +10,11 @@ import SwiftUI @MainActor public class DebridManager: ObservableObject { - // UI Variables + // Linked classes var toastModel: ToastViewModel? + let realDebrid: RealDebrid = .init() + + // UI Variables @Published var showWebView: Bool = false @Published var showLoadingProgress: Bool = false @@ -19,8 +22,6 @@ public class DebridManager: ObservableObject { @Published var currentDebridTask: Task? // RealDebrid auth variables - let realDebrid: RealDebrid = .init() - @Published var realDebridEnabled: Bool = false { didSet { UserDefaults.standard.set(realDebridEnabled, forKey: "RealDebrid.Enabled") @@ -31,7 +32,7 @@ public class DebridManager: ObservableObject { @Published var realDebridAuthUrl: String = "" // RealDebrid fetch variables - @Published var realDebridHashes: [RealDebridIA] = [] + @Published var realDebridIAValues: [RealDebridIA] = [] @Published var realDebridDownloadUrl: String = "" @Published var selectedRealDebridItem: RealDebridIA? @Published var selectedRealDebridFile: RealDebridIAFile? @@ -40,19 +41,30 @@ public class DebridManager: ObservableObject { realDebridEnabled = UserDefaults.standard.bool(forKey: "RealDebrid.Enabled") } - public func populateDebridHashes(_ searchResults: [SearchResult]) async { - var hashes: [String] = [] - - for result in searchResults { - if let hash = result.magnetHash { - hashes.append(hash) - } - } - + public func populateDebridHashes(_ resultHashes: [String]) async { do { - let debridHashes = try await realDebrid.instantAvailability(magnetHashes: hashes) + let now = Date() - realDebridHashes = debridHashes + // If a hash isn't found in the IA, update it + // If the hash is expired, remove it and update it + let sendHashes = resultHashes.filter { hash in + if let IAIndex = realDebridIAValues.firstIndex(where: { $0.hash == hash }) { + if now.timeIntervalSince1970 > realDebridIAValues[IAIndex].expiryTimeStamp { + realDebridIAValues.remove(at: IAIndex) + return true + } else { + return false + } + } else { + return true + } + } + + if !sendHashes.isEmpty { + let fetchedIAValues = try await realDebrid.instantAvailability(magnetHashes: sendHashes) + + realDebridIAValues += fetchedIAValues + } } catch { let error = error as NSError @@ -69,7 +81,7 @@ public class DebridManager: ObservableObject { return .none } - guard let debridMatch = realDebridHashes.first(where: { result.magnetHash == $0.hash }) else { + guard let debridMatch = realDebridIAValues.first(where: { result.magnetHash == $0.hash }) else { return .none } @@ -86,7 +98,7 @@ public class DebridManager: ObservableObject { return false } - if let realDebridItem = realDebridHashes.first(where: { magnetHash == $0.hash }) { + if let realDebridItem = realDebridIAValues.first(where: { magnetHash == $0.hash }) { selectedRealDebridItem = realDebridItem return true } else { diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index a55ad27..6dba198 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -11,6 +11,7 @@ enum ViewTab { case search case sources case settings + case library } @MainActor @@ -31,6 +32,8 @@ class NavigationViewModel: ObservableObject { @Published var isEditingSearch: Bool = false @Published var isSearching: Bool = false + @Published var selectedSearchResult: SearchResult? + @Published var hideNavigationBar = false @Published var currentChoiceSheet: ChoiceSheetType? @@ -86,11 +89,18 @@ class NavigationViewModel: ObservableObject { } } - public func runMagnetAction(action: DefaultMagnetActionType?, searchResult: SearchResult) { + public func runMagnetAction(_ action: DefaultMagnetActionType? = nil) { + guard let searchResult = selectedSearchResult else { + toastModel?.updateToastDescription("Magnet action error: A search result was not selected.") + print("Magnet action error: A search result was not selected.") + + return + } + let selectedAction = action ?? defaultMagnetAction guard let magnetLink = searchResult.magnetLink else { - toastModel?.toastDescription = "Could not run your action because the magnet link is invalid." + toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.") print("Magnet action error: The magnet link is invalid.") return diff --git a/Ferrite/ViewModels/ScrapingViewModel.swift b/Ferrite/ViewModels/ScrapingViewModel.swift index eb7adc4..d7a4ef3 100644 --- a/Ferrite/ViewModels/ScrapingViewModel.swift +++ b/Ferrite/ViewModels/ScrapingViewModel.swift @@ -11,16 +11,6 @@ import SwiftSoup import SwiftUI import SwiftyJSON -public struct SearchResult: Hashable, Codable { - let title: String? - let source: String - let size: String? - let magnetLink: String? - let magnetHash: String? - let seeders: String? - let leechers: String? -} - class ScrapingViewModel: ObservableObject { @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false @@ -31,7 +21,6 @@ class ScrapingViewModel: ObservableObject { @Published var runningSearchTask: Task? @Published var searchResults: [SearchResult] = [] @Published var searchText: String = "" - @Published var selectedSearchResult: SearchResult? @Published var filteredSource: Source? @Published var currentSourceName: String? diff --git a/Ferrite/Views/BatchChoiceView.swift b/Ferrite/Views/BatchChoiceView.swift index 692855c..60e1c64 100644 --- a/Ferrite/Views/BatchChoiceView.swift +++ b/Ferrite/Views/BatchChoiceView.swift @@ -21,7 +21,7 @@ struct BatchChoiceView: View { Button(file.name) { debridManager.selectedRealDebridFile = file - if let searchResult = scrapingModel.selectedSearchResult { + if let searchResult = navModel.selectedSearchResult { debridManager.currentDebridTask = Task { await debridManager.fetchRdDownload(searchResult: searchResult, iaFile: file) diff --git a/Ferrite/Views/CommonViews/ConditionalContextMenu.swift b/Ferrite/Views/CommonViews/ConditionalContextMenu.swift new file mode 100644 index 0000000..d981673 --- /dev/null +++ b/Ferrite/Views/CommonViews/ConditionalContextMenu.swift @@ -0,0 +1,39 @@ +// +// ConditionalContextMenu.swift +// Ferrite +// +// Created by Brian Dashore on 9/3/22. +// +// Used as a workaround for iOS 15 not updating context views with conditional variables +// A stateful ID is required for the contextMenu to update itself. +// + +import SwiftUI + +struct ConditionalContextMenu: ViewModifier { + let internalContent: () -> InternalContent + let id: ID + + init(@ViewBuilder _ internalContent: @escaping () -> InternalContent, id: ID) { + self.internalContent = internalContent + self.id = id + } + + func body(content: Content) -> some View { + if #available(iOS 16, *) { + content + .contextMenu { + internalContent() + } + } else { + content + .background { + Color.clear + .contextMenu { + internalContent() + } + .id(id) + } + } + } +} diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 0c1332f..fc3bb21 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -110,7 +110,11 @@ struct ContentView: View { await scrapingModel.scanSources(sources: sources) if realDebridEnabled, !scrapingModel.searchResults.isEmpty { - await debridManager.populateDebridHashes(scrapingModel.searchResults) + debridManager.realDebridIAValues = [] + + await debridManager.populateDebridHashes( + scrapingModel.searchResults.compactMap(\.magnetHash) + ) } navModel.showSearchProgress = false diff --git a/Ferrite/Views/LibraryView.swift b/Ferrite/Views/LibraryView.swift new file mode 100644 index 0000000..61caa19 --- /dev/null +++ b/Ferrite/Views/LibraryView.swift @@ -0,0 +1,83 @@ +// +// Library.swift +// Ferrite +// +// Created by Brian Dashore on 9/2/22. +// + +import SwiftUI + +struct LibraryView: View { + enum LibraryPickerSegment { + case bookmarks + case history + } + + @EnvironmentObject var navModel: NavigationViewModel + + @FetchRequest( + entity: Bookmark.entity(), + sortDescriptors: [ + NSSortDescriptor(keyPath: \Bookmark.orderNum, ascending: true) + ] + ) var bookmarks: FetchedResults + + @State private var historyEmpty = true + + @State private var selectedSegment: LibraryPickerSegment = .bookmarks + @State private var editMode: EditMode = .inactive + + var body: some View { + NavView { + VStack(spacing: 0) { + Picker("Segments", selection: $selectedSegment) { + Text("Bookmarks").tag(LibraryPickerSegment.bookmarks) + Text("History").tag(LibraryPickerSegment.history) + } + .pickerStyle(.segmented) + .padding(.horizontal) + .padding(.top) + + switch selectedSegment { + case .bookmarks: + BookmarksView(bookmarks: bookmarks) + case .history: + HistoryView() + } + + Spacer() + } + .overlay { + switch selectedSegment { + case .bookmarks: + if bookmarks.isEmpty { + EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results") + } + case .history: + if historyEmpty { + EmptyInstructionView(title: "No History", message: "Start watching to build history") + } + } + } + .navigationTitle("Library") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + EditButton() + } + } + .environment(\.editMode, $editMode) + } + .onChange(of: selectedSegment) { _ in + editMode = .inactive + } + .onDisappear { + editMode = .inactive + } + } +} + +struct LibraryView_Previews: PreviewProvider { + static var previews: some View { + LibraryView() + } +} diff --git a/Ferrite/Views/LibraryViews/BookmarksView.swift b/Ferrite/Views/LibraryViews/BookmarksView.swift new file mode 100644 index 0000000..2ad3fb2 --- /dev/null +++ b/Ferrite/Views/LibraryViews/BookmarksView.swift @@ -0,0 +1,67 @@ +// +// BookmarksView.swift +// Ferrite +// +// Created by Brian Dashore on 9/2/22. +// + +import SwiftUI + +struct BookmarksView: View { + @Environment(\.verticalSizeClass) var verticalSizeClass + + @EnvironmentObject var navModel: NavigationViewModel + @EnvironmentObject var debridManager: DebridManager + + @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false + + let backgroundContext = PersistenceController.shared.backgroundContext + + var bookmarks: FetchedResults + + @State private var viewTask: Task? + + var body: some View { + ZStack { + if !bookmarks.isEmpty { + List { + ForEach(bookmarks, id: \.self) { bookmark in + SearchResultButtonView(result: bookmark.toSearchResult(), existingBookmark: bookmark) + } + .onDelete { offsets in + for index in offsets { + if let bookmark = bookmarks[safe: index] { + PersistenceController.shared.delete(bookmark, context: backgroundContext) + + NotificationCenter.default.post(name: .didDeleteBookmark, object: nil) + } + } + } + .onMove { (source, destination) in + var changedBookmarks = bookmarks.map { $0 } + + changedBookmarks.move(fromOffsets: source, toOffset: destination) + + for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) { + changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex) + } + + PersistenceController.shared.save() + } + } + .listStyle(.insetGrouped) + .onAppear { + if realDebridEnabled { + viewTask = Task { + let hashes = bookmarks.compactMap { $0.magnetHash } + await debridManager.populateDebridHashes(hashes) + } + } + } + .onDisappear { + viewTask?.cancel() + } + } + } + } +} diff --git a/Ferrite/Views/LibraryViews/HistoryView.swift b/Ferrite/Views/LibraryViews/HistoryView.swift new file mode 100644 index 0000000..078ac30 --- /dev/null +++ b/Ferrite/Views/LibraryViews/HistoryView.swift @@ -0,0 +1,22 @@ +// +// HistoryView.swift +// Ferrite +// +// Created by Brian Dashore on 9/2/22. +// + +import SwiftUI + +struct HistoryView: View { + var body: some View { + ZStack { + EmptyView() + } + } +} + +struct HistoryView_Previews: PreviewProvider { + static var previews: some View { + HistoryView() + } +} diff --git a/Ferrite/Views/MagnetChoiceView.swift b/Ferrite/Views/MagnetChoiceView.swift index ae142b2..e3eafc5 100644 --- a/Ferrite/Views/MagnetChoiceView.swift +++ b/Ferrite/Views/MagnetChoiceView.swift @@ -23,7 +23,7 @@ struct MagnetChoiceView: View { var body: some View { NavView { Form { - if realDebridEnabled, debridManager.matchSearchResult(result: scrapingModel.selectedSearchResult) != .none { + if realDebridEnabled, debridManager.matchSearchResult(result: navModel.selectedSearchResult) != .none { Section(header: "Real Debrid options") { ListRowButtonView("Play on Outplayer", systemImage: "arrow.up.forward.app.fill") { navModel.runDebridAction(action: .outplayer, urlString: debridManager.realDebridDownloadUrl) @@ -60,7 +60,7 @@ struct MagnetChoiceView: View { Section(header: "Magnet options") { ListRowButtonView("Copy magnet", systemImage: "doc.on.doc.fill") { - UIPasteboard.general.string = scrapingModel.selectedSearchResult?.magnetLink + UIPasteboard.general.string = navModel.selectedSearchResult?.magnetLink showMagnetCopyAlert.toggle() } .alert(isPresented: $showMagnetCopyAlert) { @@ -72,7 +72,7 @@ struct MagnetChoiceView: View { } ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") { - if let result = scrapingModel.selectedSearchResult, + if let result = navModel.selectedSearchResult, let magnetLink = result.magnetLink, let url = URL(string: magnetLink) { @@ -82,9 +82,7 @@ struct MagnetChoiceView: View { } ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") { - if let result = scrapingModel.selectedSearchResult { - navModel.runMagnetAction(action: .webtor, searchResult: result) - } + navModel.runMagnetAction(.webtor) } } } diff --git a/Ferrite/Views/MainView.swift b/Ferrite/Views/MainView.swift index 693795c..0580c28 100644 --- a/Ferrite/Views/MainView.swift +++ b/Ferrite/Views/MainView.swift @@ -28,6 +28,12 @@ struct MainView: View { } .tag(ViewTab.search) + LibraryView() + .tabItem { + Label("Library", systemImage: "book.closed") + } + .tag(ViewTab.library) + SourcesView() .tabItem { Label("Sources", systemImage: "doc.text") diff --git a/Ferrite/Views/SearchResultButtonView.swift b/Ferrite/Views/SearchResultButtonView.swift new file mode 100644 index 0000000..06a90b1 --- /dev/null +++ b/Ferrite/Views/SearchResultButtonView.swift @@ -0,0 +1,109 @@ +// +// SearchResultButtonView.swift +// Ferrite +// +// Created by Brian Dashore on 9/2/22. +// + +import SwiftUI + +// BUG: iOS 15 cannot refresh the context menu. Debating using swipe actions or adopting a workaround. +struct SearchResultButtonView: View { + let backgroundContext = PersistenceController.shared.backgroundContext + + @EnvironmentObject var navModel: NavigationViewModel + @EnvironmentObject var debridManager: DebridManager + + var result: SearchResult + + @State private var runOnce = false + @State var existingBookmark: Bookmark? = nil + + var body: some View { + VStack(alignment: .leading) { + Button { + if debridManager.currentDebridTask == nil { + navModel.selectedSearchResult = result + + switch debridManager.matchSearchResult(result: result) { + case .full: + debridManager.currentDebridTask = Task { + await debridManager.fetchRdDownload(searchResult: result) + + if !debridManager.realDebridDownloadUrl.isEmpty { + navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl) + } + } + case .partial: + if debridManager.setSelectedRdResult(result: result) { + navModel.currentChoiceSheet = .batch + } + case .none: + navModel.runMagnetAction() + } + } + } label: { + Text(result.title ?? "No title") + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + .dynamicAccentColor(.primary) + .padding(.bottom, 5) + .conditionalContextMenu(id: existingBookmark) { + if let bookmark = existingBookmark { + Button { + PersistenceController.shared.delete(bookmark, context: backgroundContext) + + // When the entity is deleted, let other instances know to remove that reference + NotificationCenter.default.post(name: .didDeleteBookmark, object: nil) + } label: { + Text("Remove bookmark") + Image(systemName: "bookmark.slash.fill") + } + } else { + Button { + let newBookmark = Bookmark(context: backgroundContext) + newBookmark.title = result.title + newBookmark.source = result.source + newBookmark.magnetHash = result.magnetHash + newBookmark.magnetLink = result.magnetLink + newBookmark.seeders = result.seeders + newBookmark.leechers = result.leechers + + existingBookmark = newBookmark + + PersistenceController.shared.save(backgroundContext) + } label: { + Text("Bookmark") + Image(systemName: "bookmark") + } + } + } + + SearchResultRDView(result: result) + } + .onReceive(NotificationCenter.default.publisher(for: .didDeleteBookmark)) { _ in + existingBookmark = nil + } + .onAppear { + // Only run a exists request if a bookmark isn't passed to the view + if existingBookmark == nil && !runOnce { + let bookmarkRequest = Bookmark.fetchRequest() + bookmarkRequest.predicate = NSPredicate( + format: "title == %@ AND source == %@ AND magnetLink == %@ AND magnetHash = %@", + result.title ?? "", + result.source, + result.magnetLink ?? "", + result.magnetHash ?? "" + ) + bookmarkRequest.fetchLimit = 1 + + if let fetchedBookmark = try? backgroundContext.fetch(bookmarkRequest).first { + existingBookmark = fetchedBookmark + } + + runOnce = true + } + } + } +} diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift index 9cf050d..2c04731 100644 --- a/Ferrite/Views/SearchResultsView.swift +++ b/Ferrite/Views/SearchResultsView.swift @@ -9,7 +9,6 @@ import SwiftUI struct SearchResultsView: View { @EnvironmentObject var scrapingModel: ScrapingViewModel - @EnvironmentObject var debridManager: DebridManager @EnvironmentObject var navModel: NavigationViewModel @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false @@ -18,38 +17,7 @@ struct SearchResultsView: View { List { ForEach(scrapingModel.searchResults, id: \.self) { result in if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil { - VStack(alignment: .leading) { - Button { - if debridManager.currentDebridTask == nil { - scrapingModel.selectedSearchResult = result - - switch debridManager.matchSearchResult(result: result) { - case .full: - debridManager.currentDebridTask = Task { - await debridManager.fetchRdDownload(searchResult: result) - - if !debridManager.realDebridDownloadUrl.isEmpty { - navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl) - } - } - case .partial: - if debridManager.setSelectedRdResult(result: result) { - navModel.currentChoiceSheet = .batch - } - case .none: - navModel.runMagnetAction(action: nil, searchResult: result) - } - } - } label: { - Text(result.title ?? "No title") - .font(.callout) - .fixedSize(horizontal: false, vertical: true) - } - .dynamicAccentColor(.primary) - .padding(.bottom, 5) - - SearchResultRDView(result: result) - } + SearchResultButtonView(result: result) } } } diff --git a/Ferrite/Views/SourceViews/SourceSettingsView.swift b/Ferrite/Views/SourceViews/SourceSettingsView.swift index 6a0daa8..18d8e53 100644 --- a/Ferrite/Views/SourceViews/SourceSettingsView.swift +++ b/Ferrite/Views/SourceViews/SourceSettingsView.swift @@ -147,7 +147,7 @@ struct SourceSettingsMethodView: View { var body: some View { Section(header: InlineHeader("Fetch method")) { - if selectedSource.api != nil, selectedSource.jsonParser != nil { + if selectedSource.jsonParser != nil { Button { selectedSource.preferredParser = SourcePreferredParser.siteApi.rawValue } label: {