Skip to content

Commit 86f27c5

Browse files
committed
refactor: generate hash based default avatar instead of simple label (#1322)
Signed-off-by: leo <[email protected]>
1 parent 1f0ab2b commit 86f27c5

File tree

1 file changed

+67
-64
lines changed

1 file changed

+67
-64
lines changed

src/Views/Avatar.cs

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Globalization;
3+
using System.Security.Cryptography;
4+
using System.Text;
45

56
using Avalonia;
67
using Avalonia.Controls;
@@ -12,14 +13,6 @@ namespace SourceGit.Views
1213
{
1314
public class Avatar : Control, Models.IAvatarHost
1415
{
15-
private static readonly GradientStops[] FALLBACK_GRADIENTS = [
16-
new GradientStops() { new GradientStop(Colors.Orange, 0), new GradientStop(Color.FromRgb(255, 213, 134), 1) },
17-
new GradientStops() { new GradientStop(Colors.DodgerBlue, 0), new GradientStop(Colors.LightSkyBlue, 1) },
18-
new GradientStops() { new GradientStop(Colors.LimeGreen, 0), new GradientStop(Color.FromRgb(124, 241, 124), 1) },
19-
new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) },
20-
new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) },
21-
];
22-
2316
public static readonly StyledProperty<Models.User> UserProperty =
2417
AvaloniaProperty.Register<Avatar, Models.User>(nameof(User));
2518

@@ -29,11 +22,6 @@ public Models.User User
2922
set => SetValue(UserProperty, value);
3023
}
3124

32-
static Avatar()
33-
{
34-
UserProperty.Changed.AddClassHandler<Avatar>(OnUserPropertyChanged);
35-
}
36-
3725
public Avatar()
3826
{
3927
var refetch = new MenuItem() { Header = App.Text("RefetchAvatar") };
@@ -55,25 +43,72 @@ public override void Render(DrawingContext context)
5543
return;
5644

5745
var corner = (float)Math.Max(2, Bounds.Width / 16);
58-
var img = Models.AvatarManager.Instance.Request(User.Email, false);
59-
if (img != null)
46+
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
47+
var clip = context.PushClip(new RoundedRect(rect, corner));
48+
49+
if (_img != null)
6050
{
61-
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
62-
context.PushClip(new RoundedRect(rect, corner));
63-
context.DrawImage(img, rect);
51+
context.DrawImage(_img, rect);
6452
}
6553
else
6654
{
67-
Point textOrigin = new Point((Bounds.Width - _fallbackLabel.Width) * 0.5, (Bounds.Height - _fallbackLabel.Height) * 0.5);
68-
context.DrawRectangle(_fallbackBrush, null, new Rect(0, 0, Bounds.Width, Bounds.Height), corner, corner);
69-
context.DrawText(_fallbackLabel, textOrigin);
55+
context.DrawRectangle(Brushes.White, new Pen(new SolidColorBrush(Colors.Black, 0.3f), 0.65f), rect, corner, corner);
56+
57+
var offsetX = Bounds.Width / 8.0;
58+
var offsetY = Bounds.Height / 8.0;
59+
60+
var stepX = (Bounds.Width - offsetX * 2) / 5.0;
61+
var stepY = (Bounds.Height - offsetY * 2) / 5.0;
62+
63+
var user = User;
64+
var lowered = user.Email.ToLower(CultureInfo.CurrentCulture).Trim();
65+
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered));
66+
67+
var brush = new SolidColorBrush(new Color(255, hash[0], hash[1], hash[2]));
68+
var switches = new bool[15];
69+
for (int i = 0; i < switches.Length; i++)
70+
switches[i] = hash[i + 1] % 2 == 1;
71+
72+
for (int row = 0; row < 5; row++)
73+
{
74+
var x = offsetX + stepX * 2;
75+
var y = offsetY + stepY * row;
76+
var idx = row * 3;
77+
78+
if (switches[idx])
79+
context.FillRectangle(brush, new Rect(x, y, stepX, stepY));
80+
81+
if (switches[idx+1])
82+
context.FillRectangle(brush, new Rect(x + stepX, y, stepX, stepY));
83+
84+
if (switches[idx+2])
85+
context.FillRectangle(brush, new Rect(x + stepX * 2, y, stepX, stepY));
86+
}
87+
88+
for (int row = 0; row < 5; row++)
89+
{
90+
var x = offsetX;
91+
var y = offsetY + stepY * row;
92+
var idx = row * 3 + 2;
93+
94+
if (switches[idx])
95+
context.FillRectangle(brush, new Rect(x, y, stepX, stepY));
96+
97+
if (switches[idx-1])
98+
context.FillRectangle(brush, new Rect(x + stepX, y, stepX, stepY));
99+
}
70100
}
101+
102+
clip.Dispose();
71103
}
72104

73105
public void OnAvatarResourceChanged(string email)
74106
{
75107
if (User.Email.Equals(email, StringComparison.Ordinal))
108+
{
109+
_img = Models.AvatarManager.Instance.Request(User.Email, false);
76110
InvalidateVisual();
111+
}
77112
}
78113

79114
protected override void OnLoaded(RoutedEventArgs e)
@@ -88,53 +123,21 @@ protected override void OnUnloaded(RoutedEventArgs e)
88123
Models.AvatarManager.Instance.Unsubscribe(this);
89124
}
90125

91-
private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e)
126+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
92127
{
93-
if (avatar.User == null)
94-
return;
128+
base.OnPropertyChanged(change);
95129

96-
var fallback = GetFallbackString(avatar.User.Name);
97-
var chars = fallback.ToCharArray();
98-
var sum = 0;
99-
foreach (var c in chars)
100-
sum += Math.Abs(c);
101-
102-
avatar._fallbackBrush = new LinearGradientBrush
130+
if (change.Property == UserProperty)
103131
{
104-
GradientStops = FALLBACK_GRADIENTS[sum % FALLBACK_GRADIENTS.Length],
105-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
106-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
107-
};
132+
var user = User;
133+
if (user == null)
134+
return;
108135

109-
var typeface = new Typeface("fonts:SourceGit#JetBrains Mono");
110-
avatar._fallbackLabel = new FormattedText(
111-
fallback,
112-
CultureInfo.CurrentCulture,
113-
FlowDirection.LeftToRight,
114-
typeface,
115-
avatar.Width * 0.65,
116-
Brushes.White);
117-
118-
avatar.InvalidateVisual();
119-
}
120-
121-
private static string GetFallbackString(string name)
122-
{
123-
if (string.IsNullOrWhiteSpace(name))
124-
return "?";
125-
126-
var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
127-
var chars = new List<char>();
128-
foreach (var part in parts)
129-
chars.Add(part[0]);
130-
131-
if (chars.Count >= 2 && char.IsAsciiLetterOrDigit(chars[0]) && char.IsAsciiLetterOrDigit(chars[^1]))
132-
return string.Format("{0}{1}", chars[0], chars[^1]);
133-
134-
return name.Substring(0, 1);
136+
_img = Models.AvatarManager.Instance.Request(User.Email, false);
137+
InvalidateVisual();
138+
}
135139
}
136140

137-
private FormattedText _fallbackLabel = null;
138-
private LinearGradientBrush _fallbackBrush = null;
141+
private Bitmap _img = null;
139142
}
140143
}

0 commit comments

Comments
 (0)