Skip to content

Commit 67c694c

Browse files
committed
Improved the design of HealthPage
1 parent d2e5397 commit 67c694c

7 files changed

Lines changed: 221 additions & 53 deletions

File tree

src/Platforms/SecureFolderFS.Maui/Resources/Styles/Converters.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<vc:ImageToSourceConverter x:Key="ImageToSourceConverter" />
1616
<vc:DateTimeToStringConverter x:Key="DateTimeToStringConverter" />
1717
<vc:ThumbnailToAspectConverter x:Key="ThumbnailToAspectConverter" />
18+
<vc:SeverityHealthIconConverter x:Key="SeverityHealthIconConverter" />
1819
<vc:StringInterpolationConverter x:Key="StringInterpolationConverter" />
1920

2021
</ResourceDictionary>

src/Platforms/SecureFolderFS.Maui/UserControls/Widgets/HealthWidget.xaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,8 @@
66
xmlns:mi="http://www.aathifmahir.com/dotnet/2022/maui/icons"
77
xmlns:mi_cupertino="clr-namespace:MauiIcons.Cupertino;assembly=MauiIcons.Cupertino"
88
xmlns:mi_material="clr-namespace:MauiIcons.Material;assembly=MauiIcons.Material"
9-
xmlns:vc="clr-namespace:SecureFolderFS.Maui.ValueConverters"
109
x:Name="RootControl">
1110

12-
<ContentView.Resources>
13-
<vc:SeverityHealthIconConverter x:Key="SeverityHealthIconConverter" />
14-
</ContentView.Resources>
15-
1611
<Grid
1712
x:DataType="local:HealthWidget"
1813
BindingContext="{x:Reference RootControl}"

src/Platforms/SecureFolderFS.Maui/Views/MainPage.xaml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,12 @@
173173
</CollectionView.ItemTemplate>
174174
</CollectionView>
175175

176-
<Grid
177-
Grid.Row="0"
178-
Grid.RowSpan="2"
179-
VerticalOptions="End">
180-
<OnPlatform x:TypeArguments="Button">
181-
<On Platform="Android">
176+
<OnPlatform x:TypeArguments="Grid">
177+
<On Platform="Android">
178+
<Grid
179+
Grid.Row="0"
180+
Grid.RowSpan="2"
181+
VerticalOptions="End">
182182
<Button
183183
Margin="0,0,16,32"
184184
mi:MauiIcon.Value="{mi_material:Material Icon=Add,
@@ -191,8 +191,11 @@
191191
Text="{l:ResourceString Rid=NewVault}"
192192
TextColor="{StaticResource PrimaryContrastingDarkColor}"
193193
VerticalOptions="End" />
194-
</On>
195-
</OnPlatform>
196-
</Grid>
194+
</Grid>
195+
</On>
196+
<On Platform="iOS">
197+
<Grid />
198+
</On>
199+
</OnPlatform>
197200
</Grid>
198201
</ContentPage>

src/Platforms/SecureFolderFS.Maui/Views/Vault/HealthPage.xaml

Lines changed: 115 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
xmlns:mi_material="clr-namespace:MauiIcons.Material;assembly=MauiIcons.Material"
1111
xmlns:mtk="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
1212
xmlns:ts="clr-namespace:SecureFolderFS.Maui.TemplateSelectors"
13-
xmlns:uc="clr-namespace:SecureFolderFS.Maui.UserControls"
1413
xmlns:uco="clr-namespace:SecureFolderFS.Maui.UserControls.Options"
1514
xmlns:vc="clr-namespace:SecureFolderFS.Maui.ValueConverters"
1615
xmlns:vm="clr-namespace:SecureFolderFS.Sdk.ViewModels.Controls.Widgets.Health;assembly=SecureFolderFS.Sdk"
@@ -82,30 +81,122 @@
8281
<RowDefinition />
8382
</Grid.RowDefinitions>
8483

85-
<!-- Scan button -->
86-
<apesui:ContextMenuContainer
87-
x:Name="ItemContainer"
84+
<!-- Status card -->
85+
<Border
86+
x:Name="StatusCard"
8887
Grid.Row="0"
89-
Margin="0,4,0,0">
90-
<apesui:ContextMenuContainer.MenuItems>
91-
<apesui:ContextMenuItem
92-
Command="{Binding ViewModel.HealthViewModel.StartScanningCommand, Mode=OneWay}"
93-
CommandParameter="include_file_contents"
94-
Text="Scan full vault (include file contents)" />
95-
</apesui:ContextMenuContainer.MenuItems>
96-
<apesui:ContextMenuContainer.Content>
97-
<uc:ProgressiveButton
98-
x:Name="ProgressiveButton"
99-
Title="{Binding ViewModel.HealthViewModel.StatusTitle, Mode=OneWay}"
100-
Clicked="ProgressiveButton_Clicked"
101-
DefaultIcon="{OnPlatform Android={x:Static mi_material:MaterialIcons.Search},
102-
iOS={x:Static mi_cupertino:CupertinoIcons.Search}}"
103-
HorizontalOptions="Fill"
104-
IsProgressing="{Binding ViewModel.HealthViewModel.IsProgressing, Mode=OneWay}"
105-
Progress="{Binding ViewModel.HealthViewModel.CurrentProgress, Mode=OneWay}"
106-
Subtitle="{Binding ViewModel.HealthViewModel.Subtitle, Mode=OneWay}" />
107-
</apesui:ContextMenuContainer.Content>
108-
</apesui:ContextMenuContainer>
88+
Margin="0,4,0,0"
89+
StrokeThickness="0"
90+
VerticalOptions="Start">
91+
<Border.StrokeShape>
92+
<RoundRectangle CornerRadius="12" />
93+
</Border.StrokeShape>
94+
95+
<Grid>
96+
<!-- Gradient background (set from code-behind) -->
97+
<BoxView x:Name="GradientBackground" CornerRadius="12" />
98+
99+
<!-- Shimmer sweep overlay -->
100+
<BoxView
101+
x:Name="ShimmerOverlay"
102+
CornerRadius="12"
103+
IsVisible="False"
104+
Opacity="0.18">
105+
<BoxView.Background>
106+
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
107+
<GradientStop Offset="0.0" Color="Transparent" />
108+
<GradientStop Offset="0.4" Color="White" />
109+
<GradientStop Offset="0.6" Color="White" />
110+
<GradientStop Offset="1.0" Color="Transparent" />
111+
</LinearGradientBrush>
112+
</BoxView.Background>
113+
</BoxView>
114+
115+
<!-- Progress strip at bottom -->
116+
<BoxView
117+
x:Name="ProgressStrip"
118+
HeightRequest="3"
119+
IsVisible="{Binding ViewModel.HealthViewModel.IsProgressing, Mode=OneWay}"
120+
Opacity="0.5"
121+
VerticalOptions="End">
122+
<BoxView.Background>
123+
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
124+
<GradientStop Offset="0" Color="White" />
125+
<GradientStop Offset="1" Color="Transparent" />
126+
</LinearGradientBrush>
127+
</BoxView.Background>
128+
</BoxView>
129+
130+
<!-- Content row -->
131+
<Grid Padding="16,14" ColumnSpacing="14">
132+
<Grid.ColumnDefinitions>
133+
<ColumnDefinition Width="Auto" />
134+
<ColumnDefinition />
135+
<ColumnDefinition Width="Auto" />
136+
</Grid.ColumnDefinitions>
137+
138+
<!-- Severity icon -->
139+
<Image
140+
Grid.Column="0"
141+
HeightRequest="32"
142+
Source="{Binding ViewModel.HealthViewModel.Severity, Mode=OneWay, Converter={StaticResource SeverityHealthIconConverter}}"
143+
VerticalOptions="Center"
144+
WidthRequest="32" />
145+
146+
<!-- Title + subtitle -->
147+
<VerticalStackLayout
148+
Grid.Column="1"
149+
Spacing="2"
150+
VerticalOptions="Center">
151+
<Label
152+
FontAttributes="Bold"
153+
FontSize="16"
154+
Text="{Binding ViewModel.HealthViewModel.StatusTitle, Mode=OneWay}"
155+
TextColor="White" />
156+
<Label
157+
FontSize="13"
158+
IsVisible="{Binding ViewModel.HealthViewModel.Subtitle, Mode=OneWay, Converter={StaticResource NullToBoolConverter}}"
159+
Opacity="0.75"
160+
Text="{Binding ViewModel.HealthViewModel.Subtitle, Mode=OneWay}"
161+
TextColor="White" />
162+
</VerticalStackLayout>
163+
164+
<!-- Options button -->
165+
<apesui:ContextMenuContainer Grid.Column="2" VerticalOptions="Center">
166+
<apesui:ContextMenuContainer.MenuItems>
167+
<apesui:ContextMenuItem Command="{Binding ViewModel.HealthViewModel.StartScanningCommand, Mode=OneWay}" Text="Scan vault" />
168+
<apesui:ContextMenuItem
169+
Command="{Binding ViewModel.HealthViewModel.StartScanningCommand, Mode=OneWay}"
170+
CommandParameter="include_file_contents"
171+
Text="Scan full vault (include file contents)" />
172+
<apesui:ContextMenuItem Command="{Binding ViewModel.HealthViewModel.CancelScanningCommand, Mode=OneWay}" Text="Cancel scan" />
173+
</apesui:ContextMenuContainer.MenuItems>
174+
<apesui:ContextMenuContainer.Content>
175+
<Border
176+
Padding="8,6"
177+
BackgroundColor="#22FFFFFF"
178+
StrokeThickness="0">
179+
<Border.StrokeShape>
180+
<RoundRectangle CornerRadius="8" />
181+
</Border.StrokeShape>
182+
<Grid>
183+
<mi:MauiIcon
184+
Icon="{mi_material:Material MoreVert}"
185+
IconColor="White"
186+
IconSize="20"
187+
OnPlatforms="Android" />
188+
<mi:MauiIcon
189+
Icon="{mi_cupertino:Cupertino Ellipsis}"
190+
IconColor="White"
191+
IconSize="20"
192+
OnPlatforms="iOS" />
193+
</Grid>
194+
</Border>
195+
</apesui:ContextMenuContainer.Content>
196+
</apesui:ContextMenuContainer>
197+
</Grid>
198+
</Grid>
199+
</Border>
109200

110201
<!-- Issues list -->
111202
<Grid Grid.Row="1">

src/Platforms/SecureFolderFS.Maui/Views/Vault/HealthPage.xaml.cs

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using SecureFolderFS.Maui.Extensions;
2+
using SecureFolderFS.Sdk.Enums;
23
using SecureFolderFS.Sdk.ViewModels.Views.Vault;
34

45
namespace SecureFolderFS.Maui.Views.Vault
56
{
67
public partial class HealthPage : ContentPage, IQueryAttributable
78
{
9+
private CancellationTokenSource? _shimmerCts;
10+
811
public HealthPage()
912
{
1013
BindingContext = this;
@@ -14,19 +17,94 @@ public HealthPage()
1417
/// <inheritdoc/>
1518
public void ApplyQueryAttributes(IDictionary<string, object> query)
1619
{
20+
// Unsubscribe from previous
21+
ViewModel?.HealthViewModel.PropertyChanged -= HealthViewModel_PropertyChanged;
22+
1723
ViewModel = query.ToViewModel<VaultHealthReportViewModel>();
1824
OnPropertyChanged(nameof(ViewModel));
25+
26+
ViewModel?.HealthViewModel.PropertyChanged += HealthViewModel_PropertyChanged;
27+
UpdateGradient(ViewModel?.HealthViewModel.Severity ?? Severity.Default);
28+
}
29+
30+
/// <inheritdoc/>
31+
protected override void OnDisappearing()
32+
{
33+
base.OnDisappearing();
34+
ViewModel?.HealthViewModel.PropertyChanged -= HealthViewModel_PropertyChanged;
35+
_shimmerCts?.Dispose();
1936
}
2037

21-
private void ProgressiveButton_Clicked(object? sender, EventArgs e)
38+
private void UpdateGradient(Severity severity)
39+
{
40+
var (start, end) = severity switch
41+
{
42+
Severity.Success => (Color.FromArgb("#2A7A50"), Color.FromArgb("#3DB874")),
43+
Severity.Warning => (Color.FromArgb("#A05C00"), Color.FromArgb("#D4860A")),
44+
Severity.Critical => (Color.FromArgb("#8B1F30"), Color.FromArgb("#C94055")),
45+
_ => (Color.FromArgb("#3A3E45"), Color.FromArgb("#52575F"))
46+
};
47+
48+
GradientBackground.Background = new LinearGradientBrush([
49+
new GradientStop(start, 0f),
50+
new GradientStop(end, 1f)
51+
],
52+
new Point(0, 0.5),
53+
new Point(1, 0.5));
54+
}
55+
56+
private void StartShimmer()
57+
{
58+
_shimmerCts?.Cancel();
59+
_shimmerCts = new CancellationTokenSource();
60+
var token = _shimmerCts.Token;
61+
62+
ShimmerOverlay.IsVisible = true;
63+
64+
// Reset position to far left (off-screen)
65+
ShimmerOverlay.TranslationX = -StatusCard.Width;
66+
67+
_ = Task.Run(async () =>
68+
{
69+
while (!token.IsCancellationRequested)
70+
{
71+
await MainThread.InvokeOnMainThreadAsync(async () =>
72+
{
73+
ShimmerOverlay.TranslationX = -StatusCard.Width;
74+
await ShimmerOverlay.TranslateToAsync(StatusCard.Width, 0, 900, Easing.SinInOut);
75+
});
76+
await Task.Delay(400, token).ContinueWith(_ => { }, CancellationToken.None); // pause between sweeps
77+
}
78+
}, token);
79+
}
80+
81+
private void StopShimmer()
82+
{
83+
_shimmerCts?.Cancel();
84+
_shimmerCts = null;
85+
ShimmerOverlay.IsVisible = false;
86+
ShimmerOverlay.CancelAnimations();
87+
}
88+
89+
private void HealthViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
2290
{
23-
if (ViewModel is null)
91+
if (sender is not VaultHealthViewModel viewModel)
2492
return;
2593

26-
if (ViewModel.HealthViewModel.IsProgressing)
27-
ViewModel.HealthViewModel.CancelScanningCommand.Execute(null);
28-
else
29-
ViewModel.HealthViewModel.StartScanningCommand.Execute(null);
94+
switch (e.PropertyName)
95+
{
96+
case nameof(viewModel.Severity):
97+
UpdateGradient(viewModel.Severity);
98+
break;
99+
100+
case nameof(viewModel.IsProgressing) when viewModel.IsProgressing:
101+
StartShimmer();
102+
break;
103+
104+
case nameof(viewModel.IsProgressing) when !viewModel.IsProgressing:
105+
StopShimmer();
106+
break;
107+
}
30108
}
31109

32110
public VaultHealthReportViewModel? ViewModel

src/Platforms/SecureFolderFS.Uno/Dialogs/LicensesDialog.xaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,29 @@
5454
</Grid>
5555

5656
<ScrollViewer Grid.Row="1">
57-
<ItemsControl ItemsSource="{x:Bind ViewModel.Licenses}" Loaded="Licenses_Loaded">
57+
<ItemsControl ItemsSource="{x:Bind ViewModel.Licenses, Mode=OneWay}" Loaded="Licenses_Loaded">
5858
<ItemsControl.ItemTemplate>
5959
<DataTemplate x:DataType="vm:LicenseViewModel">
6060
<ucab:ActionBlockControl
61-
Title="{x:Bind PackageName}"
61+
Title="{x:Bind PackageName, Mode=OneWay}"
6262
BlockMode="Expandable"
63-
Description="{x:Bind LicenseName}">
63+
Description="{x:Bind LicenseName, Mode=OneWay}">
6464
<ucab:ActionBlockControl.ActionElement>
6565
<StackPanel Orientation="Horizontal" Spacing="8">
6666
<HyperlinkButton
6767
Content="{l:ResourceString Rid=Website}"
68-
NavigateUri="{x:Bind ProjectWebsiteUri}"
69-
Visibility="{x:Bind ProjectWebsiteUri, Converter={StaticResource NullToVisibilityConverter}}" />
68+
NavigateUri="{x:Bind ProjectWebsiteUri, Mode=OneWay}"
69+
Visibility="{x:Bind ProjectWebsiteUri, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
7070
<HyperlinkButton
7171
Content="{l:ResourceString Rid=License}"
72-
NavigateUri="{x:Bind LicenseUris, Converter={StaticResource OnlyFirstElementOrNullConverter}}"
73-
Visibility="{Binding NavigateUri, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource NullToVisibilityConverter}}" />
72+
NavigateUri="{x:Bind LicenseUris, Converter={StaticResource OnlyFirstElementOrNullConverter}, Mode=OneWay}"
73+
Visibility="{Binding NavigateUri, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
7474
</StackPanel>
7575
</ucab:ActionBlockControl.ActionElement>
7676
<ucab:ActionBlockControl.ExpanderContent>
7777
<TextBlock
7878
IsTextSelectionEnabled="True"
79-
Text="{x:Bind LicenseContent}"
79+
Text="{x:Bind LicenseContent, Mode=OneWay}"
8080
TextWrapping="Wrap" />
8181
</ucab:ActionBlockControl.ExpanderContent>
8282
</ucab:ActionBlockControl>

src/Sdk/SecureFolderFS.Sdk/ViewModels/Views/Vault/VaultHealthViewModel.Scanning.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private async Task ScanFolderAsync(string? mode, CancellationToken cancellationT
5050
return;
5151

5252
// Prompt the user to pick a folder
53-
IFolder pickedFolder = await FileExplorerService.PickFolderAsync(null, false);
53+
var pickedFolder = await FileExplorerService.PickFolderAsync(null, false, cancellationToken);
5454
if (pickedFolder is null)
5555
return;
5656

0 commit comments

Comments
 (0)