Skip to content

Commit c9cffae

Browse files
committed
Avoid code duplication
1 parent 6260a71 commit c9cffae

6 files changed

Lines changed: 264 additions & 326 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<ContentView
2+
x:Class="SecureFolderFS.Maui.UserControls.HealthScanControl"
3+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
xmlns:mtk="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
6+
x:Name="ThisControl">
7+
8+
<Border
9+
x:Name="CardBorder"
10+
HeightRequest="72"
11+
StrokeThickness="0">
12+
<Border.StrokeShape>
13+
<RoundRectangle CornerRadius="12" />
14+
</Border.StrokeShape>
15+
16+
<Grid>
17+
<!-- Gradient background -->
18+
<BoxView x:Name="GradientBackground" />
19+
20+
<!-- Shimmer sweep overlay -->
21+
<BoxView
22+
x:Name="ShimmerOverlay"
23+
IsVisible="False"
24+
Opacity="0.2">
25+
<BoxView.Background>
26+
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
27+
<GradientStop Offset="0.0" Color="Transparent" />
28+
<GradientStop Offset="0.4" Color="White" />
29+
<GradientStop Offset="0.6" Color="White" />
30+
<GradientStop Offset="1.0" Color="Transparent" />
31+
</LinearGradientBrush>
32+
</BoxView.Background>
33+
</BoxView>
34+
35+
<!-- Content row -->
36+
<Grid Padding="16,14" ColumnSpacing="12">
37+
<Grid.ColumnDefinitions>
38+
<ColumnDefinition Width="Auto" />
39+
<ColumnDefinition />
40+
<ColumnDefinition Width="Auto" />
41+
</Grid.ColumnDefinitions>
42+
43+
<!-- Severity icon -->
44+
<Image
45+
Grid.Column="0"
46+
HeightRequest="36"
47+
Source="{Binding Severity, Mode=OneWay, Source={x:Reference ThisControl}, Converter={StaticResource SeverityHealthIconConverter}}"
48+
VerticalOptions="Center"
49+
WidthRequest="36" />
50+
51+
<!-- Title + Subtitle -->
52+
<VerticalStackLayout Grid.Column="1" VerticalOptions="Center">
53+
<Label
54+
FontAttributes="Bold"
55+
FontSize="16"
56+
Text="{Binding Title, Mode=OneWay, Source={x:Reference ThisControl}}"
57+
TextColor="White" />
58+
<Label
59+
FontSize="13"
60+
IsVisible="{Binding Subtitle, Mode=OneWay, Source={x:Reference ThisControl}, Converter={StaticResource NullToBoolConverter}}"
61+
Opacity="0.7"
62+
Text="{Binding Subtitle, Mode=OneWay, Source={x:Reference ThisControl}}"
63+
TextColor="White" />
64+
</VerticalStackLayout>
65+
66+
<!-- Icon Slot -->
67+
<ContentView
68+
Grid.Column="2"
69+
Content="{Binding IconSlot, Mode=OneWay, Source={x:Reference ThisControl}}"
70+
IsVisible="{Binding IconSlot, Mode=OneWay, Source={x:Reference ThisControl}}" />
71+
</Grid>
72+
</Grid>
73+
74+
<Border.Behaviors>
75+
<mtk:TouchBehavior
76+
DefaultAnimationDuration="150"
77+
DefaultBackgroundColor="{AppThemeBinding Light={StaticResource LightTransparentColor},
78+
Dark={StaticResource DarkTransparentColor}}"
79+
PressedAnimationDuration="150"
80+
PressedBackgroundColor="{AppThemeBinding Light={StaticResource ShimFillSecondaryLightColor},
81+
Dark={StaticResource ShimFillSecondaryDarkColor}}"
82+
TouchGestureCompleted="TouchBehavior_TouchGestureCompleted" />
83+
</Border.Behaviors>
84+
</Border>
85+
</ContentView>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using CommunityToolkit.Maui.Core;
2+
using SecureFolderFS.Maui.Helpers;
3+
using SecureFolderFS.Sdk.Enums;
4+
using SecureFolderFS.UI.Enums;
5+
6+
namespace SecureFolderFS.Maui.UserControls
7+
{
8+
public partial class HealthScanControl : ContentView, IDisposable
9+
{
10+
private CancellationTokenSource? _shimmerCts;
11+
12+
public event EventHandler? Clicked;
13+
14+
public HealthScanControl()
15+
{
16+
InitializeComponent();
17+
GradientBackground.Background = GetGradientBrush(Severity);
18+
}
19+
20+
private void StartShimmer()
21+
{
22+
_shimmerCts?.Cancel();
23+
_shimmerCts = new CancellationTokenSource();
24+
var token = _shimmerCts.Token;
25+
26+
ShimmerOverlay.IsVisible = true;
27+
28+
_ = Task.Run(async () =>
29+
{
30+
while (!token.IsCancellationRequested)
31+
{
32+
await MainThread.InvokeOnMainThreadAsync(async () =>
33+
{
34+
var cardWidth = CardBorder.Width;
35+
ShimmerOverlay.TranslationX = -cardWidth;
36+
await ShimmerOverlay.TranslateToAsync(cardWidth, 0, 900, Easing.SinInOut);
37+
});
38+
await Task.Delay(400, token).ContinueWith(_ => { }, CancellationToken.None);
39+
}
40+
}, token);
41+
}
42+
43+
private void StopShimmer()
44+
{
45+
_shimmerCts?.Cancel();
46+
_shimmerCts = null;
47+
ShimmerOverlay.IsVisible = false;
48+
ShimmerOverlay.CancelAnimations();
49+
}
50+
51+
private static LinearGradientBrush GetGradientBrush(Severity severity)
52+
{
53+
var (start, end) = severity switch
54+
{
55+
Severity.Success => (Color.FromArgb("#2A7A50"), Color.FromArgb("#3DB874")),
56+
Severity.Warning => (Color.FromArgb("#A05C00"), Color.FromArgb("#D4860A")),
57+
Severity.Critical => (Color.FromArgb("#8B1F30"), Color.FromArgb("#C94055")),
58+
_ => MauiThemeHelper.Instance.ActualTheme switch
59+
{
60+
ThemeType.Light => (Color.FromArgb("#6B8BA4"), Color.FromArgb("#8AAEC7")),
61+
_ => (Color.FromArgb("#3A3E45"), Color.FromArgb("#52575F"))
62+
}
63+
};
64+
65+
return new LinearGradientBrush([
66+
new GradientStop(start, 0f),
67+
new GradientStop(end, 1f)
68+
],
69+
new Point(0, 0.5),
70+
new Point(1, 0.5));
71+
}
72+
73+
private void TouchBehavior_TouchGestureCompleted(object? sender, TouchGestureCompletedEventArgs e)
74+
{
75+
Clicked?.Invoke(this, EventArgs.Empty);
76+
}
77+
78+
public Severity Severity
79+
{
80+
get => (Severity)GetValue(SeverityProperty);
81+
set => SetValue(SeverityProperty, value);
82+
}
83+
public static readonly BindableProperty SeverityProperty =
84+
BindableProperty.Create(nameof(Severity), typeof(Severity), typeof(HealthScanControl), Severity.Default,
85+
propertyChanged: static (bindable, _, newValue) =>
86+
{
87+
if (bindable is HealthScanControl control && newValue is Severity severity)
88+
control.GradientBackground.Background = GetGradientBrush(severity);
89+
});
90+
91+
public string? Title
92+
{
93+
get => (string?)GetValue(TitleProperty);
94+
set => SetValue(TitleProperty, value);
95+
}
96+
public static readonly BindableProperty TitleProperty =
97+
BindableProperty.Create(nameof(Title), typeof(string), typeof(HealthScanControl));
98+
99+
public string? Subtitle
100+
{
101+
get => (string?)GetValue(SubtitleProperty);
102+
set => SetValue(SubtitleProperty, value);
103+
}
104+
public static readonly BindableProperty SubtitleProperty =
105+
BindableProperty.Create(nameof(Subtitle), typeof(string), typeof(HealthScanControl));
106+
107+
public object? IconSlot
108+
{
109+
get => (object?)GetValue(IconSlotProperty);
110+
set => SetValue(IconSlotProperty, value);
111+
}
112+
public static readonly BindableProperty IconSlotProperty =
113+
BindableProperty.Create(nameof(IconSlot), typeof(object), typeof(HealthScanControl));
114+
115+
public bool IsProgressing
116+
{
117+
get => (bool)GetValue(IsProgressingProperty);
118+
set => SetValue(IsProgressingProperty, value);
119+
}
120+
public static readonly BindableProperty IsProgressingProperty =
121+
BindableProperty.Create(nameof(IsProgressing), typeof(bool), typeof(HealthScanControl), false,
122+
propertyChanged: static (bindable, _, newValue) =>
123+
{
124+
if (bindable is not HealthScanControl control)
125+
return;
126+
127+
if (newValue is true)
128+
control.StartShimmer();
129+
else
130+
control.StopShimmer();
131+
});
132+
133+
/// <inheritdoc/>
134+
public void Dispose()
135+
{
136+
StopShimmer();
137+
}
138+
}
139+
}

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

Lines changed: 23 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,102 +2,32 @@
22
x:Class="SecureFolderFS.Maui.UserControls.Widgets.HealthWidget"
33
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
44
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5-
xmlns:local="clr-namespace:SecureFolderFS.Maui.UserControls.Widgets"
65
xmlns:mi="http://www.aathifmahir.com/dotnet/2022/maui/icons"
76
xmlns:mi_cupertino="clr-namespace:MauiIcons.Cupertino;assembly=MauiIcons.Cupertino"
87
xmlns:mi_material="clr-namespace:MauiIcons.Material;assembly=MauiIcons.Material"
8+
xmlns:uc="clr-namespace:SecureFolderFS.Maui.UserControls"
99
x:Name="RootControl">
1010

11-
<Border
12-
x:Name="CardBorder"
13-
x:DataType="local:HealthWidget"
14-
BindingContext="{x:Reference RootControl}"
15-
StrokeThickness="0">
16-
<Border.StrokeShape>
17-
<RoundRectangle CornerRadius="12" />
18-
</Border.StrokeShape>
19-
20-
<Grid>
21-
<!-- Gradient background -->
22-
<BoxView x:Name="GradientBackground" />
23-
24-
<!-- Shimmer sweep overlay -->
25-
<BoxView
26-
x:Name="ShimmerOverlay"
27-
IsVisible="False"
28-
Opacity="0.18">
29-
<BoxView.Background>
30-
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
31-
<GradientStop Offset="0.0" Color="Transparent" />
32-
<GradientStop Offset="0.4" Color="White" />
33-
<GradientStop Offset="0.6" Color="White" />
34-
<GradientStop Offset="1.0" Color="Transparent" />
35-
</LinearGradientBrush>
36-
</BoxView.Background>
37-
</BoxView>
38-
39-
<!-- Content row -->
40-
<Grid Padding="16,14" ColumnSpacing="12">
41-
<Grid.ColumnDefinitions>
42-
<ColumnDefinition Width="Auto" />
43-
<ColumnDefinition />
44-
<ColumnDefinition Width="Auto" />
45-
</Grid.ColumnDefinitions>
46-
47-
<!-- Severity icon -->
48-
<Image
49-
Grid.Column="0"
50-
HeightRequest="36"
51-
Source="{Binding Severity, Mode=OneWay, Converter={StaticResource SeverityHealthIconConverter}}"
52-
VerticalOptions="Center"
53-
WidthRequest="36" />
54-
55-
<!-- Title + last checked -->
56-
<VerticalStackLayout
57-
Grid.Column="1"
58-
Spacing="2"
59-
VerticalOptions="Center">
60-
<Label
61-
FontAttributes="Bold"
62-
FontSize="16"
63-
Text="{Binding StatusTitle, Mode=OneWay}"
64-
TextColor="White" />
65-
<Label
66-
FontSize="12"
67-
IsVisible="{Binding LastCheckedText, Mode=OneWay, Converter={StaticResource NullToBoolConverter}}"
68-
Opacity="0.7"
69-
Text="{Binding LastCheckedText, Mode=OneWay}"
70-
TextColor="White" />
71-
</VerticalStackLayout>
72-
73-
<!-- Chevron button -->
74-
<OnPlatform x:TypeArguments="Button">
75-
<On Platform="Android">
76-
<Button
77-
Grid.Column="2"
78-
Padding="8"
79-
mi:MauiIcon.Value="{mi_material:Material ChevronRight,
80-
IconColor=White}"
81-
Command="{Binding OpenVaultHealthCommand, Mode=OneWay}"
82-
HeightRequest="44"
83-
Style="{StaticResource TransparentButtonStyle}"
84-
VerticalOptions="Center"
85-
WidthRequest="44" />
86-
</On>
87-
<On Platform="iOS">
88-
<Button
89-
Grid.Column="2"
90-
Padding="8"
91-
mi:MauiIcon.Value="{mi_cupertino:Cupertino ChevronRight,
92-
IconColor=White}"
93-
Command="{Binding OpenVaultHealthCommand, Mode=OneWay}"
94-
HeightRequest="44"
95-
Style="{StaticResource TransparentButtonStyle}"
96-
VerticalOptions="Center"
97-
WidthRequest="44" />
98-
</On>
99-
</OnPlatform>
100-
</Grid>
101-
</Grid>
102-
</Border>
11+
<Grid>
12+
<uc:HealthScanControl
13+
x:Name="ScanControl"
14+
Title="{Binding StatusTitle, Mode=OneWay, Source={x:Reference RootControl}}"
15+
Clicked="HealthScanControl_Clicked"
16+
IsProgressing="{Binding IsProgressing, Mode=OneWay, Source={x:Reference RootControl}}"
17+
Severity="{Binding Severity, Mode=OneWay, Source={x:Reference RootControl}}"
18+
Subtitle="{Binding LastCheckedText, Mode=OneWay, Source={x:Reference RootControl}}">
19+
<uc:HealthScanControl.IconSlot>
20+
<Grid>
21+
<mi:MauiIcon
22+
Icon="{mi_material:Material ChevronRight}"
23+
IconColor="White"
24+
OnPlatforms="Android" />
25+
<mi:MauiIcon
26+
Icon="{mi_cupertino:Cupertino ChevronRight}"
27+
IconColor="White"
28+
OnPlatforms="iOS" />
29+
</Grid>
30+
</uc:HealthScanControl.IconSlot>
31+
</uc:HealthScanControl>
32+
</Grid>
10333
</ContentView>

0 commit comments

Comments
 (0)