@@ -21,14 +21,15 @@ public partial class TrayWindowViewModel : ObservableObject
2121 private readonly IRpcController _rpcController ;
2222 private readonly ICredentialManager _credentialManager ;
2323
24- private ToggleSwitch ? _vpnActiveSwitch ;
25- private bool _isProgrammaticStateChange ;
26-
2724 private DispatcherQueue ? _dispatcherQueue ;
2825
2926 [ ObservableProperty ]
3027 public partial VpnLifecycle VpnLifecycle { get ; set ; } = VpnLifecycle . Unknown ;
3128
29+ // This is a separate property because we need the switch to be 2-way.
30+ [ ObservableProperty ]
31+ public partial bool VpnSwitchActive { get ; set ; } = false ;
32+
3233 [ ObservableProperty ]
3334 public partial string ? VpnFailedMessage { get ; set ; } = null ;
3435
@@ -83,13 +84,13 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
8384 if ( rpcModel . RpcLifecycle is RpcLifecycle . Disconnected )
8485 {
8586 VpnLifecycle = VpnLifecycle . Unknown ;
86- SetVpnSwitch ( false ) ;
87+ VpnSwitchActive = false ;
8788 Agents = [ ] ;
8889 return ;
8990 }
9091
9192 VpnLifecycle = rpcModel . VpnLifecycle ;
92- SetVpnSwitch ( rpcModel . VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ) ;
93+ VpnSwitchActive = rpcModel . VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ;
9394
9495 // Get the current dashboard URL.
9596 var credentialModel = _credentialManager . GetCredentials ( ) ;
@@ -188,42 +189,20 @@ private void UpdateFromCredentialsModel(CredentialModel credentialModel)
188189 DashboardUrl = credentialModel . CoderUrl ?? DefaultDashboardUrl ;
189190 }
190191
191- private void SetVpnSwitch ( bool value )
192- {
193- if ( _vpnActiveSwitch == null ) return ;
194- _isProgrammaticStateChange = true ;
195- _vpnActiveSwitch . IsOn = value ;
196- _isProgrammaticStateChange = false ;
197- }
198-
199- // HACK: using a two-way bool to store the VPN active state results in
200- // erroneous events being sent (even outside our change handlers). This
201- // sucks and breaks the ViewModel separation but is necessary for the
202- // switch to function correctly.
203- public void VpnSwitch_Loaded ( object sender , RoutedEventArgs e )
204- {
205- if ( sender is not ToggleSwitch toggleSwitch ) return ;
206- _vpnActiveSwitch = toggleSwitch ;
207- SetVpnSwitch ( VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ) ;
208- }
209-
210192 public void VpnSwitch_Toggled ( object sender , RoutedEventArgs e )
211193 {
212194 if ( sender is not ToggleSwitch toggleSwitch ) return ;
213195
214- // HACK: the toggled event gets fired even when the switch state is
215- // changed from code, so we ignore all events while we're performing
216- // changes.
217- if ( _isProgrammaticStateChange ) return ;
218-
219196 VpnFailedMessage = "" ;
220197 try
221198 {
222199 // The start/stop methods will call back to update the state.
223- if ( toggleSwitch . IsOn )
200+ if ( toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Stopped )
224201 _rpcController . StartVpn ( ) ;
225- else
202+ else if ( ! toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Started )
226203 _rpcController . StopVpn ( ) ;
204+ else
205+ toggleSwitch . IsOn = VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ;
227206 }
228207 catch
229208 {
0 commit comments