Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Captcha control #54

Merged
merged 4 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added images/alohakit-captcha.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/AlohaKit.Gallery/AlohaKit.Gallery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
<!-- Required - WinUI does not yet have buildTransitive for everything -->
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.5" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.0.3.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.0.4" />
</ItemGroup>

Expand Down
3 changes: 3 additions & 0 deletions src/AlohaKit.Gallery/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]

new SectionModel(typeof(ButtonView), "Button",
"The Button responds to a tap or click."),

new SectionModel(typeof(CaptchaView), "Captcha",
"Displays a distorted word."),

new SectionModel(typeof(CheckBoxView), "CheckBox",
"CheckBox is a type of button that can either be checked or empty."),
Expand Down
61 changes: 61 additions & 0 deletions src/AlohaKit.Gallery/Views/CaptchaView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AlohaKit.Gallery.Views.CaptchaView"
xmlns:controls="clr-namespace:AlohaKit.Controls;assembly=AlohaKit"
Title="Captcha">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- DESCRIPTION -->
<StackLayout
Style="{StaticResource SectionContainerStyle}">
<Label
Text="Displays a distorted word."/>
</StackLayout>
<!-- FEATURES -->
<StackLayout
Grid.Row="1"
BackgroundColor="{AppThemeBinding Light={StaticResource LightBackgroundSecondaryColor}, Dark={StaticResource DarkBackgroundSecondaryColor}}"
Style="{StaticResource SectionContainerStyle}">
<Label
Text="Features"
Style="{StaticResource SectionTitleStyle}"/>
<Label
Text="- Can manage the word complexity."/>
<Label
Text="- All the colors can be customized."/>
</StackLayout>
<!-- SETTINGS -->
<StackLayout
Grid.Row="2"
Style="{StaticResource SectionContainerStyle}">
<Label
Text="Settings"
Style="{StaticResource SectionTitleStyle}"/>
<StackLayout
Orientation="Horizontal"
Margin="0, 6">
<Label
Text="TextColor"
VerticalOptions="Center"
Style="{StaticResource SettingsTextStyle}"/>
<Entry
x:Name="TextColorEntry"
Placeholder="TextColor"
Text="#000000"
TextColor="White"
TextChanged="OnTextColorEntryTextChanged"
Style="{StaticResource SettingsEntryStyle}"/>
</StackLayout>
</StackLayout>
<controls:Captcha
Grid.Row="3"
x:Name="Captcha"/>
</Grid>
</ContentPage>
42 changes: 42 additions & 0 deletions src/AlohaKit.Gallery/Views/CaptchaView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace AlohaKit.Gallery.Views;

public partial class CaptchaView : ContentPage
{
public CaptchaView()
{
InitializeComponent();
UpdateColors();
}

void OnTextColorEntryTextChanged(object sender, TextChangedEventArgs e)
{
UpdateColors();
}

void UpdateColors()
{
var textColor = GetColorFromString(TextColorEntry.Text);

if (textColor != null)
{
TextColorEntry.BackgroundColor = textColor;

Captcha.TextColor = textColor;
}
}

Color GetColorFromString(string value)
{
if (string.IsNullOrEmpty(value))
return null;

try
{
return Color.FromArgb(value[0].Equals('#') ? value : $"#{value}");
}
catch (Exception)
{
return null;
}
}
}
110 changes: 110 additions & 0 deletions src/AlohaKit/Controls/Captcha/Captcha.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
namespace AlohaKit.Controls
{
public class Captcha : GraphicsView
{
public Captcha()
{
HeightRequest = 50;
WidthRequest = 150;

Drawable = CaptchaDrawable = new CaptchaDrawable();
}

public CaptchaDrawable CaptchaDrawable { get; set; }

public static readonly BindableProperty LevelProperty =
BindableProperty.Create(nameof(Level), typeof(CaptchaLevel), typeof(Captcha), CaptchaLevel.Normal,
propertyChanged: (bindableObject, oldValue, newValue) =>
{
if (newValue != null && bindableObject is Captcha captcha)
{
captcha.UpdateLevel();
}
});

public CaptchaLevel Level
{
get => (CaptchaLevel)GetValue(LevelProperty);
set => SetValue(LevelProperty, value);
}

public static readonly BindableProperty TextColorProperty =
BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(Captcha), Colors.Black,
propertyChanged: (bindableObject, oldValue, newValue) =>
{
if (newValue != null && bindableObject is Captcha captcha)
{
captcha.UpdateTextColor();
}
});

public Color TextColor
{
get => (Color)GetValue(TextColorProperty);
set => SetValue(TextColorProperty, value);
}

protected override void OnParentChanged()
{
base.OnParentChanged();

if (Parent != null)
{
UpdateLevel();
UpdateTextColor();
}
}

void UpdateLevel()
{
if (CaptchaDrawable == null)
return;

if (CaptchaDrawable.Level != Level)
{
CaptchaDrawable.Level = Level;

var word = GenerateRandomWord(GetWordLength(Level));
CaptchaDrawable.Word = word;

Invalidate();
}
}

void UpdateTextColor()
{
if (CaptchaDrawable == null)
return;

CaptchaDrawable.TextColor = TextColor;

Invalidate();
}

string GenerateRandomWord(int length)
{
var random = new Random();

const string chars = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"0123456789";

return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}

int GetWordLength(CaptchaLevel level)
{
switch (level)
{
case CaptchaLevel.Weak:
return 4;
default:
case CaptchaLevel.Normal:
return 6;
case CaptchaLevel.Strong:
return 8;
}
}
}
}
102 changes: 102 additions & 0 deletions src/AlohaKit/Controls/Captcha/CaptchaDrawable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
namespace AlohaKit.Controls
{
public class CaptchaDrawable : IDrawable
{
public string Word { get; set; }
public CaptchaLevel Level { get; set; }
public Color TextColor { get; set; }

public void Draw(ICanvas canvas, RectF dirtyRect)
{
DrawText(canvas, dirtyRect);
DrawArtifacts(canvas, dirtyRect);
}

void DrawText(ICanvas canvas, RectF dirtyRect)
{
canvas.SaveState();

var height = dirtyRect.Height;
var width = dirtyRect.Width;

int minLetterDistanceY = 0;
int maxLetterDistanceY = (int)height / Word.Length;

int minLetterDistanceX = 6;
int maxLetterDistanceX = (int)width / Word.Length;

var coordRandom = new Random();

var letterPoint = new PointF(coordRandom.Next(minLetterDistanceY, maxLetterDistanceX), height / 2);

foreach (var l in Word)
{
letterPoint.Y += coordRandom.Next(0, maxLetterDistanceY);

canvas.FontSize = 24;
canvas.FontColor = TextColor;

canvas.DrawString(l.ToString(), letterPoint.X, letterPoint.Y,HorizontalAlignment.Center);

letterPoint.X += coordRandom.Next(minLetterDistanceX, maxLetterDistanceX);
}

canvas.RestoreState();
}

void DrawArtifacts(ICanvas canvas, RectF dirtyRect)
{
canvas.SaveState();

var randomLines = new Random();

for (var i = 0; i < GetArtifactLength(Level); i++)
{
var x1 = randomLines.Next(0, (int)dirtyRect.Width);
var y1 = randomLines.Next(0, (int)dirtyRect.Height);
var x2 = randomLines.Next(0, (int)dirtyRect.Width);
var y2 = randomLines.Next(0, (int)dirtyRect.Height);

canvas.StrokeColor = TextColor.WithAlpha(0.8f);

var randomStrokeSize = new Random();
canvas.StrokeSize = randomStrokeSize.Next(1, GetArtifactWidth(Level));

var p1 = new PointF(x1, y1);
var p2 = new PointF(x2, y2);

canvas.DrawLine(p1, p2);
}

canvas.RestoreState();
}

int GetArtifactLength(CaptchaLevel level)
{
switch (level)
{
case CaptchaLevel.Weak:
return 4;
default:
case CaptchaLevel.Normal:
return 6;
case CaptchaLevel.Strong:
return 8;
}
}

int GetArtifactWidth(CaptchaLevel level)
{
switch (level)
{
case CaptchaLevel.Weak:
return 2;
default:
case CaptchaLevel.Normal:
return 3;
case CaptchaLevel.Strong:
return 4;
}
}
}
}
9 changes: 9 additions & 0 deletions src/AlohaKit/Controls/Captcha/CaptchaLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace AlohaKit.Controls
{
public enum CaptchaLevel
{
Weak,
Normal,
Strong
}
}