diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index 2228727f..90eff69a 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -1,186 +1,178 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
-
- ///
- /// 用于取消命令执行的上下文对象
- ///
- public class Context {
- public bool IsCancelRequested { get; set; } = false;
- }
-
- ///
- /// 命令接口
- ///
- public class Command {
-
- ///
- /// 读取全部输出时的结果
- ///
- public class ReadToEndResult {
- public bool IsSuccess { get; set; }
- public string Output { get; set; }
- public string Error { get; set; }
- }
-
- ///
- /// 上下文
- ///
- public Context Ctx { get; set; } = null;
-
- ///
- /// 运行路径
- ///
- public string Cwd { get; set; } = "";
-
- ///
- /// 参数
- ///
- public string Args { get; set; } = "";
-
- ///
- /// 是否忽略错误
- ///
- public bool DontRaiseError { get; set; } = false;
-
- ///
- /// 使用标准错误输出
- ///
- public bool TraitErrorAsOutput { get; set; } = false;
-
- ///
- /// 用于设置该进程独有的环境变量
- ///
- public Dictionary Envs { get; set; } = new Dictionary();
-
- ///
- /// 运行
- ///
- public bool Exec() {
- var start = new ProcessStartInfo();
- start.FileName = Models.Preference.Instance.Git.Path;
- start.Arguments = "--no-pager -c core.quotepath=off " + Args;
- start.UseShellExecute = false;
- start.CreateNoWindow = true;
- start.RedirectStandardOutput = true;
- start.RedirectStandardError = true;
- start.StandardOutputEncoding = Encoding.UTF8;
- start.StandardErrorEncoding = Encoding.UTF8;
-
- if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
-
- foreach (var kv in Envs) start.EnvironmentVariables[kv.Key] = kv.Value;
-
- var progressFilter = new Regex(@"\s\d+%\s");
- var errs = new List();
- var proc = new Process() { StartInfo = start };
- var isCancelled = false;
-
- proc.OutputDataReceived += (o, e) => {
- if (Ctx != null && Ctx.IsCancelRequested) {
- isCancelled = true;
- proc.CancelErrorRead();
- proc.CancelOutputRead();
-#if NET48
- if (!proc.HasExited) proc.Kill();
-#else
- if (!proc.HasExited) proc.Kill(true);
-#endif
- return;
- }
-
- if (e.Data == null) return;
- OnReadline(e.Data);
- };
- proc.ErrorDataReceived += (o, e) => {
- if (Ctx != null && Ctx.IsCancelRequested) {
- isCancelled = true;
- proc.CancelErrorRead();
- proc.CancelOutputRead();
-#if NET48
- if (!proc.HasExited) proc.Kill();
-#else
- if (!proc.HasExited) proc.Kill(true);
-#endif
- return;
- }
-
- if (string.IsNullOrEmpty(e.Data)) return;
- if (TraitErrorAsOutput) OnReadline(e.Data);
-
- if (progressFilter.IsMatch(e.Data)) return;
- if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return;
- errs.Add(e.Data);
- };
-
- try {
- proc.Start();
- } catch (Exception e) {
- if (!DontRaiseError) Models.Exception.Raise(e.Message);
- return false;
- }
-
- proc.BeginOutputReadLine();
- proc.BeginErrorReadLine();
- proc.WaitForExit();
-
- int exitCode = proc.ExitCode;
- proc.Close();
-
- if (!isCancelled && exitCode != 0 && errs.Count > 0) {
- if (!DontRaiseError) Models.Exception.Raise(string.Join("\n", errs));
- return false;
- } else {
- return true;
- }
- }
-
- ///
- /// 直接读取全部标准输出
- ///
- public ReadToEndResult ReadToEnd() {
- var start = new ProcessStartInfo();
- start.FileName = Models.Preference.Instance.Git.Path;
- start.Arguments = "--no-pager -c core.quotepath=off " + Args;
- start.UseShellExecute = false;
- start.CreateNoWindow = true;
- start.RedirectStandardOutput = true;
- start.RedirectStandardError = true;
- start.StandardOutputEncoding = Encoding.UTF8;
- start.StandardErrorEncoding = Encoding.UTF8;
-
- if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
-
- var proc = new Process() { StartInfo = start };
- try {
- proc.Start();
- } catch (Exception e) {
- return new ReadToEndResult() {
- Output = "",
- Error = e.Message,
- IsSuccess = false,
- };
- }
-
- var rs = new ReadToEndResult();
- rs.Output = proc.StandardOutput.ReadToEnd();
- rs.Error = proc.StandardError.ReadToEnd();
-
- proc.WaitForExit();
- rs.IsSuccess = proc.ExitCode == 0;
- proc.Close();
-
- return rs;
- }
-
- ///
- /// 调用Exec时的读取函数
- ///
- ///
- public virtual void OnReadline(string line) { }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+
+ ///
+ /// 用于取消命令执行的上下文对象
+ ///
+ public class Context {
+ public bool IsCancelRequested { get; set; } = false;
+ }
+
+ ///
+ /// 命令接口
+ ///
+ public class Command {
+
+ ///
+ /// 读取全部输出时的结果
+ ///
+ public class ReadToEndResult {
+ public bool IsSuccess { get; set; }
+ public string Output { get; set; }
+ public string Error { get; set; }
+ }
+
+ ///
+ /// 上下文
+ ///
+ public Context Ctx { get; set; } = null;
+
+ ///
+ /// 运行路径
+ ///
+ public string Cwd { get; set; } = "";
+
+ ///
+ /// 参数
+ ///
+ public string Args { get; set; } = "";
+
+ ///
+ /// 是否忽略错误
+ ///
+ public bool DontRaiseError { get; set; } = false;
+
+ ///
+ /// 使用标准错误输出
+ ///
+ public bool TraitErrorAsOutput { get; set; } = false;
+
+ ///
+ /// 用于设置该进程独有的环境变量
+ ///
+ public Dictionary Envs { get; set; } = new Dictionary();
+
+ ///
+ /// 运行
+ ///
+ public bool Exec() {
+ var start = new ProcessStartInfo();
+ start.FileName = Models.Preference.Instance.Git.Path;
+ start.Arguments = "--no-pager -c core.quotepath=off " + Args;
+ start.UseShellExecute = false;
+ start.CreateNoWindow = true;
+ start.RedirectStandardOutput = true;
+ start.RedirectStandardError = true;
+ start.StandardOutputEncoding = Encoding.UTF8;
+ start.StandardErrorEncoding = Encoding.UTF8;
+
+ if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
+
+ foreach (var kv in Envs) start.EnvironmentVariables[kv.Key] = kv.Value;
+
+ var progressFilter = new Regex(@"\s\d+%\s");
+ var errs = new List();
+ var proc = new Process() { StartInfo = start };
+ var isCancelled = false;
+
+ proc.OutputDataReceived += (o, e) => {
+ if (Ctx != null && Ctx.IsCancelRequested) {
+ isCancelled = true;
+ proc.CancelErrorRead();
+ proc.CancelOutputRead();
+ if (!proc.HasExited) proc.Kill(true);
+ return;
+ }
+
+ if (e.Data == null) return;
+ OnReadline(e.Data);
+ };
+ proc.ErrorDataReceived += (o, e) => {
+ if (Ctx != null && Ctx.IsCancelRequested) {
+ isCancelled = true;
+ proc.CancelErrorRead();
+ proc.CancelOutputRead();
+ if (!proc.HasExited) proc.Kill(true);
+ return;
+ }
+
+ if (string.IsNullOrEmpty(e.Data)) return;
+ if (TraitErrorAsOutput) OnReadline(e.Data);
+
+ if (progressFilter.IsMatch(e.Data)) return;
+ if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return;
+ errs.Add(e.Data);
+ };
+
+ try {
+ proc.Start();
+ } catch (Exception e) {
+ if (!DontRaiseError) Models.Exception.Raise(e.Message);
+ return false;
+ }
+
+ proc.BeginOutputReadLine();
+ proc.BeginErrorReadLine();
+ proc.WaitForExit();
+
+ int exitCode = proc.ExitCode;
+ proc.Close();
+
+ if (!isCancelled && exitCode != 0 && errs.Count > 0) {
+ if (!DontRaiseError) Models.Exception.Raise(string.Join("\n", errs));
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ ///
+ /// 直接读取全部标准输出
+ ///
+ public ReadToEndResult ReadToEnd() {
+ var start = new ProcessStartInfo();
+ start.FileName = Models.Preference.Instance.Git.Path;
+ start.Arguments = "--no-pager -c core.quotepath=off " + Args;
+ start.UseShellExecute = false;
+ start.CreateNoWindow = true;
+ start.RedirectStandardOutput = true;
+ start.RedirectStandardError = true;
+ start.StandardOutputEncoding = Encoding.UTF8;
+ start.StandardErrorEncoding = Encoding.UTF8;
+
+ if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
+
+ var proc = new Process() { StartInfo = start };
+ try {
+ proc.Start();
+ } catch (Exception e) {
+ return new ReadToEndResult() {
+ Output = "",
+ Error = e.Message,
+ IsSuccess = false,
+ };
+ }
+
+ var rs = new ReadToEndResult();
+ rs.Output = proc.StandardOutput.ReadToEnd();
+ rs.Error = proc.StandardError.ReadToEnd();
+
+ proc.WaitForExit();
+ rs.IsSuccess = proc.ExitCode == 0;
+ proc.Close();
+
+ return rs;
+ }
+
+ ///
+ /// 调用Exec时的读取函数
+ ///
+ ///
+ public virtual void OnReadline(string line) { }
+ }
+}
diff --git a/src/Models/Version.cs b/src/Models/Version.cs
index 07ff3041..a6cf963a 100644
--- a/src/Models/Version.cs
+++ b/src/Models/Version.cs
@@ -1,80 +1,69 @@
-using System;
-using System.Reflection;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-
-#if NET6_0
-using System.Net.Http;
-#else
-using System.Net;
-using System.Text;
-#endif
-
-namespace SourceGit.Models {
-
- ///
- /// Github开放API中Release信息格式
- ///
- public class Version {
- [JsonPropertyName("id")]
- public ulong Id { get; set; }
- [JsonPropertyName("tag_name")]
- public string TagName { get; set; }
- [JsonPropertyName("target_commitish")]
- public string CommitSHA { get; set; }
- [JsonPropertyName("prerelease")]
- public bool PreRelease { get; set; }
- [JsonPropertyName("name")]
- public string Name { get; set; }
- [JsonPropertyName("body")]
- public string Body { get; set; }
- [JsonPropertyName("created_at")]
- public DateTime CreatedAt { get; set; }
-
- public string PublishTime {
- get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); }
- }
-
- public string IsPrerelease {
- get { return PreRelease ? "YES" : "NO"; }
- }
-
- public static void Check(Action onUpgradable) {
- if (!Preference.Instance.General.CheckForUpdate) return;
-
- var curDayOfYear = DateTime.Now.DayOfYear;
- var lastDayOfYear = Preference.Instance.General.LastCheckDay;
- if (lastDayOfYear != curDayOfYear) {
- Preference.Instance.General.LastCheckDay = curDayOfYear;
- Task.Run(async () => {
- try {
-#if NET6_0
- var req = new HttpClient();
- var rsp = await req.GetAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest");
- rsp.EnsureSuccessStatusCode();
-
- var raw = await rsp.Content.ReadAsStringAsync();
-#else
- var web = new WebClient() { Encoding = Encoding.UTF8 };
- var raw = await web.DownloadStringTaskAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest");
-#endif
- var ver = JsonSerializer.Deserialize(raw);
- var cur = Assembly.GetExecutingAssembly().GetName().Version;
-
- var matches = Regex.Match(ver.TagName, @"^v(\d+)\.(\d+).*");
- if (!matches.Success) return;
-
- var major = int.Parse(matches.Groups[1].Value);
- var minor = int.Parse(matches.Groups[2].Value);
- if (major > cur.Major || (major == cur.Major && minor > cur.Minor)) {
- onUpgradable?.Invoke(ver);
- }
- } catch {
- }
- });
- }
- }
- }
-}
+using System;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Net.Http;
+
+namespace SourceGit.Models {
+
+ ///
+ /// Github开放API中Release信息格式
+ ///
+ public class Version {
+ [JsonPropertyName("id")]
+ public ulong Id { get; set; }
+ [JsonPropertyName("tag_name")]
+ public string TagName { get; set; }
+ [JsonPropertyName("target_commitish")]
+ public string CommitSHA { get; set; }
+ [JsonPropertyName("prerelease")]
+ public bool PreRelease { get; set; }
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ [JsonPropertyName("body")]
+ public string Body { get; set; }
+ [JsonPropertyName("created_at")]
+ public DateTime CreatedAt { get; set; }
+
+ public string PublishTime {
+ get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); }
+ }
+
+ public string IsPrerelease {
+ get { return PreRelease ? "YES" : "NO"; }
+ }
+
+ public static void Check(Action onUpgradable) {
+ if (!Preference.Instance.General.CheckForUpdate) return;
+
+ var curDayOfYear = DateTime.Now.DayOfYear;
+ var lastDayOfYear = Preference.Instance.General.LastCheckDay;
+ if (lastDayOfYear != curDayOfYear) {
+ Preference.Instance.General.LastCheckDay = curDayOfYear;
+ Task.Run(async () => {
+ try {
+ var req = new HttpClient();
+ var rsp = await req.GetAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest");
+ rsp.EnsureSuccessStatusCode();
+
+ var raw = await rsp.Content.ReadAsStringAsync();
+ var ver = JsonSerializer.Deserialize(raw);
+ var cur = Assembly.GetExecutingAssembly().GetName().Version;
+
+ var matches = Regex.Match(ver.TagName, @"^v(\d+)\.(\d+).*");
+ if (!matches.Success) return;
+
+ var major = int.Parse(matches.Groups[1].Value);
+ var minor = int.Parse(matches.Groups[2].Value);
+ if (major > cur.Major || (major == cur.Major && minor > cur.Minor)) {
+ onUpgradable?.Invoke(ver);
+ }
+ } catch {
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/Views/About.xaml b/src/Views/About.xaml
index 361bab18..e084e0b0 100644
--- a/src/Views/About.xaml
+++ b/src/Views/About.xaml
@@ -1,124 +1,124 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/About.xaml.cs b/src/Views/About.xaml.cs
index c533ae2b..cce5e618 100644
--- a/src/Views/About.xaml.cs
+++ b/src/Views/About.xaml.cs
@@ -1,55 +1,48 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reflection;
-using System.Windows;
-
-namespace SourceGit.Views {
-
- ///
- /// 关于对话框
- ///
- public partial class About : Controls.Window {
-
- public class Keymap {
- public string Key { get; set; }
- public string Desc { get; set; }
- public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); }
- }
-
- public About() {
- InitializeComponent();
-
- var asm = Assembly.GetExecutingAssembly().GetName();
- var framework = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName;
- var dotnetVer = framework.Substring(framework.IndexOf("=") + 1);
-
- version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor} .NET : {dotnetVer}";
-
- hotkeys.ItemsSource = new List() {
- new Keymap("CTRL + T", "NewTab"),
- new Keymap("CTRL + W", "CloseTab"),
- new Keymap("CTRL + TAB", "NextTab"),
- new Keymap("CTRL + [1-9]", "SwitchTo"),
- new Keymap("CTRL + F", "Search"),
- new Keymap("F5", "Refresh"),
- new Keymap("SPACE", "ToggleStage"),
- new Keymap("ESC", "CancelPopup"),
- };
- }
-
- private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
-#if NET48
- Process.Start(e.Uri.AbsoluteUri);
-#else
- var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}");
- info.CreateNoWindow = true;
- Process.Start(info);
-#endif
- }
-
- private void Quit(object sender, RoutedEventArgs e) {
- Close();
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using System.Windows;
+
+namespace SourceGit.Views {
+
+ ///
+ /// 关于对话框
+ ///
+ public partial class About : Controls.Window {
+
+ public class Keymap {
+ public string Key { get; set; }
+ public string Desc { get; set; }
+ public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); }
+ }
+
+ public About() {
+ InitializeComponent();
+
+ var asm = Assembly.GetExecutingAssembly().GetName();
+ version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor}";
+
+ hotkeys.ItemsSource = new List() {
+ new Keymap("CTRL + T", "NewTab"),
+ new Keymap("CTRL + W", "CloseTab"),
+ new Keymap("CTRL + TAB", "NextTab"),
+ new Keymap("CTRL + [1-9]", "SwitchTo"),
+ new Keymap("CTRL + F", "Search"),
+ new Keymap("F5", "Refresh"),
+ new Keymap("SPACE", "ToggleStage"),
+ new Keymap("ESC", "CancelPopup"),
+ };
+ }
+
+ private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
+ var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}");
+ info.CreateNoWindow = true;
+ Process.Start(info);
+ }
+
+ private void Quit(object sender, RoutedEventArgs e) {
+ Close();
+ }
+ }
+}
diff --git a/src/Views/Controls/Avatar.cs b/src/Views/Controls/Avatar.cs
index 45fbb539..128033fe 100644
--- a/src/Views/Controls/Avatar.cs
+++ b/src/Views/Controls/Avatar.cs
@@ -1,258 +1,230 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Net;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-
-#if NET6_0
-using System.Net.Http;
-#endif
-
-namespace SourceGit.Views.Controls {
-
- ///
- /// 头像控件
- ///
- public class Avatar : Image {
-
- ///
- /// 显示FallbackLabel时的背景色
- ///
- private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] {
- new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90),
- new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90),
- new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90),
- new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90),
- new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90),
- };
-
- ///
- /// 头像资源本地缓存路径
- ///
- public static readonly string CACHE_PATH = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "SourceGit",
- "avatars");
-
- ///
- /// 邮件属性定义
- ///
- public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
- "Email",
- typeof(string),
- typeof(Avatar),
- new PropertyMetadata(null, OnEmailChanged));
-
- ///
- /// 邮件属性
- ///
- public string Email {
- get { return (string)GetValue(EmailProperty); }
- set { SetValue(EmailProperty, value); }
- }
-
- ///
- /// 下载头像失败时显示的Label属性定义
- ///
- public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
- "FallbackLabel",
- typeof(string),
- typeof(Avatar),
- new PropertyMetadata("?", OnFallbackLabelChanged));
-
- ///
- /// 下载头像失败时显示的Label属性
- ///
- public string FallbackLabel {
- get { return (string)GetValue(FallbackLabelProperty); }
- set { SetValue(FallbackLabelProperty, value); }
- }
-
- private static Dictionary> requesting = new Dictionary>();
- private static Dictionary loaded = new Dictionary();
- private static Task loader = null;
-
- private int colorIdx = 0;
- private FormattedText label = null;
-
- public Avatar() {
- SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality);
- SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto);
- Unloaded += (o, e) => Cancel(Email);
- }
-
- ///
- /// 取消一个下载任务
- ///
- ///
- private void Cancel(string email) {
- if (!string.IsNullOrEmpty(email) && requesting.ContainsKey(email)) {
- if (requesting[email].Count <= 1) {
- requesting.Remove(email);
- } else {
- requesting[email].Remove(this);
- }
- }
- }
-
- ///
- /// 渲染实现
- ///
- ///
- protected override void OnRender(DrawingContext dc) {
- base.OnRender(dc);
-
- if (Source == null && label != null) {
- var corner = Math.Max(2, Width / 16);
- var offsetX = (double)0;
- if (HorizontalAlignment == HorizontalAlignment.Right) {
- offsetX = -Width * 0.5;
- } else if (HorizontalAlignment == HorizontalAlignment.Left) {
- offsetX = Width * 0.5;
- }
-
- Brush brush = BACKGROUND_BRUSHES[colorIdx];
- dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner);
- dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5));
- }
- }
-
- ///
- /// 显示文本变化时触发
- ///
- ///
- ///
- private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
- Avatar a = d as Avatar;
- if (a == null) return;
-
- var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?";
-
- a.colorIdx = 0;
- a.label = new FormattedText(
- placeholder,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
- a.Width * 0.65,
- Brushes.White,
- VisualTreeHelper.GetDpi(a).PixelsPerDip);
-
- var chars = placeholder.ToCharArray();
- foreach (var ch in chars) a.colorIdx += Math.Abs(ch);
- a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length;
- }
-
- ///
- /// 邮件变化时触发
- ///
- ///
- ///
- private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
- Avatar a = d as Avatar;
- if (a == null) return;
-
- a.Cancel(e.OldValue as string);
- a.Source = null;
- a.InvalidateVisual();
-
- var email = e.NewValue as string;
- if (string.IsNullOrEmpty(email)) return;
-
- if (loaded.ContainsKey(email)) {
- a.Source = loaded[email];
- return;
- }
-
- if (requesting.ContainsKey(email)) {
- requesting[email].Add(a);
- 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)) {
- var img = new BitmapImage(new Uri(filePath));
- loaded.Add(email, img);
- a.Source = img;
- return;
- }
-
- requesting.Add(email, new List());
- requesting[email].Add(a);
-
- Action job = () => {
- if (!requesting.ContainsKey(email)) return;
-
- try {
-#if NET6_0
- var req = new HttpClient().GetAsync(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404");
- req.Wait();
-
- var rsp = req.Result;
- if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) {
- var writer = File.OpenWrite(filePath);
- rsp.Content.CopyToAsync(writer).Wait();
- writer.Close();
-
- a.Dispatcher.Invoke(() => {
- var img = new BitmapImage(new Uri(filePath));
- loaded.Add(email, img);
-
- if (requesting.ContainsKey(email)) {
- foreach (var one in requesting[email]) one.Source = img;
- }
- });
- } else {
- if (!loaded.ContainsKey(email)) loaded.Add(email, null);
- }
-#else
- HttpWebRequest req = WebRequest.CreateHttp(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404");
- req.Timeout = 2000;
- req.Method = "GET";
-
- HttpWebResponse rsp = req.GetResponse() as HttpWebResponse;
- if (rsp.StatusCode == HttpStatusCode.OK) {
- using (Stream reader = rsp.GetResponseStream())
- using (FileStream writer = File.OpenWrite(filePath)) {
- reader.CopyTo(writer);
- }
-
- a.Dispatcher.Invoke(() => {
- var img = new BitmapImage(new Uri(filePath));
- loaded.Add(email, img);
-
- if (requesting.ContainsKey(email)) {
- foreach (var one in requesting[email]) one.Source = img;
- }
- });
- } else {
- if (!loaded.ContainsKey(email)) loaded.Add(email, null);
- }
-#endif
- } catch {
- if (!loaded.ContainsKey(email)) loaded.Add(email, null);
- }
-
- requesting.Remove(email);
- };
-
- if (loader != null && !loader.IsCompleted) {
- loader = loader.ContinueWith(t => { job(); });
- } else {
- loader = Task.Run(job);
- }
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Net.Http;
+
+namespace SourceGit.Views.Controls {
+
+ ///
+ /// 头像控件
+ ///
+ public class Avatar : Image {
+
+ ///
+ /// 显示FallbackLabel时的背景色
+ ///
+ private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] {
+ new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90),
+ new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90),
+ new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90),
+ new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90),
+ new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90),
+ };
+
+ ///
+ /// 头像资源本地缓存路径
+ ///
+ public static readonly string CACHE_PATH = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "SourceGit",
+ "avatars");
+
+ ///
+ /// 邮件属性定义
+ ///
+ public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
+ "Email",
+ typeof(string),
+ typeof(Avatar),
+ new PropertyMetadata(null, OnEmailChanged));
+
+ ///
+ /// 邮件属性
+ ///
+ public string Email {
+ get { return (string)GetValue(EmailProperty); }
+ set { SetValue(EmailProperty, value); }
+ }
+
+ ///
+ /// 下载头像失败时显示的Label属性定义
+ ///
+ public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
+ "FallbackLabel",
+ typeof(string),
+ typeof(Avatar),
+ new PropertyMetadata("?", OnFallbackLabelChanged));
+
+ ///
+ /// 下载头像失败时显示的Label属性
+ ///
+ public string FallbackLabel {
+ get { return (string)GetValue(FallbackLabelProperty); }
+ set { SetValue(FallbackLabelProperty, value); }
+ }
+
+ private static Dictionary> requesting = new Dictionary>();
+ private static Dictionary loaded = new Dictionary();
+ private static Task loader = null;
+
+ private int colorIdx = 0;
+ private FormattedText label = null;
+
+ public Avatar() {
+ SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality);
+ SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto);
+ Unloaded += (o, e) => Cancel(Email);
+ }
+
+ ///
+ /// 取消一个下载任务
+ ///
+ ///
+ private void Cancel(string email) {
+ if (!string.IsNullOrEmpty(email) && requesting.ContainsKey(email)) {
+ if (requesting[email].Count <= 1) {
+ requesting.Remove(email);
+ } else {
+ requesting[email].Remove(this);
+ }
+ }
+ }
+
+ ///
+ /// 渲染实现
+ ///
+ ///
+ protected override void OnRender(DrawingContext dc) {
+ base.OnRender(dc);
+
+ if (Source == null && label != null) {
+ var corner = Math.Max(2, Width / 16);
+ var offsetX = (double)0;
+ if (HorizontalAlignment == HorizontalAlignment.Right) {
+ offsetX = -Width * 0.5;
+ } else if (HorizontalAlignment == HorizontalAlignment.Left) {
+ offsetX = Width * 0.5;
+ }
+
+ Brush brush = BACKGROUND_BRUSHES[colorIdx];
+ dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner);
+ dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5));
+ }
+ }
+
+ ///
+ /// 显示文本变化时触发
+ ///
+ ///
+ ///
+ private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ Avatar a = d as Avatar;
+ if (a == null) return;
+
+ var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?";
+
+ a.colorIdx = 0;
+ a.label = new FormattedText(
+ placeholder,
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
+ a.Width * 0.65,
+ Brushes.White,
+ VisualTreeHelper.GetDpi(a).PixelsPerDip);
+
+ var chars = placeholder.ToCharArray();
+ foreach (var ch in chars) a.colorIdx += Math.Abs(ch);
+ a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length;
+ }
+
+ ///
+ /// 邮件变化时触发
+ ///
+ ///
+ ///
+ private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ Avatar a = d as Avatar;
+ if (a == null) return;
+
+ a.Cancel(e.OldValue as string);
+ a.Source = null;
+ a.InvalidateVisual();
+
+ var email = e.NewValue as string;
+ if (string.IsNullOrEmpty(email)) return;
+
+ if (loaded.ContainsKey(email)) {
+ a.Source = loaded[email];
+ return;
+ }
+
+ if (requesting.ContainsKey(email)) {
+ requesting[email].Add(a);
+ 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)) {
+ var img = new BitmapImage(new Uri(filePath));
+ loaded.Add(email, img);
+ a.Source = img;
+ return;
+ }
+
+ requesting.Add(email, new List());
+ requesting[email].Add(a);
+
+ Action job = () => {
+ if (!requesting.ContainsKey(email)) return;
+
+ try {
+ var req = new HttpClient().GetAsync(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404");
+ req.Wait();
+
+ var rsp = req.Result;
+ if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) {
+ var writer = File.OpenWrite(filePath);
+ rsp.Content.CopyToAsync(writer).Wait();
+ writer.Close();
+
+ a.Dispatcher.Invoke(() => {
+ var img = new BitmapImage(new Uri(filePath));
+ loaded.Add(email, img);
+
+ if (requesting.ContainsKey(email)) {
+ foreach (var one in requesting[email]) one.Source = img;
+ }
+ });
+ } else {
+ if (!loaded.ContainsKey(email)) loaded.Add(email, null);
+ }
+ } catch {
+ if (!loaded.ContainsKey(email)) loaded.Add(email, null);
+ }
+
+ requesting.Remove(email);
+ };
+
+ if (loader != null && !loader.IsCompleted) {
+ loader = loader.ContinueWith(t => { job(); });
+ } else {
+ loader = Task.Run(job);
+ }
+ }
+ }
+}
diff --git a/src/Views/Upgrade.xaml.cs b/src/Views/Upgrade.xaml.cs
index eefd3a50..6a8e4027 100644
--- a/src/Views/Upgrade.xaml.cs
+++ b/src/Views/Upgrade.xaml.cs
@@ -1,32 +1,28 @@
-using System.Diagnostics;
-using System.Windows;
-
-namespace SourceGit.Views {
- ///
- /// 新版本提示窗口
- ///
- public partial class Upgrade : Controls.Window {
- public Models.Version Version { get; set; } = new Models.Version();
-
- public Upgrade(Models.Version ver) {
- Version = ver;
- InitializeComponent();
- txtRelease.Text = App.Text("UpdateAvailable.Title", ver.Name);
- }
-
- private void Download(object sender, RoutedEventArgs e) {
- var url = $"https://github.com/sourcegit-scm/sourcegit/releases/{Version.TagName}";
-#if NET48
- Process.Start(url);
-#else
- var info = new ProcessStartInfo("cmd", $"/c start {url}");
- info.CreateNoWindow = true;
- Process.Start(info);
-#endif
- }
-
- private void Quit(object sender, RoutedEventArgs e) {
- Close();
- }
- }
-}
+using System.Diagnostics;
+using System.Windows;
+
+namespace SourceGit.Views {
+ ///
+ /// 新版本提示窗口
+ ///
+ public partial class Upgrade : Controls.Window {
+ public Models.Version Version { get; set; } = new Models.Version();
+
+ public Upgrade(Models.Version ver) {
+ Version = ver;
+ InitializeComponent();
+ txtRelease.Text = App.Text("UpdateAvailable.Title", ver.Name);
+ }
+
+ private void Download(object sender, RoutedEventArgs e) {
+ var url = $"https://github.com/sourcegit-scm/sourcegit/releases/{Version.TagName}";
+ var info = new ProcessStartInfo("cmd", $"/c start {url}");
+ info.CreateNoWindow = true;
+ Process.Start(info);
+ }
+
+ private void Quit(object sender, RoutedEventArgs e) {
+ Close();
+ }
+ }
+}
diff --git a/src/Views/Widgets/Histories.xaml.cs b/src/Views/Widgets/Histories.xaml.cs
index 585bdb2d..07cb7227 100644
--- a/src/Views/Widgets/Histories.xaml.cs
+++ b/src/Views/Widgets/Histories.xaml.cs
@@ -72,15 +72,6 @@ namespace SourceGit.Views.Widgets {
} else {
searching = true;
foreach (var c in cachedCommits) {
-#if NET48
- if (c.SHA.Contains(filter)
- || c.Subject.Contains(filter)
- || c.Message.Contains(filter)
- || c.Author.Name.Contains(filter)
- || c.Committer.Name.Contains(filter)) {
- visible.Add(c);
- }
-#else
if (c.SHA.Contains(filter, StringComparison.Ordinal)
|| c.Subject.Contains(filter, StringComparison.Ordinal)
|| c.Message.Contains(filter, StringComparison.Ordinal)
@@ -88,7 +79,6 @@ namespace SourceGit.Views.Widgets {
|| c.Committer.Name.Contains(filter, StringComparison.Ordinal)) {
visible.Add(c);
}
-#endif
}
}
diff --git a/src/Views/Widgets/WorkingCopyChanges.xaml.cs b/src/Views/Widgets/WorkingCopyChanges.xaml.cs
index 94796161..97c97caf 100644
--- a/src/Views/Widgets/WorkingCopyChanges.xaml.cs
+++ b/src/Views/Widgets/WorkingCopyChanges.xaml.cs
@@ -215,11 +215,7 @@ namespace SourceGit.Views.Widgets {
if (!added) Changes.Add(c);
-#if NET48
- int sepIdx = c.Path.IndexOf("/", StringComparison.Ordinal);
-#else
int sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal);
-#endif
if (sepIdx < 0) {
GetOrAddTreeNode(Nodes, c.Path, c, false);
} else {