Skip to content

Commit ad30f1e

Browse files
authored
Implement plugin actions in the details screen (#24074)
1 parent fcb4a13 commit ad30f1e

File tree

6 files changed

+120
-104
lines changed

6 files changed

+120
-104
lines changed

Modules/Sources/WordPressCore/Plugins/InstalledPlugin.swift

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,42 @@
11
import Foundation
22
import WordPressAPI
3+
import WordPressAPIInternal
34

45
public struct InstalledPlugin: Equatable, Hashable, Identifiable, Sendable {
56
public var slug: PluginSlug
6-
public var iconURL: URL?
77
public var name: String
88
public var version: String
99
public var author: String
1010
public var shortDescription: String
11-
public var isActive: Bool
11+
public var status: PluginStatus
12+
public var networkOnly: Bool
1213

13-
public init(slug: PluginSlug, iconURL: URL?, name: String, version: String, author: String, shortDescription: String, isActive: Bool) {
14-
self.slug = slug
15-
self.iconURL = iconURL
16-
self.name = name
17-
self.version = version
18-
self.author = author
19-
self.shortDescription = shortDescription
20-
self.isActive = isActive
14+
public var isActive: Bool {
15+
status == .active || status == .networkActive
2116
}
2217

23-
public init(plugin: PluginWithViewContext) {
24-
self.slug = plugin.plugin
25-
iconURL = nil
26-
name = plugin.name
27-
version = plugin.version
28-
author = plugin.author
29-
shortDescription = plugin.description.raw
30-
isActive = plugin.status == .active || plugin.status == .networkActive
18+
public var id: String {
19+
slug.slug
3120
}
3221

33-
public init(plugin: PluginWithEditContext) {
22+
init(plugin: PluginWithEditContext) {
3423
self.slug = plugin.plugin
35-
iconURL = nil
36-
name = plugin.name
37-
version = plugin.version
38-
author = plugin.author
39-
shortDescription = plugin.description.raw
40-
isActive = plugin.status == .active || plugin.status == .networkActive
24+
self.name = plugin.name
25+
self.version = plugin.version
26+
self.author = plugin.author
27+
self.shortDescription = plugin.description.raw
28+
self.status = plugin.status
29+
self.networkOnly = plugin.networkOnly
4130
}
4231

43-
public var id: String {
44-
slug.slug
32+
init(plugin: PluginWithViewContext) {
33+
self.slug = plugin.plugin
34+
self.name = plugin.name
35+
self.version = plugin.version
36+
self.author = plugin.author
37+
self.shortDescription = plugin.description.raw
38+
self.status = plugin.status
39+
self.networkOnly = plugin.networkOnly
4540
}
4641

4742
public var possibleWpOrgDirectorySlug: PluginWpOrgDirectorySlug? {

Modules/Sources/WordPressCore/Plugins/PluginService.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,26 @@ public actor PluginService: PluginServiceProtocol {
6464
return nil
6565
}
6666

67-
public func togglePluginActivation(slug: PluginSlug) async throws {
68-
let plugin = try await client.api.plugins.retrieveWithViewContext(pluginSlug: slug)
69-
let newStatus: PluginStatus = plugin.data.status == .inactive ? (plugin.data.networkOnly ? .networkActive : .active) : .inactive
70-
let newPlugin = try await client.api.plugins.update(pluginSlug: slug, params: .init(status: newStatus))
71-
try await installedPluginDataStore.store([.init(plugin: newPlugin.data)])
67+
public func updatePluginStatus(plugin: InstalledPlugin, activated: Bool) async throws -> InstalledPlugin {
68+
let newStatus: PluginStatus = plugin.status == .inactive ? (plugin.networkOnly ? .networkActive : .active) : .inactive
69+
let newPlugin = try await client.api.plugins.update(pluginSlug: plugin.slug, params: .init(status: newStatus))
70+
let plugin = InstalledPlugin(plugin: newPlugin.data)
71+
try await installedPluginDataStore.store([plugin])
72+
return plugin
7273
}
7374

7475
public func uninstalledPlugin(slug: PluginSlug) async throws {
7576
let _ = try await client.api.plugins.delete(pluginSlug: slug)
7677
try await installedPluginDataStore.delete(query: .slug(slug))
7778
}
7879

80+
public func installPlugin(slug: PluginWpOrgDirectorySlug) async throws -> InstalledPlugin {
81+
let plugin = try await client.api.plugins.create(params: .init(slug: slug, status: .inactive)).data
82+
let installed = InstalledPlugin(plugin: plugin)
83+
try await installedPluginDataStore.store([installed])
84+
return installed
85+
}
86+
7987
public func fetchPluginsDirectory(category: WordPressOrgApiPluginDirectoryCategory) async throws {
8088
// Hard-code the pagination parameters for now. We can suface these parameters when the app needs pagination.
8189
let plugins = try await wpOrgClient.browsePlugins(category: category, page: 1, pageSize: 10).plugins

Modules/Sources/WordPressCore/Plugins/PluginServiceProtocol.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ public protocol PluginServiceProtocol: Actor {
1414

1515
func resolveIconURL(of slug: PluginWpOrgDirectorySlug, plugin: PluginInformation?) async -> URL?
1616

17-
func togglePluginActivation(slug: PluginSlug) async throws
17+
func updatePluginStatus(plugin: InstalledPlugin, activated: Bool) async throws -> InstalledPlugin
1818

1919
func uninstalledPlugin(slug: PluginSlug) async throws
20+
func installPlugin(slug: PluginWpOrgDirectorySlug) async throws -> InstalledPlugin
2021

2122
func fetchPluginsDirectory(category: WordPressOrgApiPluginDirectoryCategory) async throws
2223
func pluginDirectoryUpdates(query: CategorizedPluginInformationDataStoreQuery) async -> AsyncStream<Result<[CategorizedPluginInformation], Error>>

WordPress/Classes/Plugins/Views/InstalledPluginsListView.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,6 @@ final class InstalledPluginsListViewModel: ObservableObject {
156156
}
157157
}
158158

159-
func toggle(slug: PluginSlug) async {
160-
self.updating.insert(slug)
161-
defer { self.updating.remove(slug) }
162-
163-
do {
164-
try await self.service.togglePluginActivation(slug: slug)
165-
} catch {
166-
DDLogError("Failed to update plugin: \(error)")
167-
}
168-
}
169-
170159
func uninstall(slug: PluginSlug) async {
171160
self.updating.insert(slug)
172161
defer { self.updating.remove(slug) }

WordPress/Classes/Plugins/Views/PluginDetailsView.swift

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ struct PluginDetailsView: View {
3939
return plugin.isActive
4040
? .activated(plugin: plugin)
4141
: .activate(plugin: plugin) {
42-
// TODO
42+
Task { await viewModel.updatePluginStatus(plugin, activated: true) }
4343
}
4444
} else {
4545
return .install(slug: slug) {
46-
// TODO
46+
Task { await viewModel.install() }
4747
}
4848
}
4949
}
@@ -87,7 +87,7 @@ struct PluginDetailsView: View {
8787

8888
actionButton
8989
.view
90-
.disabled(viewModel.isLoading || viewModel.isActivating || viewModel.isUninstalling)
90+
.disabled(viewModel.isLoading || viewModel.isActivating || viewModel.isDeactivating || viewModel.isUninstalling || viewModel.isInstalling)
9191
}
9292
.listRowSeparator(.hidden)
9393

@@ -98,7 +98,13 @@ struct PluginDetailsView: View {
9898
.listSectionSeparator(.hidden)
9999

100100
if viewModel.isUninstalling {
101-
uninstallingView()
101+
inlineProgressView(title: Strings.uninstallingTitle, message: Strings.uninstallingMessage)
102+
} else if viewModel.isInstalling {
103+
inlineProgressView(title: Strings.installingTitle, message: Strings.installingMessage)
104+
} else if viewModel.isActivating {
105+
inlineProgressView(title: Strings.activatingTitle, message: Strings.activatingMessage)
106+
} else if viewModel.isDeactivating {
107+
inlineProgressView(title: Strings.deactivatingTitle, message: Strings.deactivatingMessage)
102108
} else if let newVersion {
103109
updateAvailableView(newVersion)
104110
}
@@ -134,6 +140,14 @@ struct PluginDetailsView: View {
134140
}
135141
}
136142

143+
if let installed = viewModel.installed, installed.isActive {
144+
Button {
145+
Task { await viewModel.updatePluginStatus(installed, activated: false) }
146+
} label: {
147+
Label(Strings.deactivateButton, systemImage: "circle.slash")
148+
}
149+
}
150+
137151
if let url = wpOrgURL {
138152
Section {
139153
ShareLink(item: url)
@@ -214,14 +228,14 @@ struct PluginDetailsView: View {
214228
}
215229

216230
@ViewBuilder
217-
private func uninstallingView() -> some View {
231+
private func inlineProgressView(title: String, message: String) -> some View {
218232
HStack {
219233
ProgressView()
220234

221235
VStack(alignment: .leading) {
222-
Text(Strings.uninstallingTitle)
236+
Text(title)
223237
.font(.headline)
224-
Text(Strings.uninstallingMessage)
238+
Text(message)
225239
.font(.subheadline)
226240
.foregroundStyle(.secondary)
227241
}
@@ -383,10 +397,12 @@ final class WordPressPluginDetailViewModel: ObservableObject {
383397

384398
@Published private(set) var isLoading = false
385399
@Published private(set) var isUninstalling = false
400+
@Published private(set) var isInstalling = false
386401
@Published private(set) var plugin: PluginInformation?
387402
@Published private(set) var installed: InstalledPlugin?
388403
@Published private(set) var error: String?
389404
@Published private(set) var isActivating = false
405+
@Published private(set) var isDeactivating = false
390406

391407
private var initialLoad = false
392408

@@ -428,14 +444,15 @@ final class WordPressPluginDetailViewModel: ObservableObject {
428444
}
429445
}
430446

431-
func activate(_ plugin: InstalledPlugin) async {
432-
isActivating = true
447+
func updatePluginStatus(_ plugin: InstalledPlugin, activated: Bool) async {
448+
let keyPath: ReferenceWritableKeyPath<WordPressPluginDetailViewModel, Bool> = activated ? \.isActivating : \.isDeactivating
449+
self[keyPath: keyPath] = true
433450
defer {
434-
isActivating = false
451+
self[keyPath: keyPath] = false
435452
}
436453

437454
do {
438-
try await service.togglePluginActivation(slug: plugin.slug)
455+
self.installed = try await service.updatePluginStatus(plugin: plugin, activated: false)
439456
} catch {
440457
// TODO: Show an error notice
441458
}
@@ -453,6 +470,19 @@ final class WordPressPluginDetailViewModel: ObservableObject {
453470
// TODO: Show an error notice
454471
}
455472
}
473+
474+
func install() async {
475+
isInstalling = true
476+
defer {
477+
isInstalling = false
478+
}
479+
480+
do {
481+
self.installed = try await service.installPlugin(slug: slug)
482+
} catch {
483+
// TODO: Show an error notice
484+
}
485+
}
456486
}
457487

458488
private extension PluginInformation {
@@ -619,6 +649,12 @@ private enum Strings {
619649
comment: "Button label to activate a plugin"
620650
)
621651

652+
static let deactivateButton = NSLocalizedString(
653+
"pluginDetails.deactivate.button",
654+
value: "Deactivate",
655+
comment: "Button label to deactivate a plugin"
656+
)
657+
622658
static let activatedButton = NSLocalizedString(
623659
"pluginDetails.activated.button",
624660
value: "Activated",
@@ -636,4 +672,40 @@ private enum Strings {
636672
value: "Please wait while the plugin is being removed...",
637673
comment: "Message shown while a plugin is being uninstalled"
638674
)
675+
676+
static let installingTitle = NSLocalizedString(
677+
"pluginDetails.install.title",
678+
value: "Install Plugin",
679+
comment: "Title shown while a plugin is being installed"
680+
)
681+
682+
static let installingMessage = NSLocalizedString(
683+
"pluginDetails.install.message",
684+
value: "Please wait while the plugin is being installed...",
685+
comment: "Message shown while a plugin is being installed"
686+
)
687+
688+
static let activatingTitle = NSLocalizedString(
689+
"pluginDetails.activating.title",
690+
value: "Activating Plugin",
691+
comment: "Title shown while a plugin is being activated"
692+
)
693+
694+
static let activatingMessage = NSLocalizedString(
695+
"pluginDetails.activating.message",
696+
value: "Please wait while the plugin is being activated...",
697+
comment: "Message shown while a plugin is being activated"
698+
)
699+
700+
static let deactivatingTitle = NSLocalizedString(
701+
"pluginDetails.deactivating.title",
702+
value: "Deactivating Plugin",
703+
comment: "Title shown while a plugin is being deactivated"
704+
)
705+
706+
static let deactivatingMessage = NSLocalizedString(
707+
"pluginDetails.deactivating.message",
708+
value: "Please wait while the plugin is being deactivated...",
709+
comment: "Message shown while a plugin is being deactivated"
710+
)
639711
}

WordPress/Classes/Plugins/Views/PluginListItemView.swift

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -40,62 +40,13 @@ struct PluginListItemView: View {
4040

4141
Spacer()
4242
}
43-
.contextMenu(menuItems: {
44-
menuItems
45-
})
46-
.overlay(alignment: .topTrailing) {
47-
Menu {
48-
menuItems
49-
} label: {
50-
Image(systemName: "ellipsis")
51-
.padding(4)
52-
.frame(width: 44, height: 44, alignment: .topTrailing)
53-
.contentShape(Rectangle())
54-
}
55-
.foregroundStyle(.secondary)
56-
}
5743
.sheet(isPresented: $isShowingSafariView) {
5844
if let url = plugin.possibleWpOrgDirectoryURL {
5945
SafariView(url: url)
6046
}
6147
}
6248
}
6349

64-
@ViewBuilder
65-
private var menuItems: some View {
66-
Section {
67-
if plugin.isActive {
68-
Button(Strings.deactivate, systemImage: "bolt.slash") {
69-
Task {
70-
await viewModel.toggle(slug: plugin.slug)
71-
}
72-
}
73-
} else {
74-
Button(Strings.activate, systemImage: "bolt") {
75-
Task {
76-
await viewModel.toggle(slug: plugin.slug)
77-
}
78-
}
79-
Button(Strings.delete, systemImage: "trash", role: .destructive) {
80-
Task {
81-
await viewModel.uninstall(slug: plugin.slug)
82-
}
83-
}
84-
}
85-
}
86-
.disabled(isUpdating)
87-
88-
if plugin.possibleWpOrgDirectoryURL != nil {
89-
Section {
90-
Button {
91-
isShowingSafariView = true
92-
} label: {
93-
Label(Strings.viewOnWordPressOrg, systemImage: "safari")
94-
}
95-
}
96-
}
97-
}
98-
9950
private enum Strings {
10051
static func author(_ author: String) -> String {
10152
let format = NSLocalizedString("sitePluginsList.item.author", value: "By %@", comment: "The plugin author displayed in the plugins list. The first argument is plugin author name")

0 commit comments

Comments
 (0)