mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-01-11 20:10:27 +00:00
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:
parent
ff13884b2b
commit
87d94e4c35
9 changed files with 90 additions and 58 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]?
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ struct DefaultActionPickerView: View {
|
|||
}
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ struct SettingsPluginListView: View {
|
|||
.font(.caption)
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.contextMenu {
|
||||
|
|
|
|||
Loading…
Reference in a new issue