Skip to content

Commit

Permalink
Created captcha control
Browse files Browse the repository at this point in the history
  • Loading branch information
jsuarezruiz committed Apr 7, 2023
1 parent 3a5eb0e commit 333fc7c
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 0 deletions.
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
}
}

0 comments on commit 333fc7c

Please sign in to comment.