Plugins are now a unified format for both sources and actions. Actions dictate what to do with a link and can now be added through a plugin JSON file. Backups have also been versioned to improve performance and add action support. Tags are used to give small amounts of information before a user installs a plugin. Signed-off-by: kingbri <bdashore3@proton.me>
203 lines
7.1 KiB
Swift
203 lines
7.1 KiB
Swift
//
|
|
// SourceSettingsView.swift
|
|
// Ferrite
|
|
//
|
|
// Created by Brian Dashore on 8/4/22.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct SourceSettingsView: View {
|
|
@Environment(\.presentationMode) var presentationMode
|
|
|
|
@EnvironmentObject var navModel: NavigationViewModel
|
|
|
|
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 listId = selectedSource.listId {
|
|
Text("List ID: \(listId)")
|
|
} else {
|
|
Text("No list ID 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)
|
|
.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()
|
|
}
|
|
})
|
|
.autocapitalization(.none)
|
|
.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()
|
|
}
|
|
})
|
|
.autocapitalization(.none)
|
|
.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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.backport.tint(.primary)
|
|
}
|
|
}
|