mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
269 lines
9.1 KiB
C#
269 lines
9.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using Flow.Launcher.Infrastructure.Http;
|
|
using iNKORE.UI.WPF.Modern;
|
|
|
|
namespace Flow.Launcher
|
|
{
|
|
public partial class ReleaseNotesWindow : Window
|
|
{
|
|
public string ReleaseNotes => Properties.Settings.Default.GithubRepo + "/releases";
|
|
|
|
public ReleaseNotesWindow()
|
|
{
|
|
InitializeComponent();
|
|
ThemeManager.Current.ActualApplicationThemeChanged += ThemeManager_ActualApplicationThemeChanged;
|
|
}
|
|
|
|
#region Window Events
|
|
|
|
private void ThemeManager_ActualApplicationThemeChanged(ThemeManager sender, object args)
|
|
{
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
if (ThemeManager.Current.ActualApplicationTheme == ApplicationTheme.Light)
|
|
{
|
|
MarkdownViewer.MarkdownStyle = (Style)Application.Current.Resources["DocumentStyleGithubLikeLight"];
|
|
MarkdownViewer.Foreground = Brushes.Black;
|
|
MarkdownViewer.Background = Brushes.White;
|
|
}
|
|
else
|
|
{
|
|
MarkdownViewer.MarkdownStyle = (Style)Application.Current.Resources["DocumentStyleGithubLikeDark"];
|
|
MarkdownViewer.Foreground = Brushes.White;
|
|
MarkdownViewer.Background = Brushes.Black;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
RefreshMaximizeRestoreButton();
|
|
ThemeManager_ActualApplicationThemeChanged(null, null);
|
|
}
|
|
|
|
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
private void Window_Closed(object sender, EventArgs e)
|
|
{
|
|
ThemeManager.Current.ActualApplicationThemeChanged -= ThemeManager_ActualApplicationThemeChanged;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Window Custom TitleBar
|
|
|
|
private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
|
|
{
|
|
WindowState = WindowState.Minimized;
|
|
}
|
|
|
|
private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
|
|
{
|
|
WindowState = WindowState switch
|
|
{
|
|
WindowState.Maximized => WindowState.Normal,
|
|
_ => WindowState.Maximized
|
|
};
|
|
}
|
|
|
|
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
private void RefreshMaximizeRestoreButton()
|
|
{
|
|
if (WindowState == WindowState.Maximized)
|
|
{
|
|
MaximizeButton.Visibility = Visibility.Hidden;
|
|
RestoreButton.Visibility = Visibility.Visible;
|
|
}
|
|
else
|
|
{
|
|
MaximizeButton.Visibility = Visibility.Visible;
|
|
RestoreButton.Visibility = Visibility.Hidden;
|
|
}
|
|
}
|
|
|
|
private void Window_StateChanged(object sender, EventArgs e)
|
|
{
|
|
RefreshMaximizeRestoreButton();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Control Events
|
|
|
|
private void MarkdownViewer_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
RefreshMarkdownViewer();
|
|
}
|
|
|
|
private void RefreshButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
RefreshButton.Visibility = Visibility.Collapsed;
|
|
RefreshProgressRing.Visibility = Visibility.Visible;
|
|
RefreshMarkdownViewer();
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
|
private async void RefreshMarkdownViewer()
|
|
{
|
|
var output = await GetReleaseNotesMarkdownAsync().ConfigureAwait(false);
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
RefreshProgressRing.Visibility = Visibility.Collapsed;
|
|
if (string.IsNullOrEmpty(output))
|
|
{
|
|
RefreshButton.Visibility = Visibility.Visible;
|
|
MarkdownViewer.Visibility = Visibility.Collapsed;
|
|
App.API.ShowMsgError(
|
|
Localize.checkNetworkConnectionTitle(),
|
|
Localize.checkNetworkConnectionSubTitle());
|
|
}
|
|
else
|
|
{
|
|
RefreshButton.Visibility = Visibility.Collapsed;
|
|
MarkdownViewer.Markdown = output;
|
|
MarkdownViewer.Visibility = Visibility.Visible;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
|
|
{
|
|
MarkdownScrollViewer.Height = e.NewSize.Height;
|
|
}
|
|
|
|
private void MarkdownViewer_MouseWheel(object sender, MouseWheelEventArgs e)
|
|
{
|
|
RaiseMouseWheelEvent(sender, e);
|
|
}
|
|
|
|
private void MarkdownViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
|
{
|
|
RaiseMouseWheelEvent(sender, e);
|
|
}
|
|
|
|
private void RaiseMouseWheelEvent(object sender, MouseWheelEventArgs e)
|
|
{
|
|
e.Handled = true; // Prevent the inner control from handling the event
|
|
|
|
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
|
|
{
|
|
RoutedEvent = UIElement.MouseWheelEvent,
|
|
Source = sender
|
|
};
|
|
|
|
// Raise the event on the parent ScrollViewer
|
|
MarkdownScrollViewer.RaiseEvent(eventArg);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Release Notes
|
|
|
|
private static async Task<string> GetReleaseNotesMarkdownAsync()
|
|
{
|
|
var releaseNotesJSON = await Http.GetStringAsync("https://api.github.com/repos/Flow-Launcher/Flow.Launcher/releases");
|
|
|
|
if (string.IsNullOrEmpty(releaseNotesJSON))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
var releases = JsonSerializer.Deserialize<List<GitHubReleaseInfo>>(releaseNotesJSON);
|
|
|
|
// Get the latest releases
|
|
var latestReleases = releases.OrderByDescending(release => release.PublishedDate).Take(3).ToList();
|
|
|
|
// Build the release notes in Markdown format
|
|
var releaseNotesHtmlBuilder = new StringBuilder(string.Empty);
|
|
|
|
for (int i = 0; i < latestReleases.Count; i++)
|
|
{
|
|
var release = latestReleases[i];
|
|
releaseNotesHtmlBuilder.AppendLine("# " + release.Name);
|
|
|
|
// Because MdXaml.Html package cannot correctly render images without units,
|
|
// We need to manually add unit for images
|
|
// E.g. Replace <img src="..." width="500"> with <img src="..." width="500px">
|
|
var notes = ImageUnitRegex().Replace(release.ReleaseNotes, m =>
|
|
{
|
|
var prefix = m.Groups[1].Value;
|
|
var widthValue = m.Groups[2].Value;
|
|
var quote = m.Groups[3].Value;
|
|
var suffix = m.Groups[4].Value;
|
|
// Only replace if width is number like 500 without units like 500px
|
|
if (IsNumber(widthValue))
|
|
return $"{prefix}{widthValue}px{quote}{suffix}";
|
|
return m.Value;
|
|
});
|
|
|
|
releaseNotesHtmlBuilder.AppendLine(notes);
|
|
releaseNotesHtmlBuilder.AppendLine();
|
|
|
|
// Add separator if it is not last release note
|
|
if (i < latestReleases.Count - 1)
|
|
{
|
|
releaseNotesHtmlBuilder.Append("<br />");
|
|
releaseNotesHtmlBuilder.Append("\n\n");
|
|
|
|
releaseNotesHtmlBuilder.AppendLine("---");
|
|
|
|
releaseNotesHtmlBuilder.Append("\n\n");
|
|
releaseNotesHtmlBuilder.Append("<br />");
|
|
releaseNotesHtmlBuilder.Append("\n\n");
|
|
}
|
|
}
|
|
|
|
return releaseNotesHtmlBuilder.ToString();
|
|
}
|
|
|
|
private static bool IsNumber(string input)
|
|
{
|
|
if (string.IsNullOrEmpty(input))
|
|
return false;
|
|
|
|
foreach (char c in input)
|
|
{
|
|
if (!char.IsDigit(c))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private sealed class GitHubReleaseInfo
|
|
{
|
|
[JsonPropertyName("published_at")]
|
|
public DateTimeOffset PublishedDate { get; set; }
|
|
|
|
[JsonPropertyName("name")]
|
|
public string Name { get; set; }
|
|
|
|
[JsonPropertyName("tag_name")]
|
|
public string TagName { get; set; }
|
|
|
|
[JsonPropertyName("body")]
|
|
public string ReleaseNotes { get; set; }
|
|
}
|
|
|
|
[GeneratedRegex("(<img\\s+[^>]*width\\s*=\\s*[\"']?)(\\d+)([\"']?)([^>]*>)", RegexOptions.IgnoreCase, "en-GB")]
|
|
private static partial Regex ImageUnitRegex();
|
|
|
|
#endregion
|
|
}
|
|
}
|