diff --git a/Ferrite/Models/ActionModels.swift b/Ferrite/Models/ActionModels.swift index 86b8582..8911d87 100644 --- a/Ferrite/Models/ActionModels.swift +++ b/Ferrite/Models/ActionModels.swift @@ -15,6 +15,7 @@ public struct ActionJson: Codable, Hashable, PluginJson { let deeplink: [DeeplinkActionJson]? public let author: String? public let listId: UUID? + public let listName: String? public let tags: [PluginTagJson]? public init(name: String, @@ -24,6 +25,7 @@ public struct ActionJson: Codable, Hashable, PluginJson { deeplink: [DeeplinkActionJson]?, author: String?, listId: UUID?, + listName: String?, tags: [PluginTagJson]?) { self.name = name @@ -33,6 +35,7 @@ public struct ActionJson: Codable, Hashable, PluginJson { self.deeplink = deeplink self.author = author self.listId = listId + self.listName = listName self.tags = tags } @@ -43,7 +46,8 @@ public struct ActionJson: Codable, Hashable, PluginJson { minVersion = try container.decodeIfPresent(String.self, forKey: .minVersion) requires = try container.decode([ActionRequirement].self, forKey: .requires) author = try container.decodeIfPresent(String.self, forKey: .author) - listId = try container.decodeIfPresent(UUID.self, forKey: .listId) + listId = nil + listName = nil tags = try container.decodeIfPresent([PluginTagJson].self, forKey: .tags) if let deeplinkString = try? container.decode(String.self, forKey: .deeplink) { diff --git a/Ferrite/Models/SourceModels.swift b/Ferrite/Models/SourceModels.swift index 2adf110..1f2b278 100644 --- a/Ferrite/Models/SourceModels.swift +++ b/Ferrite/Models/SourceModels.swift @@ -26,6 +26,7 @@ public struct SourceJson: Codable, Hashable, Sendable, PluginJson { let htmlParser: SourceHtmlParserJson? public let author: String? public let listId: UUID? + public let listName: String? public let tags: [PluginTagJson]? } diff --git a/Ferrite/Protocols/Plugin.swift b/Ferrite/Protocols/Plugin.swift index 08e70be..1e2faa4 100644 --- a/Ferrite/Protocols/Plugin.swift +++ b/Ferrite/Protocols/Plugin.swift @@ -30,6 +30,7 @@ public protocol PluginJson: Hashable { var version: Int16 { get } var author: String? { get } var listId: UUID? { get } + var listName: String? { get } var tags: [PluginTagJson]? { get } func getTags() -> [PluginTagJson] } diff --git a/Ferrite/ViewModels/PluginManager.swift b/Ferrite/ViewModels/PluginManager.swift index 269a65d..39d7c2e 100644 --- a/Ferrite/ViewModels/PluginManager.swift +++ b/Ferrite/ViewModels/PluginManager.swift @@ -122,6 +122,7 @@ public class PluginManager: ObservableObject { htmlParser: inputJson.htmlParser, author: pluginList.author, listId: pluginList.id, + listName: pluginList.name, tags: inputJson.tags ) } else { @@ -145,6 +146,7 @@ public class PluginManager: ObservableObject { deeplink: filteredDeeplinks, author: pluginList.author, listId: pluginList.id, + listName: pluginList.name, tags: inputJson.tags ) } else { diff --git a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift index b99061d..d35f6ab 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift @@ -24,7 +24,7 @@ struct InstalledPluginButtonView: View { )) { VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 5) { - HStack { + HStack(spacing: 5) { Text(installedPlugin.name) Text("v\(installedPlugin.version)") .foregroundColor(.secondary) @@ -32,6 +32,7 @@ struct InstalledPluginButtonView: View { Text("by \(installedPlugin.author)") .foregroundColor(.secondary) + .lineLimit(1) } if let tags = installedPlugin.getTags(), !tags.isEmpty { diff --git a/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift b/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift index a2c525b..85a55c8 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift @@ -11,20 +11,26 @@ struct PluginCatalogButtonView: View { @EnvironmentObject var pluginManager: PluginManager let availablePlugin: PJ - let doUpsert: Bool + let needsUpdate: Bool var body: some View { HStack { VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 5) { - HStack { + HStack(spacing: 5) { Text(availablePlugin.name) Text("v\(availablePlugin.version)") .foregroundColor(.secondary) } - Text("by \(availablePlugin.author ?? "No author")") - .foregroundColor(.secondary) + Group { + Text("by \(availablePlugin.author ?? "No author")") + + Text(availablePlugin.listName.map { "from \($0)" } ?? "an unknown list") + .font(.caption) + } + .foregroundColor(.secondary) + .lineLimit(1) } if let tags = availablePlugin.getTags(), !tags.isEmpty { @@ -34,18 +40,27 @@ struct PluginCatalogButtonView: View { Spacer() - Button("Install") { + Button(needsUpdate ? "UPDATE" : "INSTALL") { Task { if let availableSource = availablePlugin as? SourceJson { - await pluginManager.installSource(sourceJson: availableSource, doUpsert: doUpsert) + await pluginManager.installSource(sourceJson: availableSource, doUpsert: needsUpdate) } else if let availableAction = availablePlugin as? ActionJson { - await pluginManager.installAction(actionJson: availableAction, doUpsert: doUpsert) + await pluginManager.installAction(actionJson: availableAction, doUpsert: needsUpdate) } else { return } } } + .font( + .footnote + .weight(.bold) + ) + .padding(.horizontal, 7) + .padding(.vertical, 5) + .background(.tertiarySystemBackground) + .clipShape(RoundedRectangle(cornerRadius: 10)) } + .buttonStyle(.borderless) .padding(.vertical, 2) } } diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift index 76293f7..22ee4a5 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift @@ -12,6 +12,11 @@ struct PluginAggregateView: View { let backgroundContext = PersistenceController.shared.backgroundContext + @FetchRequest( + entity: PluginList.entity(), + sortDescriptors: [] + ) var pluginLists: FetchedResults + @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true @Binding var searchText: String @@ -23,64 +28,65 @@ struct PluginAggregateView: View { @State private var sourcePredicate: NSPredicate? var body: some View { - DynamicFetchRequest(predicate: sourcePredicate) { (installedPlugins: FetchedResults

) in - List { - if - let filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins( - forType: PJ.self, - installedPlugins: installedPlugins, - searchText: searchText - ), - !filteredUpdatedPlugins.isEmpty - { - Section(header: InlineHeader("Updates")) { - ForEach(filteredUpdatedPlugins, id: \.self) { (updatedPlugin: PJ) in - PluginCatalogButtonView(availablePlugin: updatedPlugin, doUpsert: true) + ZStack { + DynamicFetchRequest(predicate: sourcePredicate) { (installedPlugins: FetchedResults

) in + List { + if + let filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins( + forType: PJ.self, + installedPlugins: installedPlugins, + searchText: searchText + ), + !filteredUpdatedPlugins.isEmpty + { + Section(header: InlineHeader("Updates")) { + ForEach(filteredUpdatedPlugins, id: \.self) { (updatedPlugin: PJ) in + PluginCatalogButtonView(availablePlugin: updatedPlugin, needsUpdate: true) + } + } + } + + if !installedPlugins.isEmpty { + Section(header: InlineHeader("Installed")) { + ForEach(installedPlugins, id: \.self) { installedPlugin in + InstalledPluginButtonView(installedPlugin: installedPlugin) + } } } - } - if !installedPlugins.isEmpty { - Section(header: InlineHeader("Installed")) { - ForEach(installedPlugins, id: \.self) { source in - InstalledPluginButtonView(installedPlugin: source) + if + let filteredAvailablePlugins = pluginManager.fetchFilteredPlugins( + forType: PJ.self, + installedPlugins: installedPlugins, + searchText: searchText + ), + !filteredAvailablePlugins.isEmpty + { + Section(header: InlineHeader("Catalog")) { + ForEach(filteredAvailablePlugins, id: \.self) { availablePlugin in + PluginCatalogButtonView(availablePlugin: availablePlugin, needsUpdate: false) + } } } } - - if - let filteredAvailablePlugins = pluginManager.fetchFilteredPlugins( - forType: PJ.self, - installedPlugins: installedPlugins, - searchText: searchText - ), - !filteredAvailablePlugins.isEmpty - { - Section(header: InlineHeader("Catalog")) { - ForEach(filteredAvailablePlugins, id: \.self) { availablePlugin in - PluginCatalogButtonView(availablePlugin: availablePlugin, doUpsert: false) - } - } + .inlinedList(inset: 0) + .listStyle(.insetGrouped) + .backport.onAppear { + pluginsEmpty = installedPlugins.isEmpty + } + .onChange(of: searchText) { _ in + sourcePredicate = searchText.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", searchText) + } + .onChange(of: installedPlugins.count) { newCount in + pluginsEmpty = newCount == 0 } } - .inlinedList(inset: 0) - .listStyle(.insetGrouped) - .sheet(isPresented: $navModel.showSourceSettings) { - if String(describing: P.self) == "Source" { - SourceSettingsView() - .environmentObject(navModel) - } + } + .sheet(isPresented: $navModel.showSourceSettings) { + if String(describing: P.self) == "Source" { + SourceSettingsView() + .environmentObject(navModel) } - .backport.onAppear { - pluginsEmpty = installedPlugins.isEmpty - } - .onChange(of: searchText) { _ in - sourcePredicate = searchText.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", searchText) - } - .onChange(of: installedPlugins.count) { newCount in - pluginsEmpty = newCount == 0 - } - .id(UUID()) } } } diff --git a/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift b/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift index 71a7f17..b5fbe44 100644 --- a/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift +++ b/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift @@ -65,6 +65,7 @@ struct DefaultActionPickerView: View { } } .foregroundColor(.secondary) + .lineLimit(1) } Spacer() diff --git a/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift b/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift index 82da472..779036d 100644 --- a/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift +++ b/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift @@ -36,6 +36,7 @@ struct SettingsPluginListView: View { .font(.caption) } .foregroundColor(.secondary) + .lineLimit(1) } .padding(.vertical, 2) .contextMenu {