Debrid: Migrate more components to the protocol
Protocols can't be used in ObservedObjects. Observable in iOS 17 and up solves this, but Ferrite targets iOS 16 and up, so add a type-erased StateObject which supports protocols. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
b80f8900b7
commit
07731e7b00
19 changed files with 272 additions and 122 deletions
|
|
@ -96,6 +96,7 @@
|
|||
0C84FCE729E4B61A00B0DFE4 /* FilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */; };
|
||||
0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */; };
|
||||
0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C871BDE29994D9D005279AC /* FilterLabelView.swift */; };
|
||||
0C8AE2482C0FFB6600701675 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8AE2472C0FFB6600701675 /* Store.swift */; };
|
||||
0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */; };
|
||||
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */; };
|
||||
0C8DC35629CE2ABF008A83AD /* SourceSettingsApiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */; };
|
||||
|
|
@ -244,6 +245,7 @@
|
|||
0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterModels.swift; sourceTree = "<group>"; };
|
||||
0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterAmountLabelView.swift; sourceTree = "<group>"; };
|
||||
0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = "<group>"; };
|
||||
0C8AE2472C0FFB6600701675 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
|
||||
0C8DC35129CE287E008A83AD /* PluginInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInfoView.swift; sourceTree = "<group>"; };
|
||||
0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsBaseUrlView.swift; sourceTree = "<group>"; };
|
||||
0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsApiView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -457,6 +459,7 @@
|
|||
0C44E2A728D4DDDC007711AE /* Application.swift */,
|
||||
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */,
|
||||
0CD0265629FEFBF900A83D25 /* FerriteKeychain.swift */,
|
||||
0C8AE2472C0FFB6600701675 /* Store.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -858,6 +861,7 @@
|
|||
0C794B6B289DACF100DD1CC8 /* PluginCatalogButtonView.swift in Sources */,
|
||||
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */,
|
||||
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
||||
0C8AE2482C0FFB6600701675 /* Store.swift in Sources */,
|
||||
0C3E00D2296F4FD200ECECB2 /* PluginsView.swift in Sources */,
|
||||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
||||
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@
|
|||
import Foundation
|
||||
|
||||
// TODO: Fix errors
|
||||
public class AllDebrid: PollingDebridSource {
|
||||
public let id = "AllDebrid"
|
||||
public let abbreviation = "AD"
|
||||
public let website = "https://alldebrid.com"
|
||||
public class AllDebrid: PollingDebridSource, ObservableObject {
|
||||
public let id = DebridInfo(
|
||||
name: "AllDebrid", abbreviation: "AD", website: "https://alldebrid.com"
|
||||
)
|
||||
public var authTask: Task<Void, Error>?
|
||||
|
||||
public var authProcessing: Bool = false
|
||||
|
|
@ -178,7 +178,7 @@ public class AllDebrid: PollingDebridSource {
|
|||
|
||||
return DebridIA(
|
||||
magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet),
|
||||
source: self.id,
|
||||
source: self.id.name,
|
||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||
files: files
|
||||
)
|
||||
|
|
@ -292,7 +292,7 @@ public class AllDebrid: PollingDebridSource {
|
|||
cloudTorrents = rawResponse.magnets.map { magnetResponse in
|
||||
DebridCloudTorrent(
|
||||
torrentId: String(magnetResponse.id),
|
||||
source: self.id,
|
||||
source: self.id.name,
|
||||
fileName: magnetResponse.filename,
|
||||
status: magnetResponse.status,
|
||||
hash: magnetResponse.hash,
|
||||
|
|
@ -325,7 +325,7 @@ public class AllDebrid: PollingDebridSource {
|
|||
// The link is also the ID
|
||||
cloudDownloads = rawResponse.links.map { link in
|
||||
DebridCloudDownload(
|
||||
downloadId: link.link, source: self.id, fileName: link.filename, link: link.link
|
||||
downloadId: link.link, source: self.id.name, fileName: link.filename, link: link.link
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class Premiumize: OAuthDebridSource {
|
||||
public let id = "Premiumize"
|
||||
public let abbreviation = "PM"
|
||||
public let website = "https://premiumize.me"
|
||||
public class Premiumize: OAuthDebridSource, ObservableObject {
|
||||
public let id = DebridInfo(
|
||||
name: "Premiumize", abbreviation: "PM", website: "https://premiumize.me"
|
||||
)
|
||||
|
||||
@Published public var authProcessing: Bool = false
|
||||
public var isLoggedIn: Bool {
|
||||
|
|
@ -195,7 +195,7 @@ public class Premiumize: OAuthDebridSource {
|
|||
|
||||
return DebridIA(
|
||||
magnet: magnet,
|
||||
source: id,
|
||||
source: id.name,
|
||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||
files: files
|
||||
)
|
||||
|
|
@ -300,7 +300,7 @@ public class Premiumize: OAuthDebridSource {
|
|||
|
||||
// The "link" is the ID for Premiumize
|
||||
cloudDownloads = rawResponse.files.map { file in
|
||||
DebridCloudDownload(downloadId: file.id, source: self.id, fileName: file.name, link: file.id)
|
||||
DebridCloudDownload(downloadId: file.id, source: self.id.name, fileName: file.name, link: file.id)
|
||||
}
|
||||
|
||||
return cloudDownloads
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class RealDebrid: PollingDebridSource {
|
||||
public let id = "RealDebrid"
|
||||
public let abbreviation = "RD"
|
||||
public let website = "https://real-debrid.com"
|
||||
public class RealDebrid: PollingDebridSource, ObservableObject {
|
||||
public let id = DebridInfo(
|
||||
name: "RealDebrid", abbreviation: "RD", website: "https://real-debrid.com"
|
||||
)
|
||||
public var authTask: Task<Void, Error>?
|
||||
|
||||
@Published public var authProcessing: Bool = false
|
||||
|
|
@ -20,9 +20,14 @@ public class RealDebrid: PollingDebridSource {
|
|||
FerriteKeychain.shared.get("RealDebrid.AccessToken") != nil
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var IAValues: [DebridIA] = [] {
|
||||
willSet {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
var cloudTTL: Double = 0.0
|
||||
|
||||
let baseAuthUrl = "https://api.real-debrid.com/oauth/v2"
|
||||
let baseApiUrl = "https://api.real-debrid.com/rest/1.0"
|
||||
|
|
@ -282,7 +287,7 @@ public class RealDebrid: PollingDebridSource {
|
|||
IAValues.append(
|
||||
DebridIA(
|
||||
magnet: Magnet(hash: hash, link: nil),
|
||||
source: id,
|
||||
source: id.name,
|
||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||
files: files
|
||||
)
|
||||
|
|
@ -291,7 +296,7 @@ public class RealDebrid: PollingDebridSource {
|
|||
IAValues.append(
|
||||
DebridIA(
|
||||
magnet: Magnet(hash: hash, link: nil),
|
||||
source: id,
|
||||
source: id.name,
|
||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||
files: []
|
||||
)
|
||||
|
|
@ -422,7 +427,7 @@ public class RealDebrid: PollingDebridSource {
|
|||
cloudTorrents = rawResponse.map { response in
|
||||
DebridCloudTorrent(
|
||||
torrentId: response.id,
|
||||
source: self.id,
|
||||
source: self.id.name,
|
||||
fileName: response.filename,
|
||||
status: response.status,
|
||||
hash: response.hash,
|
||||
|
|
@ -448,7 +453,7 @@ public class RealDebrid: PollingDebridSource {
|
|||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
let rawResponse = try jsonDecoder.decode([UserDownloadsResponse].self, from: data)
|
||||
cloudDownloads = rawResponse.map { response in
|
||||
DebridCloudDownload(downloadId: response.id, source: self.id, fileName: response.filename, link: response.download)
|
||||
DebridCloudDownload(downloadId: response.id, source: self.id.name, fileName: response.filename, link: response.download)
|
||||
}
|
||||
|
||||
return cloudDownloads
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct DebridInfo: Hashable, Sendable {
|
||||
let name: String
|
||||
let abbreviation: String
|
||||
let website: String
|
||||
}
|
||||
|
||||
public struct DebridIA: Hashable, Sendable {
|
||||
let magnet: Magnet
|
||||
let source: String
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public protocol DebridSource: ObservableObject {
|
||||
public protocol DebridSource: AnyObservableObject {
|
||||
// ID of the service
|
||||
var id: String { get }
|
||||
var abbreviation: String { get }
|
||||
var website: String { get }
|
||||
var id: DebridInfo { get }
|
||||
|
||||
// Auth variables
|
||||
var authProcessing: Bool { get set }
|
||||
|
|
|
|||
148
Ferrite/Utils/Store.swift
Normal file
148
Ferrite/Utils/Store.swift
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// Store.swift
|
||||
// Ferrite
|
||||
//
|
||||
//
|
||||
// Originally created by William Baker on 09/06/2022.
|
||||
// https://github.com/Tiny-Home-Consulting/Dependiject/blob/master/Dependiject/Store.swift
|
||||
// Copyright (c) 2022 Tiny Home Consulting LLC. All rights reserved.
|
||||
//
|
||||
// Combined together by Brian Dashore
|
||||
//
|
||||
// TODO: Replace with Observable when minVersion >= iOS 17
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
class ErasedObservableObject: ObservableObject {
|
||||
let objectWillChange: AnyPublisher<Void, Never>
|
||||
|
||||
init(objectWillChange: AnyPublisher<Void, Never>) {
|
||||
self.objectWillChange = objectWillChange
|
||||
}
|
||||
|
||||
static func empty() -> ErasedObservableObject {
|
||||
.init(objectWillChange: Empty().eraseToAnyPublisher())
|
||||
}
|
||||
}
|
||||
|
||||
public protocol AnyObservableObject: AnyObject {
|
||||
var objectWillChange: ObservableObjectPublisher { get }
|
||||
}
|
||||
|
||||
// The generic type names were chosen to match the SwiftUI equivalents:
|
||||
// - ObjectType from StateObject<ObjectType> and ObservedObject<ObjectType>
|
||||
// - Subject from ObservedObject.Wrapper.subscript<Subject>(dynamicMember:)
|
||||
// - S from Publisher.receive<S>(on:options:)
|
||||
|
||||
/// A property wrapper used to wrap injected observable objects.
|
||||
///
|
||||
/// This is similar to SwiftUI's
|
||||
/// [`StateObject`](https://developer.apple.com/documentation/swiftui/stateobject), but without
|
||||
/// compile-time type restrictions. The lack of compile-time restrictions means that `ObjectType`
|
||||
/// may be a protocol rather than a class.
|
||||
///
|
||||
/// - Important: At runtime, the wrapped value must conform to ``AnyObservableObject``.
|
||||
///
|
||||
/// To pass properties of the observable object down the view hierarchy as bindings, use the
|
||||
/// projected value:
|
||||
/// ```swift
|
||||
/// struct ExampleView: View {
|
||||
/// @Store var viewModel = Factory.shared.resolve(ViewModelProtocol.self)
|
||||
///
|
||||
/// var body: some View {
|
||||
/// TextField("username", text: $viewModel.username)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// Not all injected objects need this property wrapper. See the example projects for examples each
|
||||
/// way.
|
||||
@propertyWrapper
|
||||
public struct Store<ObjectType> {
|
||||
/// The underlying object being stored.
|
||||
public let wrappedValue: ObjectType
|
||||
|
||||
// See https://github.com/Tiny-Home-Consulting/Dependiject/issues/38
|
||||
fileprivate var _observableObject: ObservedObject<ErasedObservableObject>
|
||||
|
||||
@MainActor internal var observableObject: ErasedObservableObject {
|
||||
return _observableObject.wrappedValue
|
||||
}
|
||||
|
||||
/// A projected value which has the same properties as the wrapped value, but presented as
|
||||
/// bindings.
|
||||
///
|
||||
/// Use this to pass bindings down the view hierarchy:
|
||||
/// ```swift
|
||||
/// struct ExampleView: View {
|
||||
/// @Store var viewModel = Factory.shared.resolve(ViewModelProtocol.self)
|
||||
///
|
||||
/// var body: some View {
|
||||
/// TextField("username", text: $viewModel.username)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public var projectedValue: Wrapper {
|
||||
return Wrapper(self)
|
||||
}
|
||||
|
||||
/// Create a stored value on a custom scheduler.
|
||||
///
|
||||
/// Use this init to schedule updates on a specific scheduler other than `DispatchQueue.main`.
|
||||
public init<S: Scheduler>(
|
||||
wrappedValue: ObjectType,
|
||||
on scheduler: S,
|
||||
schedulerOptions: S.SchedulerOptions? = nil
|
||||
) {
|
||||
self.wrappedValue = wrappedValue
|
||||
|
||||
if let observable = wrappedValue as? AnyObservableObject {
|
||||
let objectWillChange = observable.objectWillChange
|
||||
.receive(on: scheduler, options: schedulerOptions)
|
||||
.eraseToAnyPublisher()
|
||||
self._observableObject = .init(initialValue: .init(objectWillChange: objectWillChange))
|
||||
} else {
|
||||
assertionFailure(
|
||||
"Only use the Store property wrapper with objects conforming to AnyObservableObject."
|
||||
)
|
||||
self._observableObject = .init(initialValue: .empty())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a stored value which publishes on the main thread.
|
||||
///
|
||||
/// To control when updates are published, see ``init(wrappedValue:on:schedulerOptions:)``.
|
||||
public init(wrappedValue: ObjectType) {
|
||||
self.init(wrappedValue: wrappedValue, on: DispatchQueue.main)
|
||||
}
|
||||
|
||||
/// An equivalent to SwiftUI's
|
||||
/// [`ObservedObject.Wrapper`](https://developer.apple.com/documentation/swiftui/observedobject/wrapper)
|
||||
/// type.
|
||||
@dynamicMemberLookup
|
||||
public struct Wrapper {
|
||||
private var store: Store
|
||||
|
||||
internal init(_ store: Store<ObjectType>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
/// Returns a binding to the resulting value of a given key path.
|
||||
public subscript<Subject>(
|
||||
dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>
|
||||
) -> Binding<Subject> {
|
||||
return Binding {
|
||||
self.store.wrappedValue[keyPath: keyPath]
|
||||
} set: {
|
||||
self.store.wrappedValue[keyPath: keyPath] = $0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Store: DynamicProperty {
|
||||
public nonisolated mutating func update() {
|
||||
_observableObject.update()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ public class DebridManager: ObservableObject {
|
|||
@Published var allDebrid: AllDebrid = .init()
|
||||
@Published var premiumize: Premiumize = .init()
|
||||
|
||||
lazy var debridSources: [any DebridSource] = [realDebrid, allDebrid, premiumize]
|
||||
lazy var debridSources: [DebridSource] = [realDebrid, allDebrid, premiumize]
|
||||
|
||||
// UI Variables
|
||||
@Published var showWebView: Bool = false
|
||||
|
|
@ -26,6 +26,12 @@ public class DebridManager: ObservableObject {
|
|||
debridSources.contains { $0.isLoggedIn }
|
||||
}
|
||||
|
||||
@Published var selectedDebridId: DebridInfo?
|
||||
|
||||
func debridSourceFromName(_ name: String? = nil) -> DebridSource? {
|
||||
debridSources.first { $0.id.name == name ?? selectedDebridId?.name }
|
||||
}
|
||||
|
||||
// Service agnostic variables
|
||||
@Published var enabledDebrids: Set<DebridType> = [] {
|
||||
didSet {
|
||||
|
|
@ -106,12 +112,16 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
// If a UserDefaults integer isn't set, it's usually 0
|
||||
let rawPreferredService = UserDefaults.standard.integer(forKey: "Debrid.PreferredService")
|
||||
selectedDebridType = DebridType(rawValue: rawPreferredService)
|
||||
let legacyPreferredService = DebridType(rawValue: rawPreferredService)
|
||||
let preferredDebridSource = self.debridSourceFromName(legacyPreferredService?.toString())
|
||||
selectedDebridId = preferredDebridSource?.id
|
||||
|
||||
// If a user has one logged in service, automatically set the preferred service to that one
|
||||
/*
|
||||
if enabledDebrids.count == 1 {
|
||||
selectedDebridType = enabledDebrids.first
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// TODO: Remove this after v0.6.0
|
||||
|
|
@ -255,38 +265,13 @@ public class DebridManager: ObservableObject {
|
|||
return .none
|
||||
}
|
||||
|
||||
switch selectedDebridType {
|
||||
case .realDebrid:
|
||||
guard let realDebridMatch = realDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) else {
|
||||
return .none
|
||||
}
|
||||
let selectedSource = debridSourceFromName()
|
||||
|
||||
if realDebridMatch.files.count > 1 {
|
||||
return .partial
|
||||
} else {
|
||||
return .full
|
||||
}
|
||||
case .allDebrid:
|
||||
guard let allDebridMatch = allDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if allDebridMatch.files.count > 1 {
|
||||
return .partial
|
||||
} else {
|
||||
return .full
|
||||
}
|
||||
case .premiumize:
|
||||
guard let premiumizeMatch = premiumize.IAValues.first(where: { magnetHash == $0.magnet.hash }) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if premiumizeMatch.files.count > 1 {
|
||||
return .partial
|
||||
} else {
|
||||
return .full
|
||||
}
|
||||
case .none:
|
||||
if let selectedSource,
|
||||
let match = selectedSource.IAValues.first(where: { magnetHash == $0.magnet.hash })
|
||||
{
|
||||
return match.files.count > 1 ? .partial : .full
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
|
@ -297,8 +282,8 @@ public class DebridManager: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
switch selectedDebridType {
|
||||
case .realDebrid:
|
||||
switch selectedDebridId?.name {
|
||||
case .some("RealDebrid"):
|
||||
if let realDebridItem = realDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedRealDebridItem = realDebridItem
|
||||
return true
|
||||
|
|
@ -306,7 +291,7 @@ public class DebridManager: ObservableObject {
|
|||
logManager?.error("DebridManager: Could not find the associated RealDebrid entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
case .allDebrid:
|
||||
case .some("AllDebrid"):
|
||||
if let allDebridItem = allDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedAllDebridItem = allDebridItem
|
||||
return true
|
||||
|
|
@ -314,7 +299,7 @@ public class DebridManager: ObservableObject {
|
|||
logManager?.error("DebridManager: Could not find the associated AllDebrid entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
case .premiumize:
|
||||
case .some("Premiumize"):
|
||||
if let premiumizeItem = premiumize.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedPremiumizeItem = premiumizeItem
|
||||
return true
|
||||
|
|
@ -322,7 +307,7 @@ public class DebridManager: ObservableObject {
|
|||
logManager?.error("DebridManager: Could not find the associated Premiumize entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
case .none:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
var failedSourceNames: [String] = []
|
||||
for await (requestResult, sourceName) in group {
|
||||
if let requestResult {
|
||||
if await !debridManager.hasEnabledDebrids {
|
||||
if await debridManager.hasEnabledDebrids {
|
||||
await debridManager.populateDebridIA(requestResult.magnets)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,38 +8,40 @@
|
|||
import SwiftUI
|
||||
|
||||
struct DebridLabelView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
@Store var debridSource: DebridSource
|
||||
|
||||
@State var cloudLinks: [String] = []
|
||||
@State var tagColor: Color = .red
|
||||
var magnet: Magnet?
|
||||
|
||||
var body: some View {
|
||||
if let selectedDebridType = debridManager.selectedDebridType {
|
||||
Tag(
|
||||
name: selectedDebridType.toString(abbreviated: true),
|
||||
color: getTagColor(),
|
||||
horizontalPadding: 5,
|
||||
verticalPadding: 3
|
||||
)
|
||||
Tag(
|
||||
name: debridSource.id.abbreviation,
|
||||
color: tagColor,
|
||||
horizontalPadding: 5,
|
||||
verticalPadding: 3
|
||||
)
|
||||
.onAppear {
|
||||
tagColor = getTagColor()
|
||||
}
|
||||
.onChange(of: debridSource.IAValues) { _ in
|
||||
tagColor = getTagColor()
|
||||
}
|
||||
}
|
||||
|
||||
func getTagColor() -> Color {
|
||||
if let magnet, cloudLinks.isEmpty {
|
||||
switch debridManager.matchMagnetHash(magnet) {
|
||||
case .full:
|
||||
return Color.green
|
||||
case .partial:
|
||||
return Color.orange
|
||||
case .none:
|
||||
return Color.red
|
||||
guard let match = debridSource.IAValues.first(where: { magnet.hash == $0.magnet.hash }) else {
|
||||
return .red
|
||||
}
|
||||
|
||||
return match.files.count > 1 ? .orange : .green
|
||||
} else if cloudLinks.count == 1 {
|
||||
return Color.green
|
||||
return .green
|
||||
} else if cloudLinks.count > 1 {
|
||||
return Color.orange
|
||||
return .orange
|
||||
} else {
|
||||
return Color.red
|
||||
return .red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,23 +15,23 @@ struct SelectedDebridFilterView<Content: View>: View {
|
|||
var body: some View {
|
||||
Menu {
|
||||
Button {
|
||||
debridManager.selectedDebridType = nil
|
||||
debridManager.selectedDebridId = nil
|
||||
} label: {
|
||||
Text("None")
|
||||
|
||||
if debridManager.selectedDebridType == nil {
|
||||
if debridManager.selectedDebridId == nil {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(DebridType.allCases, id: \.self) { (debridType: DebridType) in
|
||||
if debridManager.enabledDebrids.contains(debridType) {
|
||||
ForEach(debridManager.debridSources, id: \.id) { debridSource in
|
||||
if debridSource.isLoggedIn {
|
||||
Button {
|
||||
debridManager.selectedDebridType = debridType
|
||||
debridManager.selectedDebridId = debridSource.id
|
||||
} label: {
|
||||
Text(debridType.toString())
|
||||
Text(debridSource.id.name)
|
||||
|
||||
if debridManager.selectedDebridType == debridType {
|
||||
if debridManager.selectedDebridId == debridSource.id {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,6 @@ struct SelectedDebridFilterView<Content: View>: View {
|
|||
} label: {
|
||||
label
|
||||
}
|
||||
.id(debridManager.selectedDebridType)
|
||||
.id(debridManager.selectedDebridId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ struct AllDebridCloudView: View {
|
|||
HStack {
|
||||
Text(cloudTorrent.status.capitalizingFirstLetter())
|
||||
Spacer()
|
||||
DebridLabelView(cloudLinks: cloudTorrent.links)
|
||||
//DebridLabelView(cloudLinks: cloudTorrent.links)
|
||||
}
|
||||
.font(.caption)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ struct RealDebridCloudView: View {
|
|||
HStack {
|
||||
Text(cloudTorrent.status.capitalizingFirstLetter())
|
||||
Spacer()
|
||||
DebridLabelView(cloudLinks: cloudTorrent.links)
|
||||
//DebridLabelView(cloudLinks: cloudTorrent.links)
|
||||
}
|
||||
.font(.caption)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ struct SearchFilterHeaderView: View {
|
|||
|
||||
SelectedDebridFilterView {
|
||||
FilterLabelView(
|
||||
name: debridManager.selectedDebridType?.toString(),
|
||||
name: debridManager.selectedDebridId?.name,
|
||||
fallbackName: "Debrid"
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ struct SearchResultInfoView: View {
|
|||
Text(size)
|
||||
}
|
||||
|
||||
DebridLabelView(magnet: result.magnet)
|
||||
if let debridSource = debridManager.debridSourceFromName() {
|
||||
DebridLabelView(debridSource: debridSource, magnet: result.magnet)
|
||||
}
|
||||
}
|
||||
.font(.caption)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
|||
struct SettingsDebridInfoView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
||||
let debridType: DebridType
|
||||
@Store var debridSource: DebridSource
|
||||
|
||||
@State private var apiKeyTempText: String = ""
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ struct SettingsDebridInfoView: View {
|
|||
List {
|
||||
Section(header: InlineHeader("Description")) {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("\(debridType.toString()) is a debrid service that is used for unrestricting downloads and media playback. You must pay to access the service.")
|
||||
Text("\(debridSource.id.name) is a debrid service that is used for unrestricting downloads and media playback. You must pay to access the service.")
|
||||
|
||||
Link("Website", destination: URL(string: debridType.website()) ?? URL(string: "https://kingbri.dev/ferrite")!)
|
||||
Link("Website", destination: URL(string: debridSource.id.website) ?? URL(string: "https://kingbri.dev/ferrite")!)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -30,21 +30,21 @@ struct SettingsDebridInfoView: View {
|
|||
) {
|
||||
Button {
|
||||
Task {
|
||||
if debridManager.enabledDebrids.contains(debridType) {
|
||||
await debridManager.logoutDebrid(debridType: debridType)
|
||||
} else if !debridManager.authProcessing(debridType) {
|
||||
await debridManager.authenticateDebrid(debridType: debridType, apiKey: nil)
|
||||
if debridSource.isLoggedIn {
|
||||
//await debridManager.logoutDebrid(debridType: debridType)
|
||||
} else if !debridSource.authProcessing {
|
||||
//await debridManager.authenticateDebrid(debridType: debridType, apiKey: nil)
|
||||
}
|
||||
|
||||
apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
}
|
||||
} label: {
|
||||
Text(
|
||||
debridManager.enabledDebrids.contains(debridType)
|
||||
debridSource.isLoggedIn
|
||||
? "Logout"
|
||||
: (debridManager.authProcessing(debridType) ? "Processing" : "Login")
|
||||
: (debridSource.authProcessing ? "Processing" : "Login")
|
||||
)
|
||||
.foregroundColor(debridManager.enabledDebrids.contains(debridType) ? .red : .blue)
|
||||
.foregroundColor(debridSource.isLoggedIn ? .red : .blue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,22 +57,22 @@ struct SettingsDebridInfoView: View {
|
|||
onCommit: {
|
||||
Task {
|
||||
if !apiKeyTempText.isEmpty {
|
||||
await debridManager.authenticateDebrid(debridType: debridType, apiKey: apiKeyTempText)
|
||||
apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
//await debridManager.authenticateDebrid(debridType: debridType, apiKey: apiKeyTempText)
|
||||
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.fieldDisabled(debridManager.enabledDebrids.contains(debridType))
|
||||
.fieldDisabled(debridSource.isLoggedIn)
|
||||
}
|
||||
.onAppear {
|
||||
Task {
|
||||
apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle(debridType.toString())
|
||||
.navigationTitle(debridSource.id.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ struct LibraryView: View {
|
|||
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
||||
}
|
||||
case .debridCloud:
|
||||
if debridManager.selectedDebridType == nil {
|
||||
if debridManager.selectedDebridId == nil {
|
||||
EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service")
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ struct LibraryView: View {
|
|||
switch navModel.libraryPickerSelection {
|
||||
case .bookmarks, .debridCloud:
|
||||
SelectedDebridFilterView {
|
||||
Text(debridManager.selectedDebridType?.toString(abbreviated: true) ?? "Debrid")
|
||||
Text(debridManager.selectedDebridId?.abbreviation ?? "Debrid")
|
||||
}
|
||||
.transaction {
|
||||
$0.animation = .none
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ struct SettingsView: View {
|
|||
NavView {
|
||||
Form {
|
||||
Section(header: InlineHeader("Debrid services")) {
|
||||
ForEach(DebridType.allCases, id: \.self) { debridType in
|
||||
ForEach(debridManager.debridSources, id: \.id) { (debridSource: DebridSource) in
|
||||
NavigationLink {
|
||||
SettingsDebridInfoView(debridType: debridType)
|
||||
SettingsDebridInfoView(debridSource: debridSource)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(debridType.toString())
|
||||
Text(debridSource.id.name)
|
||||
Spacer()
|
||||
Text(debridManager.enabledDebrids.contains(debridType) ? "Enabled" : "Disabled")
|
||||
Text(debridSource.isLoggedIn ? "Enabled" : "Disabled")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ struct BatchChoiceView: View {
|
|||
var body: some View {
|
||||
NavView {
|
||||
List {
|
||||
switch debridManager.selectedDebridType {
|
||||
case .realDebrid:
|
||||
switch debridManager.selectedDebridId?.name {
|
||||
case .some("RealDebrid"):
|
||||
ForEach(debridManager.selectedRealDebridItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
|
|
@ -34,7 +34,7 @@ struct BatchChoiceView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
case .allDebrid:
|
||||
case .some("AllDebrid"):
|
||||
ForEach(debridManager.selectedAllDebridItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
|
|
@ -44,7 +44,7 @@ struct BatchChoiceView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
case .premiumize:
|
||||
case .some("Premiumize"):
|
||||
ForEach(debridManager.selectedPremiumizeItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
|
|
@ -54,7 +54,7 @@ struct BatchChoiceView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
case .none:
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue