diff --git a/IntentsExtension/AppShortcuts.xcstrings b/IntentsExtension/AppShortcuts.xcstrings new file mode 100644 index 0000000..f8ccfa3 --- /dev/null +++ b/IntentsExtension/AppShortcuts.xcstrings @@ -0,0 +1,56 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Start ${applicationName}" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动 ${applicationName}" + } + } + } + }, + "Stop ${applicationName}" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止 ${applicationName}" + } + } + } + }, + "Restart ${applicationName}" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重启 ${applicationName}" + } + } + } + }, + "Toggle ${applicationName}" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开关 ${applicationName}" + } + } + } + }, + "Update ${applicationName} profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新 ${applicationName} 配置" + } + } + } + } + }, + "version" : "1.0" +} diff --git a/IntentsExtension/Intents.swift b/IntentsExtension/Intents.swift index 9bc14cf..9aff93c 100644 --- a/IntentsExtension/Intents.swift +++ b/IntentsExtension/Intents.swift @@ -16,7 +16,7 @@ struct StartServiceIntent: AppIntent { @Parameter(title: "Profile", optionsProvider: ProfileProvider()) var profile: String - func perform() async throws -> some IntentResult { + func perform() async throws -> some IntentResult & ProvidesDialog { guard let extensionProfile = try await (ExtensionProfile.load()) else { throw NSError(domain: "NetworkExtension not installed", code: 0) } @@ -34,7 +34,7 @@ struct StartServiceIntent: AppIntent { } if extensionProfile.status == .connected { if !profileChanged { - return .result() + return .result(dialog: "Service is already running") } try LibboxNewStandaloneCommandClient()!.serviceReload() } else if extensionProfile.status.isConnected { @@ -42,7 +42,7 @@ struct StartServiceIntent: AppIntent { } else { try await extensionProfile.start() } - return .result() + return .result(dialog: "Service started") } } @@ -56,9 +56,9 @@ struct RestartServiceIntent: AppIntent { Summary("Restart sing-box service") } - func perform() async throws -> some IntentResult { + func perform() async throws -> some IntentResult & ProvidesDialog { guard let extensionProfile = try await (ExtensionProfile.load()) else { - return .result() + return .result(dialog: "Service is not installed") } if extensionProfile.status == .connected { try LibboxNewStandaloneCommandClient()!.serviceReload() @@ -67,7 +67,7 @@ struct RestartServiceIntent: AppIntent { } else { try await extensionProfile.start() } - return .result() + return .result(dialog: "Service restarted") } } @@ -81,12 +81,12 @@ struct StopServiceIntent: AppIntent { Summary("Stop sing-box service") } - func perform() async throws -> some IntentResult { + func perform() async throws -> some IntentResult & ProvidesDialog { guard let extensionProfile = try await (ExtensionProfile.load()) else { - return .result() + return .result(dialog: "Service is not installed") } try await extensionProfile.stop() - return .result() + return .result(dialog: "Service stopped") } } @@ -165,7 +165,7 @@ struct UpdateProfileIntent: AppIntent { var profile: String init() {} - func perform() async throws -> some IntentResult { + func perform() async throws -> some IntentResult & ProvidesDialog { guard let profile = try await ProfileManager.get(by: profile) else { throw NSError(domain: "Specified profile not found: \(profile)", code: 0) } @@ -173,7 +173,7 @@ struct UpdateProfileIntent: AppIntent { throw NSError(domain: "Specified profile is not a remote profile", code: 0) } try await profile.updateRemoteProfile() - return .result() + return .result(dialog: "Profile updated") } } @@ -192,3 +192,38 @@ class RemoteProfileProvider: DynamicOptionsProvider { try await ProfileManager.listRemote().map(\.name) } } + +struct ServiceShortcuts: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] { + AppShortcut( + intent: StartServiceIntent(), + phrases: ["Start \(.applicationName)"], + shortTitle: "Start", + systemImageName: "power" + ) + AppShortcut( + intent: StopServiceIntent(), + phrases: ["Stop \(.applicationName)"], + shortTitle: "Stop", + systemImageName: "stop.fill" + ) + AppShortcut( + intent: RestartServiceIntent(), + phrases: ["Restart \(.applicationName)"], + shortTitle: "Restart", + systemImageName: "arrow.clockwise" + ) + AppShortcut( + intent: ToggleServiceIntent(), + phrases: ["Toggle \(.applicationName)"], + shortTitle: "Toggle", + systemImageName: "arrow.triangle.2.circlepath" + ) + AppShortcut( + intent: UpdateProfileIntent(), + phrases: ["Update \(.applicationName) profile"], + shortTitle: "Update Profile", + systemImageName: "arrow.down.circle" + ) + } +} diff --git a/WidgetExtension/ServiceToggleControl.swift b/WidgetExtension/ServiceToggleControl.swift index 87bcdb3..9e61561 100644 --- a/WidgetExtension/ServiceToggleControl.swift +++ b/WidgetExtension/ServiceToggleControl.swift @@ -15,6 +15,7 @@ struct ServiceToggleControl: ControlWidget { action: ToggleServiceIntent() ) { isOn in Label(isOn ? "Running" : "Stopped", systemImage: "shippingbox.fill") + .controlWidgetActionHint(isOn ? "Stop" : "Start") } .tint(.init(red: CGFloat(Double(69) / 255), green: CGFloat(Double(90) / 255), blue: CGFloat(Double(100) / 255))) }