mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-05-21 09:22:07 +00:00
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 */; };
|
0C84FCE729E4B61A00B0DFE4 /* FilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */; };
|
||||||
0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */; };
|
0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */; };
|
||||||
0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C871BDE29994D9D005279AC /* FilterLabelView.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 */; };
|
0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */; };
|
||||||
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */; };
|
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */; };
|
||||||
0C8DC35629CE2ABF008A83AD /* SourceSettingsApiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsApiView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -457,6 +459,7 @@
|
||||||
0C44E2A728D4DDDC007711AE /* Application.swift */,
|
0C44E2A728D4DDDC007711AE /* Application.swift */,
|
||||||
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */,
|
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */,
|
||||||
0CD0265629FEFBF900A83D25 /* FerriteKeychain.swift */,
|
0CD0265629FEFBF900A83D25 /* FerriteKeychain.swift */,
|
||||||
|
0C8AE2472C0FFB6600701675 /* Store.swift */,
|
||||||
);
|
);
|
||||||
path = Utils;
|
path = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -858,6 +861,7 @@
|
||||||
0C794B6B289DACF100DD1CC8 /* PluginCatalogButtonView.swift in Sources */,
|
0C794B6B289DACF100DD1CC8 /* PluginCatalogButtonView.swift in Sources */,
|
||||||
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */,
|
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */,
|
||||||
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
||||||
|
0C8AE2482C0FFB6600701675 /* Store.swift in Sources */,
|
||||||
0C3E00D2296F4FD200ECECB2 /* PluginsView.swift in Sources */,
|
0C3E00D2296F4FD200ECECB2 /* PluginsView.swift in Sources */,
|
||||||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
||||||
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */,
|
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// TODO: Fix errors
|
// TODO: Fix errors
|
||||||
public class AllDebrid: PollingDebridSource {
|
public class AllDebrid: PollingDebridSource, ObservableObject {
|
||||||
public let id = "AllDebrid"
|
public let id = DebridInfo(
|
||||||
public let abbreviation = "AD"
|
name: "AllDebrid", abbreviation: "AD", website: "https://alldebrid.com"
|
||||||
public let website = "https://alldebrid.com"
|
)
|
||||||
public var authTask: Task<Void, Error>?
|
public var authTask: Task<Void, Error>?
|
||||||
|
|
||||||
public var authProcessing: Bool = false
|
public var authProcessing: Bool = false
|
||||||
|
|
@ -178,7 +178,7 @@ public class AllDebrid: PollingDebridSource {
|
||||||
|
|
||||||
return DebridIA(
|
return DebridIA(
|
||||||
magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet),
|
magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet),
|
||||||
source: self.id,
|
source: self.id.name,
|
||||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||||
files: files
|
files: files
|
||||||
)
|
)
|
||||||
|
|
@ -292,7 +292,7 @@ public class AllDebrid: PollingDebridSource {
|
||||||
cloudTorrents = rawResponse.magnets.map { magnetResponse in
|
cloudTorrents = rawResponse.magnets.map { magnetResponse in
|
||||||
DebridCloudTorrent(
|
DebridCloudTorrent(
|
||||||
torrentId: String(magnetResponse.id),
|
torrentId: String(magnetResponse.id),
|
||||||
source: self.id,
|
source: self.id.name,
|
||||||
fileName: magnetResponse.filename,
|
fileName: magnetResponse.filename,
|
||||||
status: magnetResponse.status,
|
status: magnetResponse.status,
|
||||||
hash: magnetResponse.hash,
|
hash: magnetResponse.hash,
|
||||||
|
|
@ -325,7 +325,7 @@ public class AllDebrid: PollingDebridSource {
|
||||||
// The link is also the ID
|
// The link is also the ID
|
||||||
cloudDownloads = rawResponse.links.map { link in
|
cloudDownloads = rawResponse.links.map { link in
|
||||||
DebridCloudDownload(
|
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
|
import Foundation
|
||||||
|
|
||||||
public class Premiumize: OAuthDebridSource {
|
public class Premiumize: OAuthDebridSource, ObservableObject {
|
||||||
public let id = "Premiumize"
|
public let id = DebridInfo(
|
||||||
public let abbreviation = "PM"
|
name: "Premiumize", abbreviation: "PM", website: "https://premiumize.me"
|
||||||
public let website = "https://premiumize.me"
|
)
|
||||||
|
|
||||||
@Published public var authProcessing: Bool = false
|
@Published public var authProcessing: Bool = false
|
||||||
public var isLoggedIn: Bool {
|
public var isLoggedIn: Bool {
|
||||||
|
|
@ -195,7 +195,7 @@ public class Premiumize: OAuthDebridSource {
|
||||||
|
|
||||||
return DebridIA(
|
return DebridIA(
|
||||||
magnet: magnet,
|
magnet: magnet,
|
||||||
source: id,
|
source: id.name,
|
||||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||||
files: files
|
files: files
|
||||||
)
|
)
|
||||||
|
|
@ -300,7 +300,7 @@ public class Premiumize: OAuthDebridSource {
|
||||||
|
|
||||||
// The "link" is the ID for Premiumize
|
// The "link" is the ID for Premiumize
|
||||||
cloudDownloads = rawResponse.files.map { file in
|
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
|
return cloudDownloads
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class RealDebrid: PollingDebridSource {
|
public class RealDebrid: PollingDebridSource, ObservableObject {
|
||||||
public let id = "RealDebrid"
|
public let id = DebridInfo(
|
||||||
public let abbreviation = "RD"
|
name: "RealDebrid", abbreviation: "RD", website: "https://real-debrid.com"
|
||||||
public let website = "https://real-debrid.com"
|
)
|
||||||
public var authTask: Task<Void, Error>?
|
public var authTask: Task<Void, Error>?
|
||||||
|
|
||||||
@Published public var authProcessing: Bool = false
|
@Published public var authProcessing: Bool = false
|
||||||
|
|
@ -20,9 +20,14 @@ public class RealDebrid: PollingDebridSource {
|
||||||
FerriteKeychain.shared.get("RealDebrid.AccessToken") != nil
|
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 cloudDownloads: [DebridCloudDownload] = []
|
||||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||||
|
var cloudTTL: Double = 0.0
|
||||||
|
|
||||||
let baseAuthUrl = "https://api.real-debrid.com/oauth/v2"
|
let baseAuthUrl = "https://api.real-debrid.com/oauth/v2"
|
||||||
let baseApiUrl = "https://api.real-debrid.com/rest/1.0"
|
let baseApiUrl = "https://api.real-debrid.com/rest/1.0"
|
||||||
|
|
@ -282,7 +287,7 @@ public class RealDebrid: PollingDebridSource {
|
||||||
IAValues.append(
|
IAValues.append(
|
||||||
DebridIA(
|
DebridIA(
|
||||||
magnet: Magnet(hash: hash, link: nil),
|
magnet: Magnet(hash: hash, link: nil),
|
||||||
source: id,
|
source: id.name,
|
||||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||||
files: files
|
files: files
|
||||||
)
|
)
|
||||||
|
|
@ -291,7 +296,7 @@ public class RealDebrid: PollingDebridSource {
|
||||||
IAValues.append(
|
IAValues.append(
|
||||||
DebridIA(
|
DebridIA(
|
||||||
magnet: Magnet(hash: hash, link: nil),
|
magnet: Magnet(hash: hash, link: nil),
|
||||||
source: id,
|
source: id.name,
|
||||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||||
files: []
|
files: []
|
||||||
)
|
)
|
||||||
|
|
@ -422,7 +427,7 @@ public class RealDebrid: PollingDebridSource {
|
||||||
cloudTorrents = rawResponse.map { response in
|
cloudTorrents = rawResponse.map { response in
|
||||||
DebridCloudTorrent(
|
DebridCloudTorrent(
|
||||||
torrentId: response.id,
|
torrentId: response.id,
|
||||||
source: self.id,
|
source: self.id.name,
|
||||||
fileName: response.filename,
|
fileName: response.filename,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
hash: response.hash,
|
hash: response.hash,
|
||||||
|
|
@ -448,7 +453,7 @@ public class RealDebrid: PollingDebridSource {
|
||||||
let data = try await performRequest(request: &request, requestName: #function)
|
let data = try await performRequest(request: &request, requestName: #function)
|
||||||
let rawResponse = try jsonDecoder.decode([UserDownloadsResponse].self, from: data)
|
let rawResponse = try jsonDecoder.decode([UserDownloadsResponse].self, from: data)
|
||||||
cloudDownloads = rawResponse.map { response in
|
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
|
return cloudDownloads
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
public struct DebridInfo: Hashable, Sendable {
|
||||||
|
let name: String
|
||||||
|
let abbreviation: String
|
||||||
|
let website: String
|
||||||
|
}
|
||||||
|
|
||||||
public struct DebridIA: Hashable, Sendable {
|
public struct DebridIA: Hashable, Sendable {
|
||||||
let magnet: Magnet
|
let magnet: Magnet
|
||||||
let source: String
|
let source: String
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol DebridSource: ObservableObject {
|
public protocol DebridSource: AnyObservableObject {
|
||||||
// ID of the service
|
// ID of the service
|
||||||
var id: String { get }
|
var id: DebridInfo { get }
|
||||||
var abbreviation: String { get }
|
|
||||||
var website: String { get }
|
|
||||||
|
|
||||||
// Auth variables
|
// Auth variables
|
||||||
var authProcessing: Bool { get set }
|
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 allDebrid: AllDebrid = .init()
|
||||||
@Published var premiumize: Premiumize = .init()
|
@Published var premiumize: Premiumize = .init()
|
||||||
|
|
||||||
lazy var debridSources: [any DebridSource] = [realDebrid, allDebrid, premiumize]
|
lazy var debridSources: [DebridSource] = [realDebrid, allDebrid, premiumize]
|
||||||
|
|
||||||
// UI Variables
|
// UI Variables
|
||||||
@Published var showWebView: Bool = false
|
@Published var showWebView: Bool = false
|
||||||
|
|
@ -26,6 +26,12 @@ public class DebridManager: ObservableObject {
|
||||||
debridSources.contains { $0.isLoggedIn }
|
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
|
// Service agnostic variables
|
||||||
@Published var enabledDebrids: Set<DebridType> = [] {
|
@Published var enabledDebrids: Set<DebridType> = [] {
|
||||||
didSet {
|
didSet {
|
||||||
|
|
@ -106,12 +112,16 @@ public class DebridManager: ObservableObject {
|
||||||
|
|
||||||
// If a UserDefaults integer isn't set, it's usually 0
|
// If a UserDefaults integer isn't set, it's usually 0
|
||||||
let rawPreferredService = UserDefaults.standard.integer(forKey: "Debrid.PreferredService")
|
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 a user has one logged in service, automatically set the preferred service to that one
|
||||||
|
/*
|
||||||
if enabledDebrids.count == 1 {
|
if enabledDebrids.count == 1 {
|
||||||
selectedDebridType = enabledDebrids.first
|
selectedDebridType = enabledDebrids.first
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this after v0.6.0
|
// TODO: Remove this after v0.6.0
|
||||||
|
|
@ -255,38 +265,13 @@ public class DebridManager: ObservableObject {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selectedDebridType {
|
let selectedSource = debridSourceFromName()
|
||||||
case .realDebrid:
|
|
||||||
guard let realDebridMatch = realDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) else {
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
if realDebridMatch.files.count > 1 {
|
if let selectedSource,
|
||||||
return .partial
|
let match = selectedSource.IAValues.first(where: { magnetHash == $0.magnet.hash })
|
||||||
} else {
|
{
|
||||||
return .full
|
return match.files.count > 1 ? .partial : .full
|
||||||
}
|
} else {
|
||||||
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:
|
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +282,8 @@ public class DebridManager: ObservableObject {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selectedDebridType {
|
switch selectedDebridId?.name {
|
||||||
case .realDebrid:
|
case .some("RealDebrid"):
|
||||||
if let realDebridItem = realDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
if let realDebridItem = realDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||||
selectedRealDebridItem = realDebridItem
|
selectedRealDebridItem = realDebridItem
|
||||||
return true
|
return true
|
||||||
|
|
@ -306,7 +291,7 @@ public class DebridManager: ObservableObject {
|
||||||
logManager?.error("DebridManager: Could not find the associated RealDebrid entry for magnet hash \(magnetHash)")
|
logManager?.error("DebridManager: Could not find the associated RealDebrid entry for magnet hash \(magnetHash)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case .allDebrid:
|
case .some("AllDebrid"):
|
||||||
if let allDebridItem = allDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
if let allDebridItem = allDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||||
selectedAllDebridItem = allDebridItem
|
selectedAllDebridItem = allDebridItem
|
||||||
return true
|
return true
|
||||||
|
|
@ -314,7 +299,7 @@ public class DebridManager: ObservableObject {
|
||||||
logManager?.error("DebridManager: Could not find the associated AllDebrid entry for magnet hash \(magnetHash)")
|
logManager?.error("DebridManager: Could not find the associated AllDebrid entry for magnet hash \(magnetHash)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case .premiumize:
|
case .some("Premiumize"):
|
||||||
if let premiumizeItem = premiumize.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
if let premiumizeItem = premiumize.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||||
selectedPremiumizeItem = premiumizeItem
|
selectedPremiumizeItem = premiumizeItem
|
||||||
return true
|
return true
|
||||||
|
|
@ -322,7 +307,7 @@ public class DebridManager: ObservableObject {
|
||||||
logManager?.error("DebridManager: Could not find the associated Premiumize entry for magnet hash \(magnetHash)")
|
logManager?.error("DebridManager: Could not find the associated Premiumize entry for magnet hash \(magnetHash)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case .none:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class ScrapingViewModel: ObservableObject {
|
||||||
var failedSourceNames: [String] = []
|
var failedSourceNames: [String] = []
|
||||||
for await (requestResult, sourceName) in group {
|
for await (requestResult, sourceName) in group {
|
||||||
if let requestResult {
|
if let requestResult {
|
||||||
if await !debridManager.hasEnabledDebrids {
|
if await debridManager.hasEnabledDebrids {
|
||||||
await debridManager.populateDebridIA(requestResult.magnets)
|
await debridManager.populateDebridIA(requestResult.magnets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,40 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DebridLabelView: View {
|
struct DebridLabelView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@Store var debridSource: DebridSource
|
||||||
|
|
||||||
@State var cloudLinks: [String] = []
|
@State var cloudLinks: [String] = []
|
||||||
|
@State var tagColor: Color = .red
|
||||||
var magnet: Magnet?
|
var magnet: Magnet?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let selectedDebridType = debridManager.selectedDebridType {
|
Tag(
|
||||||
Tag(
|
name: debridSource.id.abbreviation,
|
||||||
name: selectedDebridType.toString(abbreviated: true),
|
color: tagColor,
|
||||||
color: getTagColor(),
|
horizontalPadding: 5,
|
||||||
horizontalPadding: 5,
|
verticalPadding: 3
|
||||||
verticalPadding: 3
|
)
|
||||||
)
|
.onAppear {
|
||||||
|
tagColor = getTagColor()
|
||||||
|
}
|
||||||
|
.onChange(of: debridSource.IAValues) { _ in
|
||||||
|
tagColor = getTagColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTagColor() -> Color {
|
func getTagColor() -> Color {
|
||||||
if let magnet, cloudLinks.isEmpty {
|
if let magnet, cloudLinks.isEmpty {
|
||||||
switch debridManager.matchMagnetHash(magnet) {
|
guard let match = debridSource.IAValues.first(where: { magnet.hash == $0.magnet.hash }) else {
|
||||||
case .full:
|
return .red
|
||||||
return Color.green
|
|
||||||
case .partial:
|
|
||||||
return Color.orange
|
|
||||||
case .none:
|
|
||||||
return Color.red
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return match.files.count > 1 ? .orange : .green
|
||||||
} else if cloudLinks.count == 1 {
|
} else if cloudLinks.count == 1 {
|
||||||
return Color.green
|
return .green
|
||||||
} else if cloudLinks.count > 1 {
|
} else if cloudLinks.count > 1 {
|
||||||
return Color.orange
|
return .orange
|
||||||
} else {
|
} else {
|
||||||
return Color.red
|
return .red
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,23 @@ struct SelectedDebridFilterView<Content: View>: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
Button {
|
Button {
|
||||||
debridManager.selectedDebridType = nil
|
debridManager.selectedDebridId = nil
|
||||||
} label: {
|
} label: {
|
||||||
Text("None")
|
Text("None")
|
||||||
|
|
||||||
if debridManager.selectedDebridType == nil {
|
if debridManager.selectedDebridId == nil {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(DebridType.allCases, id: \.self) { (debridType: DebridType) in
|
ForEach(debridManager.debridSources, id: \.id) { debridSource in
|
||||||
if debridManager.enabledDebrids.contains(debridType) {
|
if debridSource.isLoggedIn {
|
||||||
Button {
|
Button {
|
||||||
debridManager.selectedDebridType = debridType
|
debridManager.selectedDebridId = debridSource.id
|
||||||
} label: {
|
} label: {
|
||||||
Text(debridType.toString())
|
Text(debridSource.id.name)
|
||||||
|
|
||||||
if debridManager.selectedDebridType == debridType {
|
if debridManager.selectedDebridId == debridSource.id {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +40,6 @@ struct SelectedDebridFilterView<Content: View>: View {
|
||||||
} label: {
|
} label: {
|
||||||
label
|
label
|
||||||
}
|
}
|
||||||
.id(debridManager.selectedDebridType)
|
.id(debridManager.selectedDebridId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ struct AllDebridCloudView: View {
|
||||||
HStack {
|
HStack {
|
||||||
Text(cloudTorrent.status.capitalizingFirstLetter())
|
Text(cloudTorrent.status.capitalizingFirstLetter())
|
||||||
Spacer()
|
Spacer()
|
||||||
DebridLabelView(cloudLinks: cloudTorrent.links)
|
//DebridLabelView(cloudLinks: cloudTorrent.links)
|
||||||
}
|
}
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ struct RealDebridCloudView: View {
|
||||||
HStack {
|
HStack {
|
||||||
Text(cloudTorrent.status.capitalizingFirstLetter())
|
Text(cloudTorrent.status.capitalizingFirstLetter())
|
||||||
Spacer()
|
Spacer()
|
||||||
DebridLabelView(cloudLinks: cloudTorrent.links)
|
//DebridLabelView(cloudLinks: cloudTorrent.links)
|
||||||
}
|
}
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ struct SearchFilterHeaderView: View {
|
||||||
|
|
||||||
SelectedDebridFilterView {
|
SelectedDebridFilterView {
|
||||||
FilterLabelView(
|
FilterLabelView(
|
||||||
name: debridManager.selectedDebridType?.toString(),
|
name: debridManager.selectedDebridId?.name,
|
||||||
fallbackName: "Debrid"
|
fallbackName: "Debrid"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ struct SearchResultInfoView: View {
|
||||||
Text(size)
|
Text(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
DebridLabelView(magnet: result.magnet)
|
if let debridSource = debridManager.debridSourceFromName() {
|
||||||
|
DebridLabelView(debridSource: debridSource, magnet: result.magnet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||||
struct SettingsDebridInfoView: View {
|
struct SettingsDebridInfoView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
|
|
||||||
let debridType: DebridType
|
@Store var debridSource: DebridSource
|
||||||
|
|
||||||
@State private var apiKeyTempText: String = ""
|
@State private var apiKeyTempText: String = ""
|
||||||
|
|
||||||
|
|
@ -18,9 +18,9 @@ struct SettingsDebridInfoView: View {
|
||||||
List {
|
List {
|
||||||
Section(header: InlineHeader("Description")) {
|
Section(header: InlineHeader("Description")) {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
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 {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
if debridManager.enabledDebrids.contains(debridType) {
|
if debridSource.isLoggedIn {
|
||||||
await debridManager.logoutDebrid(debridType: debridType)
|
//await debridManager.logoutDebrid(debridType: debridType)
|
||||||
} else if !debridManager.authProcessing(debridType) {
|
} else if !debridSource.authProcessing {
|
||||||
await debridManager.authenticateDebrid(debridType: debridType, apiKey: nil)
|
//await debridManager.authenticateDebrid(debridType: debridType, apiKey: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text(
|
Text(
|
||||||
debridManager.enabledDebrids.contains(debridType)
|
debridSource.isLoggedIn
|
||||||
? "Logout"
|
? "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: {
|
onCommit: {
|
||||||
Task {
|
Task {
|
||||||
if !apiKeyTempText.isEmpty {
|
if !apiKeyTempText.isEmpty {
|
||||||
await debridManager.authenticateDebrid(debridType: debridType, apiKey: apiKeyTempText)
|
//await debridManager.authenticateDebrid(debridType: debridType, apiKey: apiKeyTempText)
|
||||||
apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.fieldDisabled(debridManager.enabledDebrids.contains(debridType))
|
.fieldDisabled(debridSource.isLoggedIn)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task {
|
Task {
|
||||||
apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.insetGrouped)
|
.listStyle(.insetGrouped)
|
||||||
.navigationTitle(debridType.toString())
|
.navigationTitle(debridSource.id.name)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ struct LibraryView: View {
|
||||||
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
||||||
}
|
}
|
||||||
case .debridCloud:
|
case .debridCloud:
|
||||||
if debridManager.selectedDebridType == nil {
|
if debridManager.selectedDebridId == nil {
|
||||||
EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service")
|
EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +69,7 @@ struct LibraryView: View {
|
||||||
switch navModel.libraryPickerSelection {
|
switch navModel.libraryPickerSelection {
|
||||||
case .bookmarks, .debridCloud:
|
case .bookmarks, .debridCloud:
|
||||||
SelectedDebridFilterView {
|
SelectedDebridFilterView {
|
||||||
Text(debridManager.selectedDebridType?.toString(abbreviated: true) ?? "Debrid")
|
Text(debridManager.selectedDebridId?.abbreviation ?? "Debrid")
|
||||||
}
|
}
|
||||||
.transaction {
|
.transaction {
|
||||||
$0.animation = .none
|
$0.animation = .none
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,14 @@ struct SettingsView: View {
|
||||||
NavView {
|
NavView {
|
||||||
Form {
|
Form {
|
||||||
Section(header: InlineHeader("Debrid services")) {
|
Section(header: InlineHeader("Debrid services")) {
|
||||||
ForEach(DebridType.allCases, id: \.self) { debridType in
|
ForEach(debridManager.debridSources, id: \.id) { (debridSource: DebridSource) in
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
SettingsDebridInfoView(debridType: debridType)
|
SettingsDebridInfoView(debridSource: debridSource)
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(debridType.toString())
|
Text(debridSource.id.name)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(debridManager.enabledDebrids.contains(debridType) ? "Enabled" : "Disabled")
|
Text(debridSource.isLoggedIn ? "Enabled" : "Disabled")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ struct BatchChoiceView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
List {
|
List {
|
||||||
switch debridManager.selectedDebridType {
|
switch debridManager.selectedDebridId?.name {
|
||||||
case .realDebrid:
|
case .some("RealDebrid"):
|
||||||
ForEach(debridManager.selectedRealDebridItem?.files ?? [], id: \.self) { file in
|
ForEach(debridManager.selectedRealDebridItem?.files ?? [], id: \.self) { file in
|
||||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||||
Button(file.name) {
|
Button(file.name) {
|
||||||
|
|
@ -34,7 +34,7 @@ struct BatchChoiceView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .allDebrid:
|
case .some("AllDebrid"):
|
||||||
ForEach(debridManager.selectedAllDebridItem?.files ?? [], id: \.self) { file in
|
ForEach(debridManager.selectedAllDebridItem?.files ?? [], id: \.self) { file in
|
||||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||||
Button(file.name) {
|
Button(file.name) {
|
||||||
|
|
@ -44,7 +44,7 @@ struct BatchChoiceView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .premiumize:
|
case .some("Premiumize"):
|
||||||
ForEach(debridManager.selectedPremiumizeItem?.files ?? [], id: \.self) { file in
|
ForEach(debridManager.selectedPremiumizeItem?.files ?? [], id: \.self) { file in
|
||||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||||
Button(file.name) {
|
Button(file.name) {
|
||||||
|
|
@ -54,7 +54,7 @@ struct BatchChoiceView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .none:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue