Skip to content

Commit d6cbf71

Browse files
feat: enabled sign out and animated window resize (#109)
Closes: #96 --------- Co-authored-by: Dean Sheather <[email protected]>
1 parent 2301c75 commit d6cbf71

10 files changed

+188
-70
lines changed

App/Controls/ExpandContent.xaml

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,43 @@
99
xmlns:toolkit="using:CommunityToolkit.WinUI"
1010
mc:Ignorable="d">
1111

12-
<Grid x:Name="CollapsiblePanel" Opacity="0" Visibility="Collapsed" toolkit:UIElementExtensions.ClipToBounds="True">
12+
<Grid x:Name="CollapsiblePanel" Opacity="0" Visibility="Collapsed" MaxHeight="0" toolkit:UIElementExtensions.ClipToBounds="True">
1313
<Grid.RenderTransform>
14-
<TranslateTransform x:Name="SlideTransform" Y="-10" />
14+
<TranslateTransform x:Name="SlideTransform" Y="-16"/>
1515
</Grid.RenderTransform>
1616

1717
<VisualStateManager.VisualStateGroups>
1818
<VisualStateGroup>
1919
<VisualState x:Name="ExpandedState">
20-
<Storyboard>
21-
<DoubleAnimation
22-
Storyboard.TargetName="CollapsiblePanel"
23-
Storyboard.TargetProperty="Opacity"
24-
To="1"
25-
Duration="0:0:0.2" />
26-
<DoubleAnimation
27-
Storyboard.TargetName="SlideTransform"
28-
Storyboard.TargetProperty="Y"
29-
To="0"
30-
Duration="0:0:0.2" />
20+
<Storyboard x:Name="ExpandSb">
21+
<DoubleAnimation Storyboard.TargetName="CollapsiblePanel"
22+
Storyboard.TargetProperty="MaxHeight"
23+
To="10000" Duration="0:0:0.16" BeginTime="0:0:0.16"
24+
EnableDependentAnimation="True"/>
25+
<DoubleAnimation Storyboard.TargetName="CollapsiblePanel"
26+
Storyboard.TargetProperty="Opacity" BeginTime="0:0:0.16"
27+
To="1" Duration="0:0:0.16"/>
28+
<DoubleAnimation Storyboard.TargetName="SlideTransform"
29+
Storyboard.TargetProperty="Y" BeginTime="0:0:0.16"
30+
To="0" Duration="0:0:0.16"/>
3131
</Storyboard>
3232
</VisualState>
33-
3433
<VisualState x:Name="CollapsedState">
35-
<Storyboard Completed="{x:Bind CollapseAnimation_Completed}">
36-
<DoubleAnimation
37-
Storyboard.TargetName="CollapsiblePanel"
38-
Storyboard.TargetProperty="Opacity"
39-
To="0"
40-
Duration="0:0:0.2" />
41-
<DoubleAnimation
42-
Storyboard.TargetName="SlideTransform"
43-
Storyboard.TargetProperty="Y"
44-
To="-10"
45-
Duration="0:0:0.2" />
34+
<Storyboard x:Name="CollapseSb"
35+
Completed="{x:Bind CollapseStoryboard_Completed}">
36+
<DoubleAnimation Storyboard.TargetName="CollapsiblePanel"
37+
Storyboard.TargetProperty="MaxHeight"
38+
To="0" Duration="0:0:0.16"
39+
EnableDependentAnimation="True"/>
40+
<DoubleAnimation Storyboard.TargetName="CollapsiblePanel"
41+
Storyboard.TargetProperty="Opacity"
42+
To="0" Duration="0:0:0.16"/>
43+
<DoubleAnimation Storyboard.TargetName="SlideTransform"
44+
Storyboard.TargetProperty="Y"
45+
To="-16" Duration="0:0:0.16"/>
4646
</Storyboard>
4747
</VisualState>
48+
4849
</VisualStateGroup>
4950
</VisualStateManager.VisualStateGroups>
5051
</Grid>

App/Controls/ExpandContent.xaml.cs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,60 @@
22
using Microsoft.UI.Xaml;
33
using Microsoft.UI.Xaml.Controls;
44
using Microsoft.UI.Xaml.Markup;
5+
using System;
6+
using System.Threading.Tasks;
57

68
namespace Coder.Desktop.App.Controls;
79

10+
811
[ContentProperty(Name = nameof(Children))]
912
[DependencyProperty<bool>("IsOpen", DefaultValue = false)]
1013
public sealed partial class ExpandContent : UserControl
1114
{
1215
public UIElementCollection Children => CollapsiblePanel.Children;
1316

17+
private readonly string _expandedState = "ExpandedState";
18+
private readonly string _collapsedState = "CollapsedState";
19+
1420
public ExpandContent()
1521
{
1622
InitializeComponent();
17-
}
23+
Loaded += (_, __) =>
24+
{
25+
// When we load the control for the first time (after panel swapping)
26+
// we need to set the initial state based on IsOpen.
27+
VisualStateManager.GoToState(
28+
this,
29+
IsOpen ? _expandedState : _collapsedState,
30+
useTransitions: false); // NO animation yet
1831

19-
public void CollapseAnimation_Completed(object? sender, object args)
20-
{
21-
// Hide the panel completely when the collapse animation is done. This
22-
// cannot be done with keyframes for some reason.
23-
//
24-
// Without this, the space will still be reserved for the panel.
25-
CollapsiblePanel.Visibility = Visibility.Collapsed;
32+
// If IsOpen was already true we must also show the panel
33+
if (IsOpen)
34+
{
35+
CollapsiblePanel.Visibility = Visibility.Visible;
36+
// This makes the panel expand to its full height
37+
CollapsiblePanel.ClearValue(FrameworkElement.MaxHeightProperty);
38+
}
39+
};
2640
}
2741

2842
partial void OnIsOpenChanged(bool oldValue, bool newValue)
2943
{
30-
var newState = newValue ? "ExpandedState" : "CollapsedState";
31-
32-
// The animation can't set visibility when starting or ending the
33-
// animation.
44+
var newState = newValue ? _expandedState : _collapsedState;
3445
if (newValue)
46+
{
3547
CollapsiblePanel.Visibility = Visibility.Visible;
48+
// We use BeginTime to ensure other panels are collapsed first.
49+
// If the user clicks the expand button quickly, we want to avoid
50+
// the panel expanding to its full height before the collapse animation completes.
51+
CollapseSb.SkipToFill();
52+
}
3653

3754
VisualStateManager.GoToState(this, newState, true);
3855
}
56+
57+
private void CollapseStoryboard_Completed(object sender, object e)
58+
{
59+
CollapsiblePanel.Visibility = Visibility.Collapsed;
60+
}
3961
}

App/Services/RpcController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ public async Task StopVpn(CancellationToken ct = default)
234234
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Unknown; });
235235
throw new VpnLifecycleException($"Failed to stop VPN. Service reported failure: {reply.Stop.ErrorMessage}");
236236
}
237+
238+
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Stopped; });
237239
}
238240

239241
public async ValueTask DisposeAsync()

App/ViewModels/AgentViewModel.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,20 @@ public AgentViewModel(ILogger<AgentViewModel> logger, ICoderApiClientFactory cod
237237

238238
Id = id;
239239

240-
PropertyChanged += (_, args) =>
240+
PropertyChanging += (x, args) =>
241241
{
242242
if (args.PropertyName == nameof(IsExpanded))
243243
{
244-
_expanderHost.HandleAgentExpanded(Id, IsExpanded);
244+
var value = !IsExpanded;
245+
if (value)
246+
_expanderHost.HandleAgentExpanded(Id, value);
247+
}
248+
};
245249

250+
PropertyChanged += (x, args) =>
251+
{
252+
if (args.PropertyName == nameof(IsExpanded))
253+
{
246254
// Every time the drawer is expanded, re-fetch all apps.
247255
if (IsExpanded && !FetchingApps)
248256
FetchApps();

App/ViewModels/TrayWindowLoginRequiredViewModel.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Coder.Desktop.App.Views;
33
using CommunityToolkit.Mvvm.Input;
44
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.UI.Xaml;
56

67
namespace Coder.Desktop.App.ViewModels;
78

@@ -31,4 +32,10 @@ public void Login()
3132
_signInWindow.Closed += (_, _) => _signInWindow = null;
3233
_signInWindow.Activate();
3334
}
35+
36+
[RelayCommand]
37+
public void Exit()
38+
{
39+
_ = ((App)Application.Current).ExitApplication();
40+
}
3441
}

App/ViewModels/TrayWindowViewModel.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public void HandleAgentExpanded(Uuid id, bool expanded)
126126
if (!expanded) return;
127127
_hasExpandedAgent = true;
128128
// Collapse every other agent.
129-
foreach (var otherAgent in Agents.Where(a => a.Id != id))
129+
foreach (var otherAgent in Agents.Where(a => a.Id != id && a.IsExpanded == true))
130130
otherAgent.SetExpanded(false);
131131
}
132132

@@ -360,11 +360,10 @@ private void ShowFileSyncListWindow()
360360
}
361361

362362
[RelayCommand]
363-
private void SignOut()
363+
private async Task SignOut()
364364
{
365-
if (VpnLifecycle is not VpnLifecycle.Stopped)
366-
return;
367-
_credentialManager.ClearCredentials();
365+
await _rpcController.StopVpn();
366+
await _credentialManager.ClearCredentials();
368367
}
369368

370369
[RelayCommand]

App/Views/Pages/TrayWindowLoginRequiredPage.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,14 @@
3434

3535
<TextBlock Text="Sign in" Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" />
3636
</HyperlinkButton>
37+
38+
<HyperlinkButton
39+
Command="{x:Bind ViewModel.ExitCommand, Mode=OneWay}"
40+
Margin="-12,-8,-12,-5"
41+
HorizontalAlignment="Stretch"
42+
HorizontalContentAlignment="Left">
43+
44+
<TextBlock Text="Exit" Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" />
45+
</HyperlinkButton>
3746
</StackPanel>
3847
</Page>

App/Views/Pages/TrayWindowMainPage.xaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@
333333

334334
<HyperlinkButton
335335
Command="{x:Bind ViewModel.SignOutCommand, Mode=OneWay}"
336-
IsEnabled="{x:Bind ViewModel.VpnLifecycle, Converter={StaticResource StoppedBoolConverter}, Mode=OneWay}"
337336
Margin="-12,0"
338337
HorizontalAlignment="Stretch"
339338
HorizontalContentAlignment="Left">

App/Views/TrayWindow.xaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,12 @@
2020

2121
<!-- This is where the current Page is displayed -->
2222
<controls:SizedFrame x:Name="RootFrame" />
23+
24+
<!-- proxy for animating resize -->
25+
<Border x:Name="SizeProxy"
26+
Width="0"
27+
Height="0"
28+
IsHitTestVisible="False"
29+
Opacity="0" />
2330
</Grid>
2431
</Window>

0 commit comments

Comments
 (0)