diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 8d43eff98..4580798b2 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -90,6 +90,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Flow.Launcher/ReleaseNotesWindow.xaml b/Flow.Launcher/ReleaseNotesWindow.xaml new file mode 100644 index 000000000..132de63c8 --- /dev/null +++ b/Flow.Launcher/ReleaseNotesWindow.xaml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/ReleaseNotesWindow.xaml.cs b/Flow.Launcher/ReleaseNotesWindow.xaml.cs new file mode 100644 index 000000000..18c67ac5b --- /dev/null +++ b/Flow.Launcher/ReleaseNotesWindow.xaml.cs @@ -0,0 +1,149 @@ +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 Flow.Launcher.Infrastructure.Http; + +namespace Flow.Launcher +{ + public partial class ReleaseNotesWindow : Window + { + public ReleaseNotesWindow() + { + InitializeComponent(); + } + + #region Window Events + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")] + private async void Window_Loaded(object sender, RoutedEventArgs e) + { + RefreshMaximizeRestoreButton(); + MarkdownViewer.Markdown = await GetReleaseNotesMarkdownAsync(); + } + + private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e) + { + Close(); + } + + #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 Release Notes + + private static async Task GetReleaseNotesMarkdownAsync() + { + var releaseNotesJSON = await Http.GetStringAsync("https://api.github.com/repos/Flow-Launcher/Flow.Launcher/releases"); + var releases = JsonSerializer.Deserialize>(releaseNotesJSON); + + // Get the latest releases + var latestReleases = releases.OrderByDescending(release => release.PublishedDate).Take(3); + + // Build the release notes in Markdown format + var releaseNotesHtmlBuilder = new StringBuilder(string.Empty); + foreach (var release in latestReleases) + { + releaseNotesHtmlBuilder.AppendLine("# " + release.Name); + + // Add unit for images: Replace with + 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(" "); + } + + 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("(]*width\\s*=\\s*[\"']?)(\\d+)([\"']?)([^>]*>)", RegexOptions.IgnoreCase, "en-GB")] + private static partial Regex ImageUnitRegex(); + + #endregion + } +}