88import AppIntents
99import SwiftUI
1010import WidgetKit
11+ import NetworkExtension
1112
1213struct ControlWidgetsControl : ControlWidget {
1314 static let kind : String = " site.yinmo.easytier.ControlWidgets "
1415
1516 var body : some ControlWidgetConfiguration {
16- AppIntentControlConfiguration (
17+ StaticControlConfiguration (
1718 kind: Self . kind,
1819 provider: VPNControlProvider ( )
1920 ) { isConnected in
2021 ControlWidgetToggle (
22+ " EasyTier " ,
2123 isOn: isConnected,
2224 action: ToggleVPNIntent ( )
23- ) {
24- Label ( " EasyTier " , systemImage: " network " )
25+ ) { isOn in
26+ Label ( isOn ? " Connected " : " Disconnected " , systemImage: " network " )
27+ . controlWidgetActionHint ( isOn ? " Disconnect " : " Connect " )
2528 }
2629 }
2730 . displayName ( " EasyTier " )
2831 . description ( " Toggle VPN connection " )
2932 }
3033}
3134
32- struct VPNControlProvider : AppIntentControlValueProvider {
33- func previewValue( configuration: VPNControlConfiguration ) -> Bool {
34- false
35- }
36-
37- func currentValue( configuration: VPNControlConfiguration ) async throws -> Bool {
38- let defaults = UserDefaults ( suiteName: " group.site.yinmo.easytier " )
39- return defaults? . bool ( forKey: " VPNIsConnected " ) ?? false
35+ extension ControlWidgetsControl {
36+ struct VPNControlProvider : ControlValueProvider {
37+ var previewValue : Bool {
38+ false
39+ }
40+
41+ func currentValue( ) async throws -> Bool {
42+ let managers = try await NETunnelProviderManager . loadAllFromPreferences ( )
43+ guard let manager = managers. first else {
44+ return false
45+ }
46+ return manager. connection. status == . connected
47+ }
4048 }
4149}
4250
43- struct VPNControlConfiguration : ControlConfigurationIntent {
44- static let title : LocalizedStringResource = " VPN Control "
51+ struct ToggleVPNIntent : SetValueIntent {
52+ static let title : LocalizedStringResource = " Toggle VPN "
53+
54+ @Parameter ( title: " Connected " )
55+ var value : Bool
56+
57+ func perform( ) async throws -> some IntentResult {
58+ let managers = try await NETunnelProviderManager . loadAllFromPreferences ( )
59+ guard let manager = managers. first else {
60+ return . result( )
61+ }
62+
63+ if value {
64+ // Connect - need to load config from App Group
65+ let defaults = UserDefaults ( suiteName: " group.site.yinmo.easytier " )
66+ guard let configData = defaults? . data ( forKey: " LastVPNConfig " ) ,
67+ let config = try ? JSONDecoder ( ) . decode ( [ String : String ] . self, from: configData) else {
68+ // Try to start with empty options as fallback
69+ try manager. connection. startVPNTunnel ( )
70+ return . result( )
71+ }
72+
73+ // Convert to NSDictionary for VPN options
74+ var options : [ String : NSObject ] = [ : ]
75+ for (key, val) in config {
76+ options [ key] = val as NSString
77+ }
78+ try manager. connection. startVPNTunnel ( options: options)
79+ } else {
80+ manager. connection. stopVPNTunnel ( )
81+ }
82+
83+ return . result( )
84+ }
4585}
0 commit comments