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:
kingbri 2022-08-05 22:31:15 -04:00
parent 1eb4bbb59a
commit ff23a854ef
11 changed files with 266 additions and 94 deletions

View file

@ -21,6 +21,9 @@
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; }; 0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; };
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.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 */; }; 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 */; }; 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 */; }; 0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */; };
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = "<group>"; };
@ -160,6 +166,17 @@
path = Models; path = Models;
sourceTree = "<group>"; 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 */ = { 0CA0545C288F7CB200850554 /* SettingsViews */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -217,6 +234,7 @@
0CA148EE2889061200DE2211 /* Views */ = { 0CA148EE2889061200DE2211 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0C794B65289DAC9F00DD1CC8 /* SourceViews */,
0CA148F02889062700DE2211 /* RepresentableViews */, 0CA148F02889062700DE2211 /* RepresentableViews */,
0CA148C0288903F000DE2211 /* CommonViews */, 0CA148C0288903F000DE2211 /* CommonViews */,
0CA0545C288F7CB200850554 /* SettingsViews */, 0CA0545C288F7CB200850554 /* SettingsViews */,
@ -229,7 +247,6 @@
0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */, 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */,
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */, 0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */, 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
0C733286289C4C820058D1FE /* SourceSettingsView.swift */,
0C32FB522890D19D002BD219 /* AboutView.swift */, 0C32FB522890D19D002BD219 /* AboutView.swift */,
); );
path = Views; path = Views;
@ -381,6 +398,7 @@
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */, 0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */,
0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */, 0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */,
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */, 0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */,
0C794B6B289DACF100DD1CC8 /* SourceCatalogView.swift in Sources */,
0CA148E9288903F000DE2211 /* MainView.swift in Sources */, 0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */, 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
0CF501F2289AE06A0099C785 /* SourceTracker+CoreDataClass.swift in Sources */, 0CF501F2289AE06A0099C785 /* SourceTracker+CoreDataClass.swift in Sources */,
@ -389,6 +407,7 @@
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */, 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
0CA148E1288903F000DE2211 /* Collection.swift in Sources */, 0CA148E1288903F000DE2211 /* Collection.swift in Sources */,
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */, 0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */,
0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */,
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */, 0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */,
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */, 0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */, 0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */,
@ -397,6 +416,7 @@
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */, 0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */,
0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */, 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */,
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */, 0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */,
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */,
0CA148E6288903F000DE2211 /* WebView.swift in Sources */, 0CA148E6288903F000DE2211 /* WebView.swift in Sources */,
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */, 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */,
0CA148E2288903F000DE2211 /* Data.swift in Sources */, 0CA148E2288903F000DE2211 /* Data.swift in Sources */,

View file

@ -23,4 +23,7 @@ class NavigationViewModel: ObservableObject {
// Used between SourceListView and SourceSettingsView // Used between SourceListView and SourceSettingsView
@Published var showSourceSettings: Bool = false @Published var showSourceSettings: Bool = false
@Published var selectedSource: Source? @Published var selectedSource: Source?
@Published var showSourceListEditor: Bool = false
@Published var selectedSourceList: SourceList?
} }

View file

@ -45,22 +45,24 @@ public class SourceManager: ObservableObject {
} }
} }
public func installSource(sourceJson: SourceJson) { public func installSource(sourceJson: SourceJson, doUpsert: Bool = false) {
let backgroundContext = PersistenceController.shared.backgroundContext 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() let existingSourceRequest = Source.fetchRequest()
existingSourceRequest.predicate = NSPredicate(format: "name == %@", sourceJson.name) existingSourceRequest.predicate = NSPredicate(format: "name == %@", sourceJson.name)
existingSourceRequest.fetchLimit = 1 existingSourceRequest.fetchLimit = 1
let existingSource = try? backgroundContext.fetch(existingSourceRequest).first if let existingSource = try? backgroundContext.fetch(existingSourceRequest).first {
if existingSource != nil { if doUpsert {
PersistenceController.shared.delete(existingSource, context: backgroundContext)
} else {
Task { @MainActor in Task { @MainActor in
toastModel?.toastDescription = "Could not install source with name \(sourceJson.name) because it is already installed." toastModel?.toastDescription = "Could not install source with name \(sourceJson.name) because it is already installed."
} }
return return
} }
}
let newSource = Source(context: backgroundContext) let newSource = Source(context: backgroundContext)
newSource.id = UUID() newSource.id = UUID()
@ -218,7 +220,7 @@ public class SourceManager: ObservableObject {
} }
@MainActor @MainActor
public func addSourceList(sourceUrl: String) async -> Bool { public func addSourceList(sourceUrl: String, existingSourceList: SourceList?) async -> Bool {
let backgroundContext = PersistenceController.shared.backgroundContext let backgroundContext = PersistenceController.shared.backgroundContext
if sourceUrl.isEmpty || URL(string: sourceUrl) == nil { if sourceUrl.isEmpty || URL(string: sourceUrl) == nil {
@ -232,13 +234,21 @@ public class SourceManager: ObservableObject {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!)) let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!))
let rawResponse = try JSONDecoder().decode(SourceListJson.self, from: data) let rawResponse = try JSONDecoder().decode(SourceListJson.self, from: data)
if let existingSourceList = existingSourceList {
existingSourceList.urlString = sourceUrl
existingSourceList.name = rawResponse.name
existingSourceList.author = rawResponse.author
} else {
let sourceListRequest = SourceList.fetchRequest() let sourceListRequest = SourceList.fetchRequest()
sourceListRequest.predicate = NSPredicate(format: "urlString == %@ OR author == %@", sourceUrl, rawResponse.author) 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 sourceListRequest.fetchLimit = 1
if (try? backgroundContext.fetch(sourceListRequest).first) != nil { if (try? backgroundContext.fetch(sourceListRequest).first) != nil {
urlErrorAlertText = "A source with the same URL or author exists. Please remove it and try again." urlErrorAlertText = "An existing source with this information was found. Please try editing the source list instead."
showUrlErrorAlert.toggle() showUrlErrorAlert.toggle()
return false return false
} }
@ -247,6 +257,7 @@ public class SourceManager: ObservableObject {
newSourceUrl.urlString = sourceUrl newSourceUrl.urlString = sourceUrl
newSourceUrl.name = rawResponse.name newSourceUrl.name = rawResponse.name
newSourceUrl.author = rawResponse.author newSourceUrl.author = rawResponse.author
}
try backgroundContext.save() try backgroundContext.save()

View file

@ -1,5 +1,5 @@
// //
// ErrorViewModel.swift // ToastViewModel.swift
// Ferrite // Ferrite
// //
// Created by Brian Dashore on 7/19/22. // Created by Brian Dashore on 7/19/22.

View file

@ -1,5 +1,5 @@
// //
// SettingsSourceUrlView.swift // SettingsSourceListView.swift
// Ferrite // Ferrite
// //
// Created by Brian Dashore on 7/25/22. // Created by Brian Dashore on 7/25/22.
@ -10,27 +10,43 @@ import SwiftUI
struct SettingsSourceListView: View { struct SettingsSourceListView: View {
let backgroundContext = PersistenceController.shared.backgroundContext let backgroundContext = PersistenceController.shared.backgroundContext
@EnvironmentObject var navModel: NavigationViewModel
@FetchRequest( @FetchRequest(
entity: SourceList.entity(), entity: SourceList.entity(),
sortDescriptors: [] sortDescriptors: []
) var sourceLists: FetchedResults<SourceList> ) var sourceLists: FetchedResults<SourceList>
@State private var presentSourceSheet = false @State private var presentSourceSheet = false
@State private var selectedSourceList: SourceList?
var body: some View { var body: some View {
List { List {
ForEach(sourceLists, id: \.self) { sourceList in ForEach(sourceLists, id: \.self) { sourceList in
VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) {
Text(sourceList.name) Text(sourceList.name)
Text(sourceList.author)
.foregroundColor(.gray)
Text("ID: \(sourceList.id)") Text("ID: \(sourceList.id)")
.font(.caption) .font(.caption)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
.contextMenu {
Button {
navModel.selectedSourceList = sourceList
presentSourceSheet.toggle()
} label: {
Text("Edit")
Image(systemName: "pencil")
} }
.onDelete { offsets in
for index in offsets { Button {
if let sourceUrl = sourceLists[safe: index] { PersistenceController.shared.delete(sourceList, context: backgroundContext)
PersistenceController.shared.delete(sourceUrl, context: backgroundContext) } label: {
Text("Remove")
Image(systemName: "trash")
} }
} }
} }

View file

@ -10,6 +10,7 @@ import SwiftUI
struct SourceListEditorView: View { struct SourceListEditorView: View {
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@EnvironmentObject var navModel: NavigationViewModel
@EnvironmentObject var sourceManager: SourceManager @EnvironmentObject var sourceManager: SourceManager
let backgroundContext = PersistenceController.shared.backgroundContext let backgroundContext = PersistenceController.shared.backgroundContext
@ -26,6 +27,9 @@ struct SourceListEditorView: View {
.autocapitalization(.none) .autocapitalization(.none)
} }
} }
.onAppear {
sourceUrl = navModel.selectedSourceList?.urlString ?? ""
}
.alert(isPresented: $sourceManager.showUrlErrorAlert) { .alert(isPresented: $sourceManager.showUrlErrorAlert) {
Alert( Alert(
title: Text("Error"), title: Text("Error"),
@ -45,13 +49,19 @@ struct SourceListEditorView: View {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") { Button("Save") {
Task { Task {
if await sourceManager.addSourceList(sourceUrl: sourceUrl) { if await sourceManager.addSourceList(
sourceUrl: sourceUrl,
existingSourceList: navModel.selectedSourceList
) {
dismiss() dismiss()
} }
} }
} }
} }
} }
.onDisappear {
navModel.selectedSourceList = nil
}
} }
} }
} }

View 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")
}
}
}
}

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

View file

@ -22,21 +22,24 @@ struct SourceSettingsView: View {
Text(selectedSource.name) Text(selectedSource.name)
Text("v\(selectedSource.version)") Text("v\(selectedSource.version)")
.foregroundColor(.secondary)
} }
Text("by \(selectedSource.author)") Text("by \(selectedSource.author)")
.foregroundColor(.secondary) .foregroundColor(.secondary)
Group {
Text("ID: \(selectedSource.id)")
if let listId = selectedSource.listId { if let listId = selectedSource.listId {
Text("List ID: \(listId)") Text("List ID: \(listId)")
.font(.caption)
.foregroundColor(.secondary)
} else { } else {
Text("No list ID found. This source should be removed.") Text("No list ID found. This source should be removed.")
.font(.caption)
.foregroundColor(.secondary)
} }
} }
.foregroundColor(.secondary)
.font(.caption)
}
} }
SourceSettingsMethodView(selectedSource: selectedSource) SourceSettingsMethodView(selectedSource: selectedSource)

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

View file

@ -18,48 +18,39 @@ struct SourcesView: View {
sortDescriptors: [] sortDescriptors: []
) var sources: FetchedResults<Source> ) 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 { var body: some View {
NavView { NavView {
List { List {
if !updatedSources.isEmpty {
Section("Updates") {
ForEach(updatedSources, id: \.self) { source in
SourceUpdateButtonView(updatedSource: source)
}
}
}
if !sources.isEmpty { if !sources.isEmpty {
Section("Installed") { Section("Installed") {
ForEach(sources, id: \.self) { source in ForEach(sources, id: \.self) { source in
Toggle(isOn: Binding<Bool>( InstalledSourceView(installedSource: source)
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")
}
}
} }
.sheet(isPresented: $navModel.showSourceSettings) { .sheet(isPresented: $navModel.showSourceSettings) {
SourceSettingsView() SourceSettingsView()
@ -67,30 +58,25 @@ struct SourcesView: View {
} }
} }
if sourceManager.availableSources.contains(where: { avail in if sourceManager.availableSources.contains(where: { availableSource in
!sources.contains(where: { avail.name == $0.name }) !sources.contains(
where: {
availableSource.name == $0.name &&
availableSource.listId == $0.listId &&
availableSource.author == $0.author
}
)
}) { }) {
Section("Catalog") { Section("Catalog") {
ForEach(sourceManager.availableSources, id: \.self) { availableSource in ForEach(sourceManager.availableSources, id: \.self) { availableSource in
if !sources.contains(where: { availableSource.name == $0.name }) { if !sources.contains(
HStack { where: {
VStack(alignment: .leading, spacing: 5) { availableSource.name == $0.name &&
HStack { availableSource.listId == $0.listId &&
Text(availableSource.name) availableSource.author == $0.author
Text("v\(availableSource.version)")
.foregroundColor(.secondary)
}
Text("by \(availableSource.author ?? "Unknown")")
.foregroundColor(.secondary)
}
Spacer()
Button("Install") {
sourceManager.installSource(sourceJson: availableSource)
}
} }
) {
SourceCatalogButtonView(availableSource: availableSource)
} }
} }
} }