From 39a705717e72d1c58d78e4d5dbbbcb312fe01d50 Mon Sep 17 00:00:00 2001 From: kingbri Date: Fri, 24 Mar 2023 16:22:24 -0400 Subject: [PATCH] Plugins: Unify settings Plugin settings used to only be available for installed sources. Change this to display info about an installed plugin and add settings depending on the plugin type. For example, a source will have additional settings specified by its own views. Signed-off-by: kingbri --- Ferrite.xcodeproj/project.pbxproj | 16 ++ Ferrite/ViewModels/NavigationViewModel.swift | 5 - .../Buttons/InstalledPluginButtonView.swift | 19 +- .../Plugin/PluginAggregateView.swift | 16 +- .../Plugin/PluginInfoView.swift | 78 +++++++ .../Plugin/Source/SourceSettingsApiView.swift | 54 +++++ .../Source/SourceSettingsBaseUrlView.swift | 36 +++ .../Source/SourceSettingsMethodView.swift | 62 ++++++ .../Plugin/Source/SourceSettingsView.swift | 208 +----------------- 9 files changed, 275 insertions(+), 219 deletions(-) create mode 100644 Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift create mode 100644 Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift create mode 100644 Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift create mode 100644 Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index f85a249..081ab6e 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -93,6 +93,10 @@ 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */; }; 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */; }; 0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C871BDE29994D9D005279AC /* FilterLabelView.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 */; }; + 0C8DC35829CE2ACA008A83AD /* SourceSettingsMethodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35729CE2ACA008A83AD /* SourceSettingsMethodView.swift */; }; 0C95D8D828A55B03005E22B3 /* DefaultActionPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D728A55B03005E22B3 /* DefaultActionPickerView.swift */; }; 0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsPluginListView.swift */; }; 0CA05459288EE9E600850554 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05458288EE9E600850554 /* PluginManager.swift */; }; @@ -228,6 +232,10 @@ 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataClass.swift"; sourceTree = ""; }; 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataProperties.swift"; sourceTree = ""; }; 0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = ""; }; + 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInfoView.swift; sourceTree = ""; }; + 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsBaseUrlView.swift; sourceTree = ""; }; + 0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsApiView.swift; sourceTree = ""; }; + 0C8DC35729CE2ACA008A83AD /* SourceSettingsMethodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsMethodView.swift; sourceTree = ""; }; 0C95D8D728A55B03005E22B3 /* DefaultActionPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultActionPickerView.swift; sourceTree = ""; }; 0CA05456288EE58200850554 /* SettingsPluginListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPluginListView.swift; sourceTree = ""; }; 0CA05458288EE9E600850554 /* PluginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginManager.swift; sourceTree = ""; }; @@ -417,6 +425,7 @@ 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */, 0C5005512992B6750064606A /* PluginTagsView.swift */, 0CD5F1FC299C083B00476DDB /* PluginPickerView.swift */, + 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */, ); path = Plugin; sourceTree = ""; @@ -475,6 +484,9 @@ isa = PBXGroup; children = ( 0C733286289C4C820058D1FE /* SourceSettingsView.swift */, + 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */, + 0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */, + 0C8DC35729CE2ACA008A83AD /* SourceSettingsMethodView.swift */, ); path = Source; sourceTree = ""; @@ -773,6 +785,7 @@ 0C6771FA29B3D1AE005D38D2 /* KodiModels.swift in Sources */, 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */, 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */, + 0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */, 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */, 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */, 0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */, @@ -824,6 +837,7 @@ 0CA148E1288903F000DE2211 /* Collection.swift in Sources */, 0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */, 0C794B69289DACC800DD1CC8 /* InstalledPluginButtonView.swift in Sources */, + 0C8DC35829CE2ACA008A83AD /* SourceSettingsMethodView.swift in Sources */, 0C5708EB29B8F89300BE07F9 /* SettingsLogView.swift in Sources */, 0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */, 0C6771FE29B521F1005D38D2 /* SettingsDebridInfoView.swift in Sources */, @@ -869,7 +883,9 @@ 0C6771F429B3B4FD005D38D2 /* KodiWrapper.swift in Sources */, 0C3E00D0296F4DB200ECECB2 /* ActionModels.swift in Sources */, 0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */, + 0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */, 0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */, + 0C8DC35629CE2ABF008A83AD /* SourceSettingsApiView.swift in Sources */, 0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */, 0C0974B029CCAAAF006DE7A3 /* OperatingSystemVersion.swift in Sources */, 0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */, diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index 749632f..1056d99 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -58,11 +58,6 @@ public class NavigationViewModel: ObservableObject { @Published var selectedTab: ViewTab = .search - // TODO: Maybe move these to their own StateObjects? - // Used between SourceListView and SourceSettingsView - @Published var showSourceSettings: Bool = false - var selectedSource: Source? - // Used between service views and editor views in Settings @Published var selectedPluginList: PluginList? @Published var selectedKodiServer: KodiServer? diff --git a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift index d35f6ab..30f6be8 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift @@ -10,10 +10,11 @@ import SwiftUI struct InstalledPluginButtonView: View { let backgroundContext = PersistenceController.shared.backgroundContext - @EnvironmentObject var navModel: NavigationViewModel - @ObservedObject var installedPlugin: P + @Binding var showPluginOptions: Bool + @Binding var selectedPlugin: P? + var body: some View { Toggle(isOn: Binding( get: { installedPlugin.enabled }, @@ -42,14 +43,12 @@ struct InstalledPluginButtonView: View { .padding(.vertical, 2) } .contextMenu { - if let installedSource = installedPlugin as? Source { - Button { - navModel.selectedSource = installedSource - navModel.showSourceSettings.toggle() - } label: { - Text("Settings") - Image(systemName: "gear") - } + Button { + selectedPlugin = installedPlugin + showPluginOptions.toggle() + } label: { + Text("Options") + Image(systemName: "gear") } if #available(iOS 15.0, *) { diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift index 19252ac..08c2ab6 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift @@ -27,6 +27,9 @@ struct PluginAggregateView: View { @State private var sourcePredicate: NSPredicate? + @State private var showPluginOptions = false + @State private var selectedPlugin: P? + var body: some View { ZStack { DynamicFetchRequest(predicate: sourcePredicate) { (installedPlugins: FetchedResults

) in @@ -49,7 +52,11 @@ struct PluginAggregateView: View { if !installedPlugins.isEmpty { Section(header: InlineHeader("Installed")) { ForEach(installedPlugins, id: \.self) { installedPlugin in - InstalledPluginButtonView(installedPlugin: installedPlugin) + InstalledPluginButtonView( + installedPlugin: installedPlugin, + showPluginOptions: $showPluginOptions, + selectedPlugin: $selectedPlugin + ) } } } @@ -82,11 +89,8 @@ struct PluginAggregateView: View { } } } - .sheet(isPresented: $navModel.showSourceSettings) { - if String(describing: P.self) == "Source" { - SourceSettingsView() - .environmentObject(navModel) - } + .sheet(isPresented: $showPluginOptions) { + PluginInfoView(selectedPlugin: $selectedPlugin) } } } diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift new file mode 100644 index 0000000..0d5b5c5 --- /dev/null +++ b/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift @@ -0,0 +1,78 @@ +// +// PluginInfoView.swift +// Ferrite +// +// Created by Brian Dashore on 3/24/23. +// + +import SwiftUI + +struct PluginInfoView: View { + @Environment(\.presentationMode) var presentationMode + + @Binding var selectedPlugin: P? + + @FetchRequest( + entity: PluginList.entity(), + sortDescriptors: [] + ) var pluginLists: FetchedResults + + var body: some View { + NavView { + List { + if let selectedPlugin { + Section(header: InlineHeader("Info")) { + VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 5) { + HStack(spacing: 5) { + Text(selectedPlugin.name) + + Text("v\(selectedPlugin.version)") + .foregroundColor(.secondary) + } + + Text("by \(selectedPlugin.author)") + .foregroundColor(.secondary) + + Group { + Text("ID: \(selectedPlugin.id)") + + if let pluginList = pluginLists.first(where: { $0.id == selectedPlugin.listId }) + { + Text("List: \(pluginList.name)") + Text("List ID: \(pluginList.id.uuidString)") + } else { + Text("No plugin list found. This source should be removed.") + } + } + .foregroundColor(.secondary) + .font(.caption) + } + + if let tags = selectedPlugin.getTags(), !tags.isEmpty { + PluginTagsView(tags: tags) + } + } + .padding(.vertical, 2) + } + + if let selectedSource = selectedPlugin as? Source { + SourceSettingsView(selectedSource: selectedSource) + } + } + } + .listStyle(.insetGrouped) + .onDisappear { + PersistenceController.shared.save() + } + .navigationTitle("Plugin Options") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + presentationMode.wrappedValue.dismiss() + } + } + } + } + } +} diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift new file mode 100644 index 0000000..30ada8b --- /dev/null +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift @@ -0,0 +1,54 @@ +// +// SourceSettingsApiView.swift +// Ferrite +// +// Created by Brian Dashore on 3/24/23. +// + +import SwiftUI + +struct SourceSettingsApiView: View { + @ObservedObject var selectedSourceApi: SourceApi + + @State private var tempClientId: String = "" + @State private var tempClientSecret: String = "" + + enum Field { + case secure, plain + } + + var body: some View { + Section( + header: InlineHeader("API credentials"), + footer: Text("Grab the required API credentials from the website. A client secret can be an API token.") + ) { + if let clientId = selectedSourceApi.clientId, clientId.dynamic { + TextField("Client ID", text: $tempClientId, onEditingChanged: { isFocused in + if !isFocused { + clientId.value = tempClientId + clientId.timeStamp = Date() + } + }) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .backport.onAppear { + tempClientId = clientId.value ?? "" + } + } + + if let clientSecret = selectedSourceApi.clientSecret, clientSecret.dynamic { + TextField("Token", text: $tempClientSecret, onEditingChanged: { isFocused in + if !isFocused { + clientSecret.value = tempClientSecret + clientSecret.timeStamp = Date() + } + }) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .backport.onAppear { + tempClientSecret = clientSecret.value ?? "" + } + } + } + } +} diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift new file mode 100644 index 0000000..dadcc97 --- /dev/null +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift @@ -0,0 +1,36 @@ +// +// SourceSettingsBaseUrlView.swift +// Ferrite +// +// Created by Brian Dashore on 3/24/23. +// + +import SwiftUI + +struct SourceSettingsBaseUrlView: View { + @ObservedObject var selectedSource: Source + + @State private var tempBaseUrl: String = "" + var body: some View { + Section( + header: InlineHeader("Base URL"), + footer: Text("Enter the base URL of your server.") + ) { + TextField("https://...", text: $tempBaseUrl, onEditingChanged: { isFocused in + if !isFocused { + if tempBaseUrl.last == "/" { + selectedSource.baseUrl = String(tempBaseUrl.dropLast()) + } else { + selectedSource.baseUrl = tempBaseUrl + } + } + }) + .keyboardType(.URL) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .backport.onAppear { + tempBaseUrl = selectedSource.baseUrl ?? "" + } + } + } +} diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift new file mode 100644 index 0000000..ca64b7a --- /dev/null +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift @@ -0,0 +1,62 @@ +// +// SourceSettingsMethodView.swift +// Ferrite +// +// Created by Brian Dashore on 3/24/23. +// + +import SwiftUI + +struct SourceSettingsMethodView: View { + @ObservedObject var selectedSource: Source + + var body: some View { + Section(header: InlineHeader("Fetch method")) { + if selectedSource.jsonParser != nil { + Button { + selectedSource.preferredParser = SourcePreferredParser.siteApi.rawValue + } label: { + HStack { + Text("Website API") + Spacer() + if SourcePreferredParser.siteApi.rawValue == selectedSource.preferredParser { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + } + } + + if selectedSource.rssParser != nil { + Button { + selectedSource.preferredParser = SourcePreferredParser.rss.rawValue + } label: { + HStack { + Text("RSS") + Spacer() + if SourcePreferredParser.rss.rawValue == selectedSource.preferredParser { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + } + } + + if selectedSource.htmlParser != nil { + Button { + selectedSource.preferredParser = SourcePreferredParser.scraping.rawValue + } label: { + HStack { + Text("Web scraping") + Spacer() + if SourcePreferredParser.scraping.rawValue == selectedSource.preferredParser { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + } + } + } + .backport.tint(.primary) + } +} diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift index f402137..cc7a3a9 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsView.swift @@ -8,207 +8,19 @@ import SwiftUI struct SourceSettingsView: View { - @Environment(\.presentationMode) var presentationMode - - @EnvironmentObject var navModel: NavigationViewModel - - @FetchRequest( - entity: PluginList.entity(), - sortDescriptors: [] - ) var pluginLists: FetchedResults - - var body: some View { - NavView { - List { - if let selectedSource = navModel.selectedSource { - Section(header: InlineHeader("Info")) { - VStack(alignment: .leading) { - VStack(alignment: .leading, spacing: 5) { - HStack { - Text(selectedSource.name) - - Text("v\(selectedSource.version)") - .foregroundColor(.secondary) - } - - Text("by \(selectedSource.author)") - .foregroundColor(.secondary) - - Group { - Text("ID: \(selectedSource.id)") - - if let pluginList = pluginLists.first(where: { $0.id == selectedSource.listId }) - { - Text("List: \(pluginList.name)") - Text("List ID: \(pluginList.id.uuidString)") - } else { - Text("No plugin list found. This source should be removed.") - } - } - .foregroundColor(.secondary) - .font(.caption) - } - - if let tags = selectedSource.getTags(), !tags.isEmpty { - PluginTagsView(tags: tags) - } - } - .padding(.vertical, 2) - } - - if selectedSource.dynamicBaseUrl { - SourceSettingsBaseUrlView(selectedSource: selectedSource) - } - - if let sourceApi = selectedSource.api, - sourceApi.clientId?.dynamic ?? false || sourceApi.clientSecret?.dynamic ?? false - { - SourceSettingsApiView(selectedSourceApi: sourceApi) - } - - SourceSettingsMethodView(selectedSource: selectedSource) - } - } - .listStyle(.insetGrouped) - .onDisappear { - PersistenceController.shared.save() - } - .navigationTitle("Source Settings") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - presentationMode.wrappedValue.dismiss() - } - } - } - } - } -} - -struct SourceSettingsBaseUrlView: View { - @ObservedObject var selectedSource: Source - - @State private var tempBaseUrl: String = "" - var body: some View { - Section( - header: InlineHeader("Base URL"), - footer: Text("Enter the base URL of your server.") - ) { - TextField("https://...", text: $tempBaseUrl, onEditingChanged: { isFocused in - if !isFocused { - if tempBaseUrl.last == "/" { - selectedSource.baseUrl = String(tempBaseUrl.dropLast()) - } else { - selectedSource.baseUrl = tempBaseUrl - } - } - }) - .keyboardType(.URL) - .autocorrectionDisabled(true) - .autocapitalization(.none) - .backport.onAppear { - tempBaseUrl = selectedSource.baseUrl ?? "" - } - } - } -} - -struct SourceSettingsApiView: View { - @ObservedObject var selectedSourceApi: SourceApi - - @State private var tempClientId: String = "" - @State private var tempClientSecret: String = "" - - enum Field { - case secure, plain - } - - var body: some View { - Section( - header: InlineHeader("API credentials"), - footer: Text("Grab the required API credentials from the website. A client secret can be an API token.") - ) { - if let clientId = selectedSourceApi.clientId, clientId.dynamic { - TextField("Client ID", text: $tempClientId, onEditingChanged: { isFocused in - if !isFocused { - clientId.value = tempClientId - clientId.timeStamp = Date() - } - }) - .autocorrectionDisabled(true) - .autocapitalization(.none) - .backport.onAppear { - tempClientId = clientId.value ?? "" - } - } - - if let clientSecret = selectedSourceApi.clientSecret, clientSecret.dynamic { - TextField("Token", text: $tempClientSecret, onEditingChanged: { isFocused in - if !isFocused { - clientSecret.value = tempClientSecret - clientSecret.timeStamp = Date() - } - }) - .autocorrectionDisabled(true) - .autocapitalization(.none) - .backport.onAppear { - tempClientSecret = clientSecret.value ?? "" - } - } - } - } -} - -struct SourceSettingsMethodView: View { @ObservedObject var selectedSource: Source var body: some View { - Section(header: InlineHeader("Fetch method")) { - if selectedSource.jsonParser != nil { - Button { - selectedSource.preferredParser = SourcePreferredParser.siteApi.rawValue - } label: { - HStack { - Text("Website API") - Spacer() - if SourcePreferredParser.siteApi.rawValue == selectedSource.preferredParser { - Image(systemName: "checkmark") - .foregroundColor(.blue) - } - } - } - } - - if selectedSource.rssParser != nil { - Button { - selectedSource.preferredParser = SourcePreferredParser.rss.rawValue - } label: { - HStack { - Text("RSS") - Spacer() - if SourcePreferredParser.rss.rawValue == selectedSource.preferredParser { - Image(systemName: "checkmark") - .foregroundColor(.blue) - } - } - } - } - - if selectedSource.htmlParser != nil { - Button { - selectedSource.preferredParser = SourcePreferredParser.scraping.rawValue - } label: { - HStack { - Text("Web scraping") - Spacer() - if SourcePreferredParser.scraping.rawValue == selectedSource.preferredParser { - Image(systemName: "checkmark") - .foregroundColor(.blue) - } - } - } - } + if selectedSource.dynamicBaseUrl { + SourceSettingsBaseUrlView(selectedSource: selectedSource) } - .backport.tint(.primary) + + if let sourceApi = selectedSource.api, + sourceApi.clientId?.dynamic ?? false || sourceApi.clientSecret?.dynamic ?? false + { + SourceSettingsApiView(selectedSourceApi: sourceApi) + } + + SourceSettingsMethodView(selectedSource: selectedSource) } }