Skip to content

Commit d6cbf71

Browse files
feat: enabled sign out and animated window resize (#109)
Closes: #96 --------- Co-authored-by: Dean Sheather <dean@deansheather.com>
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy