Plugins: Fix animation and appearance

Switching to a list that changes state on updates caused sheets
to break when animating. Place the list in a container ZStack
that doesn't break sheet presentation.

Also modernize the plugin installation buttons and make the catalog
buttons include the plugin list name which should help prevent
duplication.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-03-23 15:29:32 -04:00
parent ff13884b2b
commit 87d94e4c35
9 changed files with 90 additions and 58 deletions

View file

@ -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) {

View file

@ -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]?
}

View file

@ -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]
}

View file

@ -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 {

View file

@ -24,7 +24,7 @@ struct InstalledPluginButtonView<P: Plugin>: 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<P: Plugin>: View {
Text("by \(installedPlugin.author)")
.foregroundColor(.secondary)
.lineLimit(1)
}
if let tags = installedPlugin.getTags(), !tags.isEmpty {

View file

@ -11,20 +11,26 @@ struct PluginCatalogButtonView<PJ: PluginJson>: 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<PJ: PluginJson>: 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)
}
}

View file

@ -12,6 +12,11 @@ struct PluginAggregateView<P: Plugin, PJ: PluginJson>: View {
let backgroundContext = PersistenceController.shared.backgroundContext
@FetchRequest(
entity: PluginList.entity(),
sortDescriptors: []
) var pluginLists: FetchedResults<PluginList>
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true
@Binding var searchText: String
@ -23,64 +28,65 @@ struct PluginAggregateView<P: Plugin, PJ: PluginJson>: View {
@State private var sourcePredicate: NSPredicate?
var body: some View {
DynamicFetchRequest(predicate: sourcePredicate) { (installedPlugins: FetchedResults<P>) 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<P>) 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())
}
}
}

View file

@ -65,6 +65,7 @@ struct DefaultActionPickerView: View {
}
}
.foregroundColor(.secondary)
.lineLimit(1)
}
Spacer()

View file

@ -36,6 +36,7 @@ struct SettingsPluginListView: View {
.font(.caption)
}
.foregroundColor(.secondary)
.lineLimit(1)
}
.padding(.vertical, 2)
.contextMenu {