Sources: Add source updating and source list edits
Sources can now be updated based on the repo ID. To preserve repo IDs across single URL links, the source lists can be edited and the ID is transferred over. Signed-off-by: kingbri <bdashore3@gmail.com>
This commit is contained in:
parent
1eb4bbb59a
commit
ff23a854ef
11 changed files with 266 additions and 94 deletions
|
|
@ -21,6 +21,9 @@
|
|||
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; };
|
||||
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */; };
|
||||
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */; };
|
||||
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C794B66289DACB600DD1CC8 /* SourceUpdateButtonView.swift */; };
|
||||
0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C794B68289DACC800DD1CC8 /* InstalledSourceView.swift */; };
|
||||
0C794B6B289DACF100DD1CC8 /* SourceCatalogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C794B6A289DACF100DD1CC8 /* SourceCatalogView.swift */; };
|
||||
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */; };
|
||||
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */; };
|
||||
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */; };
|
||||
|
|
@ -74,6 +77,9 @@
|
|||
0C733286289C4C820058D1FE /* SourceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsView.swift; sourceTree = "<group>"; };
|
||||
0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C794B66289DACB600DD1CC8 /* SourceUpdateButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceUpdateButtonView.swift; sourceTree = "<group>"; };
|
||||
0C794B68289DACC800DD1CC8 /* InstalledSourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledSourceView.swift; sourceTree = "<group>"; };
|
||||
0C794B6A289DACF100DD1CC8 /* SourceCatalogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceCatalogView.swift; sourceTree = "<group>"; };
|
||||
0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -160,6 +166,17 @@
|
|||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0C794B65289DAC9F00DD1CC8 /* SourceViews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C733286289C4C820058D1FE /* SourceSettingsView.swift */,
|
||||
0C794B66289DACB600DD1CC8 /* SourceUpdateButtonView.swift */,
|
||||
0C794B68289DACC800DD1CC8 /* InstalledSourceView.swift */,
|
||||
0C794B6A289DACF100DD1CC8 /* SourceCatalogView.swift */,
|
||||
);
|
||||
path = SourceViews;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0CA0545C288F7CB200850554 /* SettingsViews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -217,6 +234,7 @@
|
|||
0CA148EE2889061200DE2211 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C794B65289DAC9F00DD1CC8 /* SourceViews */,
|
||||
0CA148F02889062700DE2211 /* RepresentableViews */,
|
||||
0CA148C0288903F000DE2211 /* CommonViews */,
|
||||
0CA0545C288F7CB200850554 /* SettingsViews */,
|
||||
|
|
@ -229,7 +247,6 @@
|
|||
0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */,
|
||||
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
|
||||
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
|
||||
0C733286289C4C820058D1FE /* SourceSettingsView.swift */,
|
||||
0C32FB522890D19D002BD219 /* AboutView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
|
|
@ -381,6 +398,7 @@
|
|||
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */,
|
||||
0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */,
|
||||
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */,
|
||||
0C794B6B289DACF100DD1CC8 /* SourceCatalogView.swift in Sources */,
|
||||
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
||||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
||||
0CF501F2289AE06A0099C785 /* SourceTracker+CoreDataClass.swift in Sources */,
|
||||
|
|
@ -389,6 +407,7 @@
|
|||
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
|
||||
0CA148E1288903F000DE2211 /* Collection.swift in Sources */,
|
||||
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */,
|
||||
0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */,
|
||||
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */,
|
||||
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
|
||||
0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */,
|
||||
|
|
@ -397,6 +416,7 @@
|
|||
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */,
|
||||
0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */,
|
||||
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */,
|
||||
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */,
|
||||
0CA148E6288903F000DE2211 /* WebView.swift in Sources */,
|
||||
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */,
|
||||
0CA148E2288903F000DE2211 /* Data.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -23,4 +23,7 @@ class NavigationViewModel: ObservableObject {
|
|||
// Used between SourceListView and SourceSettingsView
|
||||
@Published var showSourceSettings: Bool = false
|
||||
@Published var selectedSource: Source?
|
||||
|
||||
@Published var showSourceListEditor: Bool = false
|
||||
@Published var selectedSourceList: SourceList?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,21 +45,23 @@ public class SourceManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func installSource(sourceJson: SourceJson) {
|
||||
public func installSource(sourceJson: SourceJson, doUpsert: Bool = false) {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
// If a source exists, don't add the new one
|
||||
// If a source exists, don't add the new one unless upserting
|
||||
let existingSourceRequest = Source.fetchRequest()
|
||||
existingSourceRequest.predicate = NSPredicate(format: "name == %@", sourceJson.name)
|
||||
existingSourceRequest.fetchLimit = 1
|
||||
|
||||
let existingSource = try? backgroundContext.fetch(existingSourceRequest).first
|
||||
if existingSource != nil {
|
||||
Task { @MainActor in
|
||||
toastModel?.toastDescription = "Could not install source with name \(sourceJson.name) because it is already installed."
|
||||
if let existingSource = try? backgroundContext.fetch(existingSourceRequest).first {
|
||||
if doUpsert {
|
||||
PersistenceController.shared.delete(existingSource, context: backgroundContext)
|
||||
} else {
|
||||
Task { @MainActor in
|
||||
toastModel?.toastDescription = "Could not install source with name \(sourceJson.name) because it is already installed."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let newSource = Source(context: backgroundContext)
|
||||
|
|
@ -218,7 +220,7 @@ public class SourceManager: ObservableObject {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
public func addSourceList(sourceUrl: String) async -> Bool {
|
||||
public func addSourceList(sourceUrl: String, existingSourceList: SourceList?) async -> Bool {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
if sourceUrl.isEmpty || URL(string: sourceUrl) == nil {
|
||||
|
|
@ -232,22 +234,31 @@ public class SourceManager: ObservableObject {
|
|||
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!))
|
||||
let rawResponse = try JSONDecoder().decode(SourceListJson.self, from: data)
|
||||
|
||||
let sourceListRequest = SourceList.fetchRequest()
|
||||
sourceListRequest.predicate = NSPredicate(format: "urlString == %@ OR author == %@", sourceUrl, rawResponse.author)
|
||||
sourceListRequest.fetchLimit = 1
|
||||
if let existingSourceList = existingSourceList {
|
||||
existingSourceList.urlString = sourceUrl
|
||||
existingSourceList.name = rawResponse.name
|
||||
existingSourceList.author = rawResponse.author
|
||||
} else {
|
||||
let sourceListRequest = SourceList.fetchRequest()
|
||||
let urlPredicate = NSPredicate(format: "urlString == %@", sourceUrl)
|
||||
let infoPredicate = NSPredicate(format: "author == %@ AND name == %@", rawResponse.author, rawResponse.name)
|
||||
sourceListRequest.predicate = NSCompoundPredicate.init(type: .or, subpredicates: [urlPredicate, infoPredicate])
|
||||
sourceListRequest.fetchLimit = 1
|
||||
|
||||
if (try? backgroundContext.fetch(sourceListRequest).first) != nil {
|
||||
urlErrorAlertText = "A source with the same URL or author exists. Please remove it and try again."
|
||||
showUrlErrorAlert.toggle()
|
||||
return false
|
||||
if (try? backgroundContext.fetch(sourceListRequest).first) != nil {
|
||||
urlErrorAlertText = "An existing source with this information was found. Please try editing the source list instead."
|
||||
showUrlErrorAlert.toggle()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
let newSourceUrl = SourceList(context: backgroundContext)
|
||||
newSourceUrl.id = UUID()
|
||||
newSourceUrl.urlString = sourceUrl
|
||||
newSourceUrl.name = rawResponse.name
|
||||
newSourceUrl.author = rawResponse.author
|
||||
}
|
||||
|
||||
let newSourceUrl = SourceList(context: backgroundContext)
|
||||
newSourceUrl.id = UUID()
|
||||
newSourceUrl.urlString = sourceUrl
|
||||
newSourceUrl.name = rawResponse.name
|
||||
newSourceUrl.author = rawResponse.author
|
||||
|
||||
try backgroundContext.save()
|
||||
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// ErrorViewModel.swift
|
||||
// ToastViewModel.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 7/19/22.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// SettingsSourceUrlView.swift
|
||||
// SettingsSourceListView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 7/25/22.
|
||||
|
|
@ -10,27 +10,43 @@ import SwiftUI
|
|||
struct SettingsSourceListView: View {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
|
||||
@FetchRequest(
|
||||
entity: SourceList.entity(),
|
||||
sortDescriptors: []
|
||||
) var sourceLists: FetchedResults<SourceList>
|
||||
|
||||
@State private var presentSourceSheet = false
|
||||
@State private var selectedSourceList: SourceList?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(sourceLists, id: \.self) { sourceList in
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(sourceList.name)
|
||||
|
||||
Text(sourceList.author)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Text("ID: \(sourceList.id)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.onDelete { offsets in
|
||||
for index in offsets {
|
||||
if let sourceUrl = sourceLists[safe: index] {
|
||||
PersistenceController.shared.delete(sourceUrl, context: backgroundContext)
|
||||
.contextMenu {
|
||||
Button {
|
||||
navModel.selectedSourceList = sourceList
|
||||
presentSourceSheet.toggle()
|
||||
} label: {
|
||||
Text("Edit")
|
||||
Image(systemName: "pencil")
|
||||
}
|
||||
|
||||
Button {
|
||||
PersistenceController.shared.delete(sourceList, context: backgroundContext)
|
||||
} label: {
|
||||
Text("Remove")
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||
struct SourceListEditorView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
@EnvironmentObject var sourceManager: SourceManager
|
||||
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
|
@ -26,6 +27,9 @@ struct SourceListEditorView: View {
|
|||
.autocapitalization(.none)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
sourceUrl = navModel.selectedSourceList?.urlString ?? ""
|
||||
}
|
||||
.alert(isPresented: $sourceManager.showUrlErrorAlert) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
|
|
@ -45,13 +49,19 @@ struct SourceListEditorView: View {
|
|||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Save") {
|
||||
Task {
|
||||
if await sourceManager.addSourceList(sourceUrl: sourceUrl) {
|
||||
if await sourceManager.addSourceList(
|
||||
sourceUrl: sourceUrl,
|
||||
existingSourceList: navModel.selectedSourceList
|
||||
) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
navModel.selectedSourceList = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
Ferrite/Views/SourceViews/InstalledSourceView.swift
Normal file
53
Ferrite/Views/SourceViews/InstalledSourceView.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// InstalledSourceView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 8/5/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InstalledSourceView: View {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
|
||||
@ObservedObject var installedSource: Source
|
||||
|
||||
var body: some View {
|
||||
Toggle(isOn: Binding<Bool>(
|
||||
get: { installedSource.enabled },
|
||||
set: {
|
||||
installedSource.enabled = $0
|
||||
PersistenceController.shared.save()
|
||||
}
|
||||
)) {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack {
|
||||
Text(installedSource.name)
|
||||
Text("v\(installedSource.version)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("by \(installedSource.author)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
navModel.selectedSource = installedSource
|
||||
navModel.showSourceSettings.toggle()
|
||||
} label: {
|
||||
Text("Settings")
|
||||
Image(systemName: "gear")
|
||||
}
|
||||
|
||||
Button {
|
||||
PersistenceController.shared.delete(installedSource, context: backgroundContext)
|
||||
} label: {
|
||||
Text("Remove")
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Ferrite/Views/SourceViews/SourceCatalogView.swift
Normal file
35
Ferrite/Views/SourceViews/SourceCatalogView.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// SourceCatalogButtonView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 8/5/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SourceCatalogButtonView: View {
|
||||
@EnvironmentObject var sourceManager: SourceManager
|
||||
|
||||
let availableSource: SourceJson
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack {
|
||||
Text(availableSource.name)
|
||||
Text("v\(availableSource.version)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("by \(availableSource.author ?? "Unknown")")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Install") {
|
||||
sourceManager.installSource(sourceJson: availableSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,20 +22,23 @@ struct SourceSettingsView: View {
|
|||
Text(selectedSource.name)
|
||||
|
||||
Text("v\(selectedSource.version)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("by \(selectedSource.author)")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
if let listId = selectedSource.listId {
|
||||
Text("List ID: \(listId)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Text("No list ID found. This source should be removed.")
|
||||
.font(.caption)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
|
||||
35
Ferrite/Views/SourceViews/SourceUpdateButtonView.swift
Normal file
35
Ferrite/Views/SourceViews/SourceUpdateButtonView.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// SourceUpdateButtonView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 8/5/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SourceUpdateButtonView: View {
|
||||
@EnvironmentObject var sourceManager: SourceManager
|
||||
|
||||
let updatedSource: SourceJson
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack {
|
||||
Text(updatedSource.name)
|
||||
Text("v\(updatedSource.version)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("by \(updatedSource.author ?? "Unknown")")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Update") {
|
||||
sourceManager.installSource(sourceJson: updatedSource, doUpsert: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,48 +18,39 @@ struct SourcesView: View {
|
|||
sortDescriptors: []
|
||||
) var sources: FetchedResults<Source>
|
||||
|
||||
@State private var availableSourceLength = 0
|
||||
private var updatedSources: [SourceJson] {
|
||||
var tempSources: [SourceJson] = []
|
||||
|
||||
for source in sources {
|
||||
guard let availableSource = sourceManager.availableSources.first(where: {
|
||||
source.listId == $0.listId && source.name == $0.name && source.author == $0.author
|
||||
}) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if availableSource.version > source.version {
|
||||
tempSources.append(availableSource)
|
||||
}
|
||||
}
|
||||
|
||||
return tempSources
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavView {
|
||||
List {
|
||||
if !updatedSources.isEmpty {
|
||||
Section("Updates") {
|
||||
ForEach(updatedSources, id: \.self) { source in
|
||||
SourceUpdateButtonView(updatedSource: source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !sources.isEmpty {
|
||||
Section("Installed") {
|
||||
ForEach(sources, id: \.self) { source in
|
||||
Toggle(isOn: Binding<Bool>(
|
||||
get: { source.enabled },
|
||||
set: {
|
||||
source.enabled = $0
|
||||
PersistenceController.shared.save()
|
||||
}
|
||||
)) {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack {
|
||||
Text(source.name)
|
||||
Text("v\(source.version)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("by \(source.author)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
navModel.selectedSource = source
|
||||
navModel.showSourceSettings.toggle()
|
||||
} label: {
|
||||
Text("Settings")
|
||||
Image(systemName: "gear")
|
||||
}
|
||||
|
||||
Button {
|
||||
PersistenceController.shared.delete(source, context: backgroundContext)
|
||||
} label: {
|
||||
Text("Remove")
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
}
|
||||
InstalledSourceView(installedSource: source)
|
||||
}
|
||||
.sheet(isPresented: $navModel.showSourceSettings) {
|
||||
SourceSettingsView()
|
||||
|
|
@ -67,30 +58,25 @@ struct SourcesView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if sourceManager.availableSources.contains(where: { avail in
|
||||
!sources.contains(where: { avail.name == $0.name })
|
||||
if sourceManager.availableSources.contains(where: { availableSource in
|
||||
!sources.contains(
|
||||
where: {
|
||||
availableSource.name == $0.name &&
|
||||
availableSource.listId == $0.listId &&
|
||||
availableSource.author == $0.author
|
||||
}
|
||||
)
|
||||
}) {
|
||||
Section("Catalog") {
|
||||
ForEach(sourceManager.availableSources, id: \.self) { availableSource in
|
||||
if !sources.contains(where: { availableSource.name == $0.name }) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack {
|
||||
Text(availableSource.name)
|
||||
Text("v\(availableSource.version)")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("by \(availableSource.author ?? "Unknown")")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Install") {
|
||||
sourceManager.installSource(sourceJson: availableSource)
|
||||
}
|
||||
if !sources.contains(
|
||||
where: {
|
||||
availableSource.name == $0.name &&
|
||||
availableSource.listId == $0.listId &&
|
||||
availableSource.author == $0.author
|
||||
}
|
||||
) {
|
||||
SourceCatalogButtonView(availableSource: availableSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue