From e9670ea1185d3f925bf1255debb8372fd569f1cd Mon Sep 17 00:00:00 2001 From: kingbri Date: Sun, 24 Jul 2022 14:50:29 -0400 Subject: [PATCH] RealDebrid: Add batch torrent support Batch torrents are torrents that have multiple files bundled within one torrent file. RealDebrid does support these, but it is difficult to get them to work. The main flow requires setting a specific combination in RealDebrid to allow for link generation. However, this is not intuitive to users and is bad API design on RealDebrid's part. Ferrite's implementation presents users with all the possible files from batches (duplicates deleted) and selects the user-chosen file to download. That way, only the user chosen file is presented to play on an external video player. This still needs work for optimization purposes, but this commit does produce a working build. Signed-off-by: kingbri --- Ferrite.xcodeproj/project.pbxproj | 10 +++- Ferrite/API/RealDebridModels.swift | 32 +++++++++++- Ferrite/API/RealDebridWrapper.swift | 63 ++++++++++++++++++++---- Ferrite/FerriteApp.swift | 2 + Ferrite/Models/DebridManager.swift | 53 ++++++++++++++++++-- Ferrite/Models/NavigationViewModel.swift | 21 ++++++++ Ferrite/Models/ScrapingViewModel.swift | 4 +- Ferrite/Views/BatchChoiceView.swift | 61 +++++++++++++++++++++++ Ferrite/Views/ContentView.swift | 7 ++- Ferrite/Views/MagnetChoiceView.swift | 23 +++------ Ferrite/Views/SearchResultsView.swift | 39 +++++++++------ 11 files changed, 265 insertions(+), 50 deletions(-) create mode 100644 Ferrite/Models/NavigationViewModel.swift create mode 100644 Ferrite/Views/BatchChoiceView.swift diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index 7985319..8b15878 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -32,6 +32,8 @@ 0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D3288903F000DE2211 /* SearchResultsView.swift */; }; 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D4288903F000DE2211 /* ContentView.swift */; }; 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; }; + 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; }; + 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */; }; 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */; }; /* End PBXBuildFile section */ @@ -43,7 +45,7 @@ 0CA148C1288903F000DE2211 /* NavView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavView.swift; sourceTree = ""; }; 0CA148C2288903F000DE2211 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0CA148C3288903F000DE2211 /* ScrapingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrapingViewModel.swift; sourceTree = ""; }; - 0CA148C4288903F000DE2211 /* RealDebridModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealDebridModels.swift; sourceTree = ""; }; + 0CA148C4288903F000DE2211 /* RealDebridModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.swift; path = RealDebridModels.swift; sourceTree = ""; }; 0CA148C6288903F000DE2211 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0CA148C7288903F000DE2211 /* FerriteApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FerriteApp.swift; sourceTree = ""; }; 0CA148C9288903F000DE2211 /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; @@ -58,6 +60,8 @@ 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 = ""; }; 0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchChoiceView.swift; sourceTree = ""; }; + 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -128,6 +132,7 @@ 0CA148BB288903F000DE2211 /* SettingsView.swift */, 0CA148BE288903F000DE2211 /* CardView.swift */, 0CA148BC288903F000DE2211 /* LoginWebView.swift */, + 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */, 0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */, ); path = Views; @@ -139,6 +144,7 @@ 0CA148CD288903F000DE2211 /* DebridManager.swift */, 0CA148C3288903F000DE2211 /* ScrapingViewModel.swift */, 0CA148CF288903F000DE2211 /* ToastViewModel.swift */, + 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */, ); path = Models; sourceTree = ""; @@ -261,6 +267,7 @@ files = ( 0CA148DB288903F000DE2211 /* NavView.swift in Sources */, 0CA148E9288903F000DE2211 /* MainView.swift in Sources */, + 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */, 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */, 0CA148E1288903F000DE2211 /* Collection.swift in Sources */, 0CA148E4288903F000DE2211 /* Keychain.swift in Sources */, @@ -272,6 +279,7 @@ 0CA148E6288903F000DE2211 /* WebView.swift in Sources */, 0CA148E2288903F000DE2211 /* Data.swift in Sources */, 0CA148DE288903F000DE2211 /* RealDebridModels.swift in Sources */, + 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */, 0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */, 0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */, 0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */, diff --git a/Ferrite/API/RealDebridModels.swift b/Ferrite/API/RealDebridModels.swift index 63cb7c7..49eb17f 100644 --- a/Ferrite/API/RealDebridModels.swift +++ b/Ferrite/API/RealDebridModels.swift @@ -51,10 +51,10 @@ public struct TokenResponse: Codable { // MARK: - instantAvailability endpoint // Thanks Skitty! -struct InstantAvailabilityResponse: Codable { +public struct InstantAvailabilityResponse: Codable { var data: InstantAvailabilityData? - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let data = try? container.decode(InstantAvailabilityData.self) { @@ -72,6 +72,34 @@ struct InstantAvailabilityInfo: Codable { var filesize: Int } +// MARK: - Instant Availability client side structures +public struct RealDebridIA: Codable, Hashable { + let hash: String + var files: [RealDebridIAFile] = [] + var batches: [RealDebridIABatch] = [] +} + +public struct RealDebridIABatch: Codable, Hashable { + let files: [RealDebridIABatchFile] +} + +public struct RealDebridIABatchFile: Codable, Hashable { + let id: Int + let fileName: String +} + +public struct RealDebridIAFile: Codable, Hashable { + let name: String + let batchIndex: Int + let batchFileIndex: Int +} + +public enum RealDebridIAStatus: Codable, Hashable { + case full + case partial + case none +} + // MARK: - addMagnet endpoint public struct AddMagnetResponse: Codable { let id: String diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index 0267d62..5d29442 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -218,8 +218,8 @@ public class RealDebrid: ObservableObject { // Checks if the magnet is streamable on RD // Currently does not work for batch links - public func instantAvailability(magnetHashes: [String]) async throws -> [String] { - var availableHashes: [String] = [] + public func instantAvailability(magnetHashes: [String]) async throws -> [RealDebridIA] { + var availableHashes: [RealDebridIA] = [] var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnetHashes.joined(separator: "/"))")!) let data = try await performRequest(request: &request, requestName: #function) @@ -232,9 +232,47 @@ public class RealDebrid: ObservableObject { continue } - // Do not include if a hash is a batch - if !(data.rd.count > 1), !(data.rd[safe: 0]?.keys.count ?? 0 > 1) { - availableHashes.append(hash) + if data.rd.isEmpty { + continue + } + + // Is this a batch + if data.rd.count > 1 || data.rd[0].count > 1 { + // Batch array + let batches = data.rd.map { fileDict in + let batchFiles: [RealDebridIABatchFile] = fileDict.map { (key, value) in + // Force unwrapped ID. Is safe because ID is guaranteed on a successful response + return RealDebridIABatchFile(id: Int(key)!, fileName: value.filename) + }.sorted(by: { $0.id < $1.id }) + + return RealDebridIABatch(files: batchFiles) + } + + // RD files array + // Possibly sort this in the future, but not sure how at the moment + var files: [RealDebridIAFile] = [] + + for index in batches.indices { + let batchFiles = batches[index].files + + for batchFileIndex in batchFiles.indices { + let batchFile = batchFiles[batchFileIndex] + + if !files.contains(where: { $0.name == batchFile.fileName }) { + files.append( + RealDebridIAFile( + name: batchFile.fileName, + batchIndex: index, + batchFileIndex: batchFileIndex + ) + ) + } + } + } + + availableHashes.append(RealDebridIA(hash: hash, files: files, batches: batches)) + } else { + availableHashes.append(RealDebridIA(hash: hash)) } } @@ -259,13 +297,19 @@ public class RealDebrid: ObservableObject { } // Queues the magnet link for downloading - public func selectFiles(debridID: String) async throws { + public func selectFiles(debridID: String, fileIds: [Int]) async throws { var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/selectFiles/\(debridID)")!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") var bodyComponents = URLComponents() - bodyComponents.queryItems = [URLQueryItem(name: "files", value: "all")] + + if fileIds.isEmpty { + bodyComponents.queryItems = [URLQueryItem(name: "files", value: "all")] + } else { + let joinedIds = fileIds.map(String.init).joined(separator: ",") + bodyComponents.queryItems = [URLQueryItem(name: "files", value: joinedIds)] + } request.httpBody = bodyComponents.query?.data(using: .utf8) @@ -273,13 +317,14 @@ public class RealDebrid: ObservableObject { } // Fetches the info of a torrent - public func torrentInfo(debridID: String) async throws -> String { + public func torrentInfo(debridID: String, selectedIndex: Int?) async throws -> String { var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!) let data = try await performRequest(request: &request, requestName: #function) let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data) - if let torrentLink = rawResponse.links[safe: 0] { + // Error out if no index is provided + if let torrentLink = rawResponse.links[safe: selectedIndex ?? -1] { return torrentLink } else { throw RealDebridError.EmptyData diff --git a/Ferrite/FerriteApp.swift b/Ferrite/FerriteApp.swift index 3bdeaa9..5171831 100644 --- a/Ferrite/FerriteApp.swift +++ b/Ferrite/FerriteApp.swift @@ -12,6 +12,7 @@ struct FerriteApp: App { @StateObject var scrapingModel: ScrapingViewModel = .init() @StateObject var toastModel: ToastViewModel = .init() @StateObject var debridManager: DebridManager = .init() + @StateObject var navigationModel: NavigationViewModel = .init() var body: some Scene { WindowGroup { @@ -23,6 +24,7 @@ struct FerriteApp: App { .environmentObject(debridManager) .environmentObject(scrapingModel) .environmentObject(toastModel) + .environmentObject(navigationModel) } } } diff --git a/Ferrite/Models/DebridManager.swift b/Ferrite/Models/DebridManager.swift index 345e11f..b3177e1 100644 --- a/Ferrite/Models/DebridManager.swift +++ b/Ferrite/Models/DebridManager.swift @@ -18,9 +18,11 @@ public class DebridManager: ObservableObject { @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false - @Published var realDebridHashes: [String] = [] + @Published var realDebridHashes: [RealDebridIA] = [] @Published var realDebridAuthUrl: String = "" @Published var realDebridDownloadUrl: String = "" + @Published var selectedRealDebridItem: RealDebridIA? + @Published var selectedRealDebridFile: RealDebridIAFile? init() { realDebrid.parentManager = self @@ -50,6 +52,38 @@ public class DebridManager: ObservableObject { } } + public func matchSearchResult(result: SearchResult?) -> RealDebridIAStatus { + guard let result = result else { + return .none + } + + guard let debridMatch = realDebridHashes.first(where: { result.magnetHash == $0.hash }) else { + return .none + } + + if debridMatch.batches.isEmpty { + return .full + } else { + return .partial + } + } + + @MainActor + public func setSelectedRdResult(result: SearchResult) -> Bool { + guard let magnetHash = result.magnetHash else { + toastModel?.toastDescription = "Could not find the torrent magnet hash" + return false + } + + if let realDebridItem = realDebridHashes.first(where: { magnetHash == $0.hash }) { + selectedRealDebridItem = realDebridItem + return true + } else { + toastModel?.toastDescription = "Could not find the associated RealDebrid entry for magnet hash \(magnetHash)" + return false + } + } + public func authenticateRd() async { do { let url = try await realDebrid.getVerificationInfo() @@ -67,12 +101,23 @@ public class DebridManager: ObservableObject { } } - public func fetchRdDownload(searchResult: SearchResult) async { + public func fetchRdDownload(searchResult: SearchResult, iaFile: RealDebridIAFile? = nil) async { do { let realDebridId = try await realDebrid.addMagnet(magnetLink: searchResult.magnetLink) - try await realDebrid.selectFiles(debridID: realDebridId) - let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId) + var fileIds: [Int] = [] + + if let iaFile = iaFile { + guard let iaBatchFromFile = selectedRealDebridItem?.batches[safe: iaFile.batchIndex] else { + return + } + + fileIds = iaBatchFromFile.files.map({ $0.id }) + } + + try await realDebrid.selectFiles(debridID: realDebridId, fileIds: fileIds) + + let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId, selectedIndex: iaFile == nil ? 0 : iaFile?.batchFileIndex) let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink) Task { @MainActor in diff --git a/Ferrite/Models/NavigationViewModel.swift b/Ferrite/Models/NavigationViewModel.swift new file mode 100644 index 0000000..20b09f8 --- /dev/null +++ b/Ferrite/Models/NavigationViewModel.swift @@ -0,0 +1,21 @@ +// +// NavigationViewModel.swift +// Ferrite +// +// Created by Brian Dashore on 7/24/22. +// + +import SwiftUI + +class NavigationViewModel: ObservableObject { + enum ChoiceSheetType: Identifiable { + var id: Int { + hashValue + } + + case magnet + case batch + } + + @Published var currentChoiceSheet: ChoiceSheetType? +} diff --git a/Ferrite/Models/ScrapingViewModel.swift b/Ferrite/Models/ScrapingViewModel.swift index e7d5c9d..40aee36 100644 --- a/Ferrite/Models/ScrapingViewModel.swift +++ b/Ferrite/Models/ScrapingViewModel.swift @@ -54,9 +54,7 @@ class ScrapingViewModel: ObservableObject { @Published var searchResults: [SearchResult] = [] @Published var debridHashes: [String] = [] @Published var searchText: String = "" - - @Published var realDebridAuthUrl: String = "" - @Published var showWebView: Bool = false + @Published var selectedSearchResult: SearchResult? // Fetches the HTML body for the source website @MainActor diff --git a/Ferrite/Views/BatchChoiceView.swift b/Ferrite/Views/BatchChoiceView.swift new file mode 100644 index 0000000..dbaac88 --- /dev/null +++ b/Ferrite/Views/BatchChoiceView.swift @@ -0,0 +1,61 @@ +// +// BatchChoiceView.swift +// Ferrite +// +// Created by Brian Dashore on 7/24/22. +// + +import SwiftUI + +struct BatchChoiceView: View { + @Environment(\.dismiss) var dismiss + + @EnvironmentObject var debridManager: DebridManager + @EnvironmentObject var scrapingModel: ScrapingViewModel + @EnvironmentObject var navigationModel: NavigationViewModel + + var body: some View { + NavView { + List { + // To present this sheet, an RD item had to be set, this force unwrap is therefore safe + ForEach(debridManager.selectedRealDebridItem!.files, id: \.self) { file in + Button(file.name) { + debridManager.selectedRealDebridFile = file + + if let searchResult = scrapingModel.selectedSearchResult { + Task { + await debridManager.fetchRdDownload(searchResult: searchResult, iaFile: file) + + // The download may complete before this sheet dismisses + try? await Task.sleep(seconds: 1) + navigationModel.currentChoiceSheet = .magnet + + debridManager.selectedRealDebridFile = nil + debridManager.selectedRealDebridItem = nil + } + } + + dismiss() + } + } + } + .navigationTitle("Select a file") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + debridManager.selectedRealDebridItem = nil + + dismiss() + } + } + } + } + } +} + +struct BatchChoiceView_Previews: PreviewProvider { + static var previews: some View { + BatchChoiceView() + } +} diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 32edbe7..801976b 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -11,6 +11,8 @@ struct ContentView: View { @EnvironmentObject var scrapingModel: ScrapingViewModel @EnvironmentObject var debridManager: DebridManager + @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false + var body: some View { NavView { VStack { @@ -25,7 +27,10 @@ struct ContentView: View { } await scrapingModel.scrapeWebsite(source: source, html: html) - await debridManager.populateDebridHashes(scrapingModel.searchResults) + + if realDebridEnabled { + await debridManager.populateDebridHashes(scrapingModel.searchResults) + } } } } diff --git a/Ferrite/Views/MagnetChoiceView.swift b/Ferrite/Views/MagnetChoiceView.swift index 257a2b9..60a68d2 100644 --- a/Ferrite/Views/MagnetChoiceView.swift +++ b/Ferrite/Views/MagnetChoiceView.swift @@ -11,19 +11,18 @@ import ActivityView struct MagnetChoiceView: View { @Environment(\.dismiss) var dismiss + @EnvironmentObject var scrapingModel: ScrapingViewModel @EnvironmentObject var debridManager: DebridManager @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false - @Binding var selectedResult: SearchResult? - @State private var showActivityView = false @State private var activityItem: ActivityItem? var body: some View { NavView { Form { - if realDebridEnabled, debridManager.realDebridHashes.contains(selectedResult?.magnetHash ?? "") { + if realDebridEnabled, debridManager.matchSearchResult(result: scrapingModel.selectedSearchResult) != .none { Section("Real Debrid options") { Button("Play on Outplayer") { guard let downloadUrl = URL(string: "outplayer://\(debridManager.realDebridDownloadUrl)") else { @@ -66,11 +65,11 @@ struct MagnetChoiceView: View { Section("Magnet options") { Button("Copy magnet") { - UIPasteboard.general.string = selectedResult?.magnetLink + UIPasteboard.general.string = scrapingModel.selectedSearchResult?.magnetLink } Button("Share magnet") { - if let result = selectedResult, let url = URL(string: result.magnetLink) { + if let result = scrapingModel.selectedSearchResult, let url = URL(string: result.magnetLink) { activityItem = ActivityItem(items: url) showActivityView.toggle() } @@ -83,6 +82,8 @@ struct MagnetChoiceView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { + debridManager.realDebridDownloadUrl = "" + dismiss() } } @@ -93,16 +94,6 @@ struct MagnetChoiceView: View { struct MagnetChoiceView_Previews: PreviewProvider { static var previews: some View { - MagnetChoiceView( - selectedResult: - .constant( - SearchResult( - title: "", - source: "", - size: "", - magnetLink: "", - magnetHash: nil) - ) - ) + MagnetChoiceView() } } diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift index b80ed7e..c860091 100644 --- a/Ferrite/Views/SearchResultsView.swift +++ b/Ferrite/Views/SearchResultsView.swift @@ -13,36 +13,42 @@ struct SearchResultsView: View { @EnvironmentObject var scrapingModel: ScrapingViewModel @EnvironmentObject var debridManager: DebridManager + @EnvironmentObject var navigationModel: NavigationViewModel @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false - @State var selectedResult: SearchResult? - - @State private var showExternalSheet = false - @State private var resultUsesRd = false - var body: some View { List { ForEach(scrapingModel.searchResults, id: \.self) { result in VStack(alignment: .leading) { Button { - selectedResult = result + scrapingModel.selectedSearchResult = result - if debridManager.realDebridHashes.contains(result.magnetHash ?? ""), realDebridEnabled { + switch debridManager.matchSearchResult(result: result) { + case .full: Task { await debridManager.fetchRdDownload(searchResult: result) - showExternalSheet.toggle() + navigationModel.currentChoiceSheet = .magnet } - } else { - showExternalSheet.toggle() + case .partial: + if debridManager.setSelectedRdResult(result: result) { + navigationModel.currentChoiceSheet = .batch + } + case .none: + navigationModel.currentChoiceSheet = .magnet } } label: { Text(result.title) .font(.callout) .fixedSize(horizontal: false, vertical: true) } - .sheet(isPresented: $showExternalSheet) { - MagnetChoiceView(selectedResult: $selectedResult) + .sheet(item: $navigationModel.currentChoiceSheet) { item in + switch item { + case .magnet: + MagnetChoiceView() + case .batch: + BatchChoiceView() + } } .tint(colorScheme == .light ? .black : .white) .padding(.bottom, 5) @@ -59,11 +65,16 @@ struct SearchResultsView: View { .fontWeight(.bold) .padding(2) .background { - if debridManager.realDebridHashes.contains(result.magnetHash ?? "") { + switch debridManager.matchSearchResult(result: result) { + case .full: Color.green .cornerRadius(4) .opacity(0.5) - } else { + case .partial: + Color.orange + .cornerRadius(4) + .opacity(0.5) + case .none: Color.red .cornerRadius(4) .opacity(0.5)