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:
kingbri 2024-06-05 12:33:11 -04:00
parent b80f8900b7
commit 07731e7b00
19 changed files with 272 additions and 122 deletions

View file

@ -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 */,

View file

@ -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
)
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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()
}
}

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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
}
}
}

View file

@ -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)
}
}

View file

@ -102,7 +102,7 @@ struct AllDebridCloudView: View {
HStack {
Text(cloudTorrent.status.capitalizingFirstLetter())
Spacer()
DebridLabelView(cloudLinks: cloudTorrent.links)
//DebridLabelView(cloudLinks: cloudTorrent.links)
}
.font(.caption)
}

View file

@ -103,7 +103,7 @@ struct RealDebridCloudView: View {
HStack {
Text(cloudTorrent.status.capitalizingFirstLetter())
Spacer()
DebridLabelView(cloudLinks: cloudTorrent.links)
//DebridLabelView(cloudLinks: cloudTorrent.links)
}
.font(.caption)
}

View file

@ -53,7 +53,7 @@ struct SearchFilterHeaderView: View {
SelectedDebridFilterView {
FilterLabelView(
name: debridManager.selectedDebridType?.toString(),
name: debridManager.selectedDebridId?.name,
fallbackName: "Debrid"
)
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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()
}
}