feature: supports get avatar from avatars.githubusercontent.com
* move all images to `src/Resources/Images` folder
|
@ -1,21 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public interface IAvatarHost
|
||||
{
|
||||
void OnAvatarResourceChanged(string md5);
|
||||
void OnAvatarResourceChanged(string email);
|
||||
}
|
||||
|
||||
public static class AvatarManager
|
||||
public static partial class AvatarManager
|
||||
{
|
||||
public static string SelectedServer
|
||||
{
|
||||
|
@ -33,29 +38,35 @@ namespace SourceGit.Models
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
var md5 = null as string;
|
||||
var email = null as string;
|
||||
|
||||
lock (_synclock)
|
||||
{
|
||||
foreach (var one in _requesting)
|
||||
{
|
||||
md5 = one;
|
||||
email = one;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (md5 == null)
|
||||
if (email == null)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
var md5 = GetEmailHash(email);
|
||||
var matchGithubUser = REG_GITHUB_USER_EMAIL().Match(email);
|
||||
var url = matchGithubUser.Success ?
|
||||
$"https://avatars.githubusercontent.com/{matchGithubUser.Groups[2].Value}" :
|
||||
$"{SelectedServer}{md5}?d=404";
|
||||
|
||||
var localFile = Path.Combine(_storePath, md5);
|
||||
var img = null as Bitmap;
|
||||
try
|
||||
{
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) };
|
||||
var task = client.GetAsync($"{SelectedServer}{md5}?d=404");
|
||||
var task = client.GetAsync(url);
|
||||
task.Wait();
|
||||
|
||||
var rsp = task.Result;
|
||||
|
@ -82,13 +93,13 @@ namespace SourceGit.Models
|
|||
|
||||
lock (_synclock)
|
||||
{
|
||||
_requesting.Remove(md5);
|
||||
_requesting.Remove(email);
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_resources[md5] = img;
|
||||
NotifyResourceChanged(md5);
|
||||
_resources[email] = img;
|
||||
NotifyResourceChanged(email);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -104,25 +115,36 @@ namespace SourceGit.Models
|
|||
_avatars.Remove(host);
|
||||
}
|
||||
|
||||
public static Bitmap Request(string md5, bool forceRefetch = false)
|
||||
public static Bitmap Request(string email, bool forceRefetch)
|
||||
{
|
||||
if (email.Equals("noreply@github.com", StringComparison.Ordinal))
|
||||
{
|
||||
if (_githubEmailAvatar == null)
|
||||
{
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute));
|
||||
_githubEmailAvatar = new Bitmap(icon);
|
||||
}
|
||||
|
||||
return _githubEmailAvatar;
|
||||
}
|
||||
|
||||
if (forceRefetch)
|
||||
{
|
||||
if (_resources.ContainsKey(md5))
|
||||
_resources.Remove(md5);
|
||||
if (_resources.ContainsKey(email))
|
||||
_resources.Remove(email);
|
||||
|
||||
var localFile = Path.Combine(_storePath, md5);
|
||||
var localFile = Path.Combine(_storePath, GetEmailHash(email));
|
||||
if (File.Exists(localFile))
|
||||
File.Delete(localFile);
|
||||
|
||||
NotifyResourceChanged(md5);
|
||||
NotifyResourceChanged(email);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_resources.TryGetValue(md5, out var value))
|
||||
if (_resources.TryGetValue(email, out var value))
|
||||
return value;
|
||||
|
||||
var localFile = Path.Combine(_storePath, md5);
|
||||
var localFile = Path.Combine(_storePath, GetEmailHash(email));
|
||||
if (File.Exists(localFile))
|
||||
{
|
||||
try
|
||||
|
@ -130,7 +152,7 @@ namespace SourceGit.Models
|
|||
using (var stream = File.OpenRead(localFile))
|
||||
{
|
||||
var img = Bitmap.DecodeToWidth(stream, 128);
|
||||
_resources.Add(md5, img);
|
||||
_resources.Add(email, img);
|
||||
return img;
|
||||
}
|
||||
}
|
||||
|
@ -143,18 +165,28 @@ namespace SourceGit.Models
|
|||
|
||||
lock (_synclock)
|
||||
{
|
||||
if (!_requesting.Contains(md5))
|
||||
_requesting.Add(md5);
|
||||
if (!_requesting.Contains(email))
|
||||
_requesting.Add(email);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void NotifyResourceChanged(string md5)
|
||||
private static string GetEmailHash(string email)
|
||||
{
|
||||
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
|
||||
var builder = new StringBuilder();
|
||||
foreach (var c in hash)
|
||||
builder.Append(c.ToString("x2"));
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void NotifyResourceChanged(string email)
|
||||
{
|
||||
foreach (var avatar in _avatars)
|
||||
{
|
||||
avatar.OnAvatarResourceChanged(md5);
|
||||
avatar.OnAvatarResourceChanged(email);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,5 +195,9 @@ namespace SourceGit.Models
|
|||
private static readonly List<IAvatarHost> _avatars = new List<IAvatarHost>();
|
||||
private static readonly Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
|
||||
private static readonly HashSet<string> _requesting = new HashSet<string>();
|
||||
|
||||
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
|
||||
private static partial Regex REG_GITHUB_USER_EMAIL();
|
||||
private static Bitmap _githubEmailAvatar = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
get
|
||||
{
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute));
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute));
|
||||
return new Bitmap(icon);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace SourceGit.Models
|
|||
|
||||
try
|
||||
{
|
||||
var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{icon}.png",
|
||||
var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/ExternalToolIcons/{icon}.png",
|
||||
UriKind.RelativeOrAbsolute));
|
||||
IconImage = new Bitmap(asset);
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
src/Resources/Images/github.png
Normal file
After Width: | Height: | Size: 4 KiB |
|
@ -29,10 +29,11 @@
|
|||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="App.ico" />
|
||||
<AvaloniaResource Include="Resources/ExternalToolIcons/*" />
|
||||
<AvaloniaResource Include="Resources/ExternalToolIcons/JetBrains/*" />
|
||||
<AvaloniaResource Include="Resources/Fonts/*" />
|
||||
<AvaloniaResource Include="Resources/ShellIcons/*" />
|
||||
<AvaloniaResource Include="Resources/Images/*" />
|
||||
<AvaloniaResource Include="Resources/Images/ExternalToolIcons/*" />
|
||||
<AvaloniaResource Include="Resources/Images/ExternalToolIcons/JetBrains/*" />
|
||||
<AvaloniaResource Include="Resources/Images/ShellIcons/*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
@ -42,7 +40,7 @@ namespace SourceGit.Views
|
|||
{
|
||||
if (User != null)
|
||||
{
|
||||
Models.AvatarManager.Request(_emailMD5, true);
|
||||
Models.AvatarManager.Request(User.Email, true);
|
||||
InvalidateVisual();
|
||||
}
|
||||
};
|
||||
|
@ -59,7 +57,7 @@ namespace SourceGit.Views
|
|||
return;
|
||||
|
||||
var corner = (float)Math.Max(2, Bounds.Width / 16);
|
||||
var img = Models.AvatarManager.Request(_emailMD5);
|
||||
var img = Models.AvatarManager.Request(User.Email, false);
|
||||
if (img != null)
|
||||
{
|
||||
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
|
||||
|
@ -74,9 +72,9 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
public void OnAvatarResourceChanged(string md5)
|
||||
public void OnAvatarResourceChanged(string email)
|
||||
{
|
||||
if (_emailMD5 == md5)
|
||||
if (User.Email.Equals(email, StringComparison.Ordinal))
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
@ -97,10 +95,7 @@ namespace SourceGit.Views
|
|||
private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (avatar.User == null)
|
||||
{
|
||||
avatar._emailMD5 = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = string.IsNullOrWhiteSpace(avatar.User.Name) ? "?" : avatar.User.Name.Substring(0, 1);
|
||||
var chars = placeholder.ToCharArray();
|
||||
|
@ -108,15 +103,6 @@ namespace SourceGit.Views
|
|||
foreach (var c in chars)
|
||||
sum += Math.Abs(c);
|
||||
|
||||
var lowered = avatar.User.Email.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
|
||||
var builder = new StringBuilder();
|
||||
foreach (var c in hash)
|
||||
builder.Append(c.ToString("x2"));
|
||||
var md5 = builder.ToString();
|
||||
if (avatar._emailMD5 == null || avatar._emailMD5 != md5)
|
||||
avatar._emailMD5 = md5;
|
||||
|
||||
avatar._fallbackBrush = new LinearGradientBrush
|
||||
{
|
||||
GradientStops = FALLBACK_GRADIENTS[sum % FALLBACK_GRADIENTS.Length],
|
||||
|
@ -139,6 +125,5 @@ namespace SourceGit.Views
|
|||
|
||||
private FormattedText _fallbackLabel = null;
|
||||
private LinearGradientBrush _fallbackBrush = null;
|
||||
private string _emailMD5 = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,22 +294,22 @@
|
|||
IsVisible="{OnPlatform False, Windows=True}">
|
||||
<ComboBox.Items>
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/git-bash.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/Images/ShellIcons/git-bash.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<TextBlock Grid.Column="1" Text="Git Bash" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/pwsh.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/Images/ShellIcons/pwsh.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<TextBlock Grid.Column="1" Text="PowerShell" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/cmd.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/Images/ShellIcons/cmd.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<TextBlock Grid.Column="1" Text="Command Prompt" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/wt.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/Images/ShellIcons/wt.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
<TextBlock Grid.Column="1" Text="Default Shell in Windows Terminal" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
</ComboBox.Items>
|
||||
|
|