Skip to content

Commit 9fd6cb4

Browse files
committed
Merge branch 'feature/pro-feature-flag' into develop
2 parents fd1e1cf + 9e3a013 commit 9fd6cb4

15 files changed

Lines changed: 152 additions & 17 deletions

File tree

Core/Package.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ let package = Package(
117117
"GitHubCopilotService",
118118
"CodeiumService",
119119
"LaunchAgentManager",
120+
"PlusFeatureFlag",
120121
.product(name: "Toast", package: "Tool"),
121122
.product(name: "SuggestionModel", package: "Tool"),
122123
.product(name: "MarkdownUI", package: "swift-markdown-ui"),
@@ -263,6 +264,13 @@ let package = Package(
263264
]
264265
),
265266
.target(name: "UserDefaultsObserver"),
267+
.target(
268+
name: "PlusFeatureFlag",
269+
dependencies: [
270+
].pro([
271+
"LicenseManagement"
272+
])
273+
),
266274

267275
// MARK: - GitHub Copilot
268276

Core/Sources/ChatGPTChatTab/ChatGPTChatTab.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class ChatGPTChatTab: ChatTab {
1515

1616
struct Builder: ChatTabBuilder {
1717
var title: String
18+
var buildable: Bool { true }
1819
var customCommand: CustomCommand?
1920

2021
func build() -> any ChatTab {

Core/Sources/Client/AsyncXPCService.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,24 @@ public struct AsyncXPCService {
134134
{ service in { service.customCommand(id: id, editorContent: $0, withReply: $1) } }
135135
)
136136
}
137+
138+
public func postNotification(name: String) async throws {
139+
try await withXPCServiceConnected(connection: connection) {
140+
service, continuation in
141+
service.postNotification(name: name) {
142+
continuation.resume(())
143+
}
144+
}
145+
}
146+
147+
public func performAction(name: String, arguments: String) async throws -> String {
148+
try await withXPCServiceConnected(connection: connection) {
149+
service, continuation in
150+
service.performAction(name: name, arguments: arguments) {
151+
continuation.resume($0)
152+
}
153+
}
154+
}
137155
}
138156

139157
struct AutoFinishContinuation<T> {

Core/Sources/HostApp/CustomCommandSettings/CustomCommand.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ struct CustomCommandFeature: ReducerProtocol {
1818
case deleteCommand(CustomCommand)
1919
}
2020

21-
@Dependency(\.toastController) var toastController
21+
@Dependency(\.toast) var toast
2222

2323
var body: some ReducerProtocol<State, Action> {
2424
Reduce { state, action in
2525
switch action {
2626
case .createNewCommand:
27+
if settings.customCommands.count >= 10 {
28+
toast("Upgrade to Plus to add more commands", .info)
29+
return .none
30+
}
2731
state.editCustomCommand = EditCustomCommand.State(nil)
2832
return .none
2933
case let .editCommand(command):

Core/Sources/HostApp/CustomCommandSettings/CustomCommandView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ComposableArchitecture
22
import MarkdownUI
3+
import PlusFeatureFlag
34
import Preferences
45
import SwiftUI
56

@@ -72,7 +73,12 @@ struct CustomCommandView: View {
7273
Button(action: {
7374
store.send(.createNewCommand)
7475
}) {
75-
Text(Image(systemName: "plus.circle.fill")) + Text(" New Command")
76+
if isFeatureAvailable(\.unlimitedCustomCommands) {
77+
Text(Image(systemName: "plus.circle.fill")) + Text(" New Command")
78+
} else {
79+
Text(Image(systemName: "plus.circle.fill")) +
80+
Text(" New Command (\(settings.customCommands.count)/10)")
81+
}
7682
}
7783
.buttonStyle(.plain)
7884
.padding()

Core/Sources/HostApp/HostApp.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,51 @@
1-
import Foundation
1+
import Client
22
import ComposableArchitecture
3+
import Foundation
4+
5+
#if canImport(LicenseManagement)
6+
import LicenseManagement
7+
#endif
38

49
struct HostApp: ReducerProtocol {
510
struct State: Equatable {
611
var general = General.State()
712
}
8-
13+
914
enum Action: Equatable {
1015
case appear
16+
case informExtensionServiceAboutLicenseKeyChange
1117
case general(General.Action)
1218
}
13-
19+
20+
@Dependency(\.toast) var toast
21+
1422
var body: some ReducerProtocol<State, Action> {
1523
Scope(state: \.general, action: /Action.general) {
1624
General()
1725
}
18-
26+
1927
Reduce { _, action in
2028
switch action {
2129
case .appear:
2230
return .none
31+
case .informExtensionServiceAboutLicenseKeyChange:
32+
#if canImport(LicenseManagement)
33+
return .run { _ in
34+
let service = try getService()
35+
do {
36+
try await service
37+
.postNotification(name: Notification.Name.licenseKeyChanged.rawValue)
38+
} catch {
39+
toast(error.localizedDescription, .error)
40+
}
41+
}
42+
#else
43+
return .none
44+
#endif
2345
case .general:
2446
return .none
2547
}
2648
}
2749
}
2850
}
51+

Core/Sources/HostApp/TabContainer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ public struct TabContainer: View {
5959
image: "command.square"
6060
)
6161
#if canImport(ProHostApp)
62-
PlusView().tabBarItem(
62+
PlusView(onLicenseKeyChanged: {
63+
store.send(.informExtensionServiceAboutLicenseKeyChange)
64+
}).tabBarItem(
6365
tag: 5,
6466
title: "Plus",
6567
image: "plus.diamond"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
#if canImport(LicenseManagement)
5+
6+
import LicenseManagement
7+
8+
#else
9+
10+
public typealias PlusFeatureFlag = Int
11+
12+
public struct PlusFeatureFlags {
13+
public let browserTab = 1
14+
public let unlimitedCustomCommands = 1
15+
init() {}
16+
}
17+
18+
#endif
19+
20+
public func withFeatureEnabled(
21+
_ flag: KeyPath<PlusFeatureFlags, PlusFeatureFlag>,
22+
then: () throws -> Void
23+
) rethrows {
24+
#if canImport(LicenseManagement)
25+
try LicenseManagement.withFeatureEnabled(flag, then: then)
26+
#endif
27+
}
28+
29+
public func withFeatureEnabled(
30+
_ flag: KeyPath<PlusFeatureFlags, PlusFeatureFlag>,
31+
then: () async throws -> Void
32+
) async rethrows {
33+
#if canImport(LicenseManagement)
34+
try await LicenseManagement.withFeatureEnabled(flag, then: then)
35+
#endif
36+
}
37+
38+
public func isFeatureAvailable(_ flag: KeyPath<PlusFeatureFlags, PlusFeatureFlag>) -> Bool {
39+
#if canImport(LicenseManagement)
40+
return LicenseManagement.isFeatureAvailable(flag)
41+
#else
42+
return false
43+
#endif
44+
}
45+

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ struct GUI: ReducerProtocol {
4141
Reduce { _, action in
4242
switch action {
4343
case let .createNewTapButtonClicked(kind):
44-
let chatTap = kind?.builder.build() ?? ChatGPTChatTab()
44+
guard let builder = kind?.builder, builder.buildable else { return .none }
45+
let chatTap = builder.build()
4546
return .run { send in
4647
await send(.appendAndSelectTab(chatTap))
4748
}

Core/Sources/Service/XPCService.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,5 +184,14 @@ public class XPCService: NSObject, XPCServiceProtocol {
184184
reply(nil)
185185
}
186186
}
187+
188+
public func postNotification(name: String, withReply reply: @escaping () -> Void) {
189+
reply()
190+
NSWorkspace.shared.notificationCenter.post(name: .init(name), object: nil)
191+
}
192+
193+
public func performAction(name: String, arguments: String, withReply reply: @escaping (String) -> Void) {
194+
reply("None")
195+
}
187196
}
188197

0 commit comments

Comments
 (0)