From 5d6a7ba4df7c6bd12597b8dbdbf9a26f12330070 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 31 Mar 2021 19:47:37 +0800 Subject: [PATCH] feature: show avatars in histories --- src/Helpers/Avatar.cs | 185 ++++++++++++++++++++++++++++++++++++ src/UI/CommitViewer.xaml | 12 +-- src/UI/CommitViewer.xaml.cs | 69 ++------------ src/UI/Histories.xaml | 12 ++- 4 files changed, 204 insertions(+), 74 deletions(-) create mode 100644 src/Helpers/Avatar.cs diff --git a/src/Helpers/Avatar.cs b/src/Helpers/Avatar.cs new file mode 100644 index 00000000..2d7026ea --- /dev/null +++ b/src/Helpers/Avatar.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace SourceGit.Helpers { + + /// + /// Avatar control + /// + public class Avatar : Image { + + /// + /// Colors used in avatar + /// + public static Brush[] Colors = new Brush[] { + Brushes.DarkBlue, + Brushes.DarkCyan, + Brushes.DarkGoldenrod, + Brushes.DarkGray, + Brushes.DarkGreen, + Brushes.DarkKhaki, + Brushes.DarkMagenta, + Brushes.DarkOliveGreen, + Brushes.DarkOrange, + Brushes.DarkOrchid, + Brushes.DarkRed, + Brushes.DarkSalmon, + Brushes.DarkSeaGreen, + Brushes.DarkSlateBlue, + Brushes.DarkSlateGray, + Brushes.DarkTurquoise, + Brushes.DarkViolet + }; + + /// + /// User property definition. + /// + public static readonly DependencyProperty UserProperty = DependencyProperty.Register( + "User", + typeof(Git.User), + typeof(Avatar), + new PropertyMetadata(null, OnUserChanged)); + + /// + /// User property + /// + public Git.User User { + get { return (Git.User)GetValue(UserProperty); } + set { SetValue(UserProperty, value); } + } + + /// + /// Loading request + /// + private class Request { + public BitmapImage img = null; + public List targets = new List(); + } + + /// + /// Path to cache downloaded avatars + /// + private static readonly string CACHE_PATH = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "SourceGit", + "avatars"); + + /// + /// Current requests. + /// + private static Dictionary requesting = new Dictionary(); + + /// + /// Render implementation. + /// + /// + protected override void OnRender(DrawingContext dc) { + base.OnRender(dc); + + if (Source == null && User != null) { + var placeholder = User.Name.Length > 0 ? User.Name.Substring(0, 1) : "?"; + var formatted = new FormattedText( + placeholder, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), + Width * 0.75, + Brushes.White, + VisualTreeHelper.GetDpi(this).PixelsPerDip); + + double offsetX = 0; + if (HorizontalAlignment == HorizontalAlignment.Right) { + offsetX = -Width * 0.5; + } + + var chars = placeholder.ToCharArray(); + var sum = 0; + foreach (var ch in chars) sum += Math.Abs(ch); + var brush = Colors[sum % Colors.Length]; + + dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), Width / 16, Height / 16); + dc.DrawText(formatted, new Point(formatted.Width * -0.5 + offsetX, formatted.Height * -0.5)); + } + } + + /// + /// Reset image. + /// + private void ReloadImage(Git.User oldUser) { + if (oldUser != null && requesting.ContainsKey(oldUser.Email)) { + if (requesting[oldUser.Email].targets.Count <= 1) { + requesting.Remove(oldUser.Email); + } else { + requesting[oldUser.Email].targets.Remove(this); + } + } + + Source = null; + InvalidateVisual(); + + if (User == null) return; + + var email = User.Email; + if (requesting.ContainsKey(email)) { + requesting[email].targets.Add(this); + return; + } + + byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim())); + string md5 = ""; + for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2"); + md5 = md5.ToLower(); + + string filePath = Path.Combine(CACHE_PATH, md5); + if (File.Exists(filePath)) { + Source = new BitmapImage(new Uri(filePath)); + return; + } + + requesting.Add(email, new Request()); + requesting[email].targets.Add(this); + + BitmapImage downloading = new BitmapImage(new Uri("https://www.gravatar.com/avatar/" + md5 + "?d=404")); + requesting[email].img = downloading; + downloading.DownloadCompleted += (o, e) => { + var owner = o as BitmapImage; + if (owner != null) { + if (!Directory.Exists(CACHE_PATH)) Directory.CreateDirectory(CACHE_PATH); + + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(owner)); + using (var fs = new FileStream(filePath, FileMode.Create)) { + encoder.Save(fs); + } + + if (requesting.ContainsKey(email)) { + BitmapImage exists = new BitmapImage(new Uri(filePath)); + foreach (var one in requesting[email].targets) one.Source = exists; + requesting.Remove(email); + } + } + }; + downloading.DownloadFailed += (o, e) => { + requesting.Remove(email); + }; + } + + /// + /// Callback on user property changed + /// + /// + /// + private static void OnUserChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + Avatar a = d as Avatar; + if (a != null) a.ReloadImage(e.OldValue as Git.User); + } + } +} diff --git a/src/UI/CommitViewer.xaml b/src/UI/CommitViewer.xaml index bc4db91a..e5104435 100644 --- a/src/UI/CommitViewer.xaml +++ b/src/UI/CommitViewer.xaml @@ -62,11 +62,7 @@ - - - - - + @@ -89,11 +85,7 @@ - - - - - + diff --git a/src/UI/CommitViewer.xaml.cs b/src/UI/CommitViewer.xaml.cs index 930c65b0..9e72bdc2 100644 --- a/src/UI/CommitViewer.xaml.cs +++ b/src/UI/CommitViewer.xaml.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; -using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Windows; @@ -20,11 +19,6 @@ namespace SourceGit.UI { /// Commit detail viewer /// public partial class CommitViewer : UserControl { - private static readonly string AVATAR_PATH = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "SourceGit", - "avatars"); - private Git.Repository repo = null; private Git.Commit commit = null; private List cachedChanges = new List(); @@ -104,31 +98,17 @@ namespace SourceGit.UI { authorName.Text = commit.Author.Name; authorEmail.Text = commit.Author.Email; authorTime.Text = commit.Author.Time; + authorAvatar.User = commit.Author; - if (commit.Committer.Email == commit.Author.Email) { - if (commit.Committer.Time == commit.Author.Time) { - committerPanel.Visibility = Visibility.Hidden; + committerName.Text = commit.Committer.Name; + committerEmail.Text = commit.Committer.Email; + committerTime.Text = commit.Committer.Time; + committerAvatar.User = commit.Committer; - SetAvatar(authorAvatar, authorAvatarMask, commit.Author.Email); - } else { - committerPanel.Visibility = Visibility.Visible; - - committerName.Text = commit.Committer.Name; - committerEmail.Text = commit.Committer.Email; - committerTime.Text = commit.Committer.Time; - - SetAvatar(authorAvatar, authorAvatarMask, commit.Author.Email); - SetAvatar(committerAvatar, committerAvatarMask, commit.Committer.Email, false); - } + if (commit.Committer.Email == commit.Author.Email && commit.Committer.Time == commit.Author.Time) { + committerPanel.Visibility = Visibility.Hidden; } else { committerPanel.Visibility = Visibility.Visible; - - committerName.Text = commit.Committer.Name; - committerEmail.Text = commit.Committer.Email; - committerTime.Text = commit.Committer.Time; - - SetAvatar(authorAvatar, authorAvatarMask, commit.Author.Email); - SetAvatar(committerAvatar, committerAvatarMask, commit.Committer.Email); } if (commit.Decorators.Count == 0) { @@ -138,41 +118,6 @@ namespace SourceGit.UI { } } - private void SetAvatar(Image img, Border mask, string email, bool save = true) { - byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim())); - string md5 = ""; - for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2"); - md5 = md5.ToLower(); - - if (!Directory.Exists(AVATAR_PATH)) Directory.CreateDirectory(AVATAR_PATH); - - mask.Visibility = Visibility.Visible; - var sha = commit.SHA; - - string filePath = Path.Combine(AVATAR_PATH, md5); - if (File.Exists(filePath)) { - img.Source = new BitmapImage(new Uri(filePath)); - mask.Visibility = Visibility.Hidden; - } else { - var bitmap = new BitmapImage(new Uri("https://www.gravatar.com/avatar/" + md5 + "?d=404")); - if (save) { - bitmap.DownloadCompleted += (o, e) => { - var owner = o as BitmapImage; - if (owner != null) { - var encoder = new PngBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(owner)); - using (var fs = new FileStream(filePath, FileMode.Create)) { - encoder.Save(fs); - } - } - - if (commit.SHA == sha) mask.Visibility = Visibility.Hidden; - }; - } - img.Source = bitmap; - } - } - private void NavigateParent(object sender, RequestNavigateEventArgs e) { repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString); e.Handled = true; diff --git a/src/UI/Histories.xaml b/src/UI/Histories.xaml index 08756556..9ec1781e 100644 --- a/src/UI/Histories.xaml +++ b/src/UI/Histories.xaml @@ -136,7 +136,15 @@ - + + + + + + + + + @@ -154,7 +162,7 @@ - +