Merge pull request #3671 from Flow-Launcher/release_notes

Release Notes Window
This commit is contained in:
Jack Ye 2025-06-09 16:01:07 +08:00 committed by GitHub
commit 60770da446
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1271 additions and 58 deletions

View file

@ -220,5 +220,18 @@ namespace Flow.Launcher.Infrastructure.Http
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
public static async Task<string> GetStringAsync(string url, CancellationToken token = default)
{
try
{
Log.Debug(ClassName, $"Url <{url}>");
return await client.GetStringAsync(url, token);
}
catch (System.Exception e)
{
return string.Empty;
}
}
}
}

View file

@ -33,7 +33,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
_storage.Save();
}
private string _theme = Constant.DefaultTheme;
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
public string ColorScheme { get; set; } = "System";
@ -64,6 +63,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
OnPropertyChanged();
}
}
private string _theme = Constant.DefaultTheme;
public string Theme
{
get => _theme;
@ -79,6 +79,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
public bool UseDropShadowEffect { get; set; } = true;
public BackdropTypes BackdropType{ get; set; } = BackdropTypes.None;
public string ReleaseNotesVersion { get; set; } = string.Empty;
/* Appearance Settings. It should be separated from the setting later.*/
public double WindowHeightSize { get; set; } = 42;

View file

@ -84,6 +84,15 @@ namespace Flow.Launcher.Plugin
/// <param name="subTitle">Optional message subtitle</param>
void ShowMsgError(string title, string subTitle = "");
/// <summary>
/// Show the error message using Flow's standard error icon.
/// </summary>
/// <param name="title">Message title</param>
/// <param name="buttonText">Message button content</param>
/// <param name="buttonAction">Message button action</param>
/// <param name="subTitle">Optional message subtitle</param>
void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "");
/// <summary>
/// Show the MainWindow when hiding
/// </summary>
@ -127,6 +136,27 @@ namespace Flow.Launcher.Plugin
/// <param name="useMainWindowAsOwner">when true will use main windows as the owner</param>
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
/// <summary>
/// Show message box with button
/// </summary>
/// <param name="title">Message title</param>
/// <param name="buttonText">Message button content</param>
/// <param name="buttonAction">Message button action</param>
/// <param name="subTitle">Message subtitle</param>
/// <param name="iconPath">Message icon path (relative path to your plugin folder)</param>
void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle = "", string iconPath = "");
/// <summary>
/// Show message box with button
/// </summary>
/// <param name="title">Message title</param>
/// <param name="buttonText">Message button content</param>
/// <param name="buttonAction">Message button action</param>
/// <param name="subTitle">Message subtitle</param>
/// <param name="iconPath">Message icon path (relative path to your plugin folder)</param>
/// <param name="useMainWindowAsOwner">when true will use main windows as the owner</param>
void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
/// <summary>
/// Open setting dialog
/// </summary>

View file

@ -171,6 +171,8 @@ namespace Flow.Launcher
Current.Resources["SettingWindowFont"] = new FontFamily(_settings.SettingWindowFont);
Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(_settings.SettingWindowFont);
Notification.Install();
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------");

View file

@ -90,6 +90,11 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="MdXaml" Version="1.27.0" />
<PackageReference Include="MdXaml.AnimatedGif" Version="1.27.0" />
<PackageReference Include="MdXaml.Html" Version="1.27.0" />
<PackageReference Include="MdXaml.Plugins" Version="1.27.0" />
<PackageReference Include="MdXaml.Svg" Version="1.27.0" />
<!-- Do not upgrade Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Hosting since we are .Net7.0 -->
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />

View file

@ -365,6 +365,13 @@
<system:String x:Key="LogLevelINFO">Info</system:String>
<system:String x:Key="settingWindowFontTitle">Setting Window Font</system:String>
<!-- Release Notes Window -->
<system:String x:Key="seeMoreReleaseNotes">See more release notes on GitHub</system:String>
<system:String x:Key="checkNetworkConnectionTitle">Failed to fetch release notes</system:String>
<system:String x:Key="checkNetworkConnectionSubTitle">Please check your network connection or ensure GitHub is accessible</system:String>
<system:String x:Key="appUpdateTitle">Flow Launcher has been updated to {0}</system:String>
<system:String x:Key="appUpdateButtonContent">Click here to view the release notes</system:String>
<!-- FileManager Setting Dialog -->
<system:String x:Key="fileManagerWindow">Select File Manager</system:String>
<system:String x:Key="fileManager_learnMore">Learn more</system:String>

View file

@ -121,6 +121,9 @@ namespace Flow.Launcher
// Set First Launch to false
_settings.FirstLaunch = false;
// Update release notes version
_settings.ReleaseNotesVersion = Constant.Version;
// Set Backdrop Type to Acrylic for Windows 11 when First Launch. Default is None
if (Win32Helper.IsBackdropSupported()) _settings.BackdropType = BackdropTypes.Acrylic;
@ -132,6 +135,25 @@ namespace Flow.Launcher
welcomeWindow.Show();
}
if (_settings.ReleaseNotesVersion != Constant.Version)
{
// Update release notes version
_settings.ReleaseNotesVersion = Constant.Version;
// Display message box with button
App.API.ShowMsgWithButton(
string.Format(App.API.GetTranslation("appUpdateTitle"), Constant.Version),
App.API.GetTranslation("appUpdateButtonContent"),
() =>
{
Application.Current.Dispatcher.Invoke(() =>
{
var releaseNotesWindow = new ReleaseNotesWindow();
releaseNotesWindow.Show();
});
});
}
// Initialize place holder
SetupPlaceholderText();
_viewModel.PlaceholderText = _settings.PlaceholderText;

View file

@ -1,41 +1,76 @@
<Window x:Class="Flow.Launcher.Msg"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="#ebebeb"
Topmost="True"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="None"
ShowInTaskbar="False"
Title="Msg" Height="60" Width="420">
<Window
x:Class="Flow.Launcher.Msg"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Msg"
Width="420"
Height="60"
Background="#ebebeb"
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="Height"
Topmost="True"
WindowStyle="None">
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation x:Name="showAnimation" Duration="0:0:0.3" Storyboard.TargetProperty="Top"
AccelerationRatio="0.2" />
<DoubleAnimation
x:Name="showAnimation"
AccelerationRatio="0.2"
Storyboard.TargetProperty="Top"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5">
<Grid
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition />
<ColumnDefinition Width="2.852" />
<ColumnDefinition Width="13.148"/>
<ColumnDefinition Width="13.148" />
</Grid.ColumnDefinitions>
<Image x:Name="imgIco" Width="32" Height="32" HorizontalAlignment="Left" Margin="0,9" />
<Grid HorizontalAlignment="Stretch" Margin="5 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
<Image
x:Name="imgIco"
Width="32"
Height="32"
Margin="0 9"
HorizontalAlignment="Left" />
<Grid
Grid.Column="1"
Margin="5 0 0 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock x:Name="tbTitle" FontSize="16" Foreground="#37392c" FontWeight="Medium">Title</TextBlock>
<TextBlock Grid.Row="1" Foreground="#8e94a4" x:Name="tbSubTitle">sdfdsf</TextBlock>
<TextBlock
x:Name="tbTitle"
FontSize="16"
FontWeight="Medium"
Foreground="#37392c">
Title
</TextBlock>
<TextBlock
x:Name="tbSubTitle"
Grid.Row="1"
Foreground="#8e94a4">
sdfdsf
</TextBlock>
</Grid>
<Image x:Name="imgClose" Grid.Column="2" Cursor="Hand" Width="16" VerticalAlignment="Top"
HorizontalAlignment="Right" Grid.ColumnSpan="2" />
<Image
x:Name="imgClose"
Grid.Column="2"
Grid.ColumnSpan="2"
Width="16"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Cursor="Hand" />
</Grid>
</Window>

View file

@ -0,0 +1,84 @@
<Window
x:Class="Flow.Launcher.MsgWithButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Msg"
Width="420"
Height="60"
Background="#ebebeb"
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="Height"
Topmost="True"
WindowStyle="None">
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
x:Name="showAnimation"
AccelerationRatio="0.2"
Storyboard.TargetProperty="Top"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<StackPanel Orientation="Vertical">
<Grid
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition />
<ColumnDefinition Width="2.852" />
<ColumnDefinition Width="13.148" />
</Grid.ColumnDefinitions>
<Image
x:Name="imgIco"
Width="32"
Height="32"
Margin="0 9"
HorizontalAlignment="Left" />
<Grid
Grid.Column="1"
Margin="5 0 0 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock
x:Name="tbTitle"
FontSize="16"
FontWeight="Medium"
Foreground="#37392c">
Title
</TextBlock>
<TextBlock
x:Name="tbSubTitle"
Grid.Row="1"
Foreground="#8e94a4">
sdfdsf
</TextBlock>
</Grid>
<Image
x:Name="imgClose"
Grid.Column="2"
Grid.ColumnSpan="2"
Width="16"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Cursor="Hand" />
</Grid>
<Button
x:Name="btn"
Margin="5 0 5 5"
HorizontalAlignment="Stretch"
Content="fwafaw"
Foreground="#dcdcdc" />
</StackPanel>
</Window>

View file

@ -0,0 +1,95 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media.Animation;
using Flow.Launcher.Infrastructure;
namespace Flow.Launcher
{
public partial class MsgWithButton : Window
{
private readonly Storyboard fadeOutStoryboard = new();
private bool closing;
public MsgWithButton()
{
InitializeComponent();
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dipWorkingArea = Win32Helper.TransformPixelsToDIP(this,
screen.WorkingArea.Width,
screen.WorkingArea.Height);
Left = dipWorkingArea.X - Width;
Top = dipWorkingArea.Y;
showAnimation.From = dipWorkingArea.Y;
showAnimation.To = dipWorkingArea.Y - Height;
// Create the fade out storyboard
fadeOutStoryboard.Completed += fadeOutStoryboard_Completed;
DoubleAnimation fadeOutAnimation = new DoubleAnimation(dipWorkingArea.Y - Height, dipWorkingArea.Y, new Duration(TimeSpan.FromSeconds(5)))
{
AccelerationRatio = 0.2
};
Storyboard.SetTarget(fadeOutAnimation, this);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(TopProperty));
fadeOutStoryboard.Children.Add(fadeOutAnimation);
_ = LoadImageAsync();
imgClose.MouseUp += imgClose_MouseUp;
}
private async System.Threading.Tasks.Task LoadImageAsync()
{
imgClose.Source = await App.API.LoadImageAsync(Path.Combine(Constant.ProgramDirectory, "Images\\close.png"));
}
void imgClose_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!closing)
{
closing = true;
fadeOutStoryboard.Begin();
}
}
private void fadeOutStoryboard_Completed(object sender, EventArgs e)
{
Close();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
public async void Show(string title, string buttonText, Action buttonAction, string subTitle, string iconPath)
{
tbTitle.Text = title;
tbSubTitle.Text = subTitle;
btn.Content = buttonText;
btn.Click += (s, e) => buttonAction();
if (string.IsNullOrEmpty(subTitle))
{
tbSubTitle.Visibility = Visibility.Collapsed;
}
if (!File.Exists(iconPath))
{
imgIco.Source = await App.API.LoadImageAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
}
else
{
imgIco.Source = await App.API.LoadImageAsync(iconPath);
}
Show();
await Dispatcher.InvokeAsync(async () =>
{
if (!closing)
{
closing = true;
await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin);
}
});
}
}
}

View file

@ -1,8 +1,9 @@
using Flow.Launcher.Infrastructure;
using Microsoft.Toolkit.Uwp.Notifications;
using System;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Windows;
using Flow.Launcher.Infrastructure;
using Microsoft.Toolkit.Uwp.Notifications;
namespace Flow.Launcher
{
@ -12,10 +13,30 @@ namespace Flow.Launcher
internal static bool legacy = !Win32Helper.IsNotificationSupported();
private static readonly ConcurrentDictionary<string, Action> _notificationActions = new();
internal static void Install()
{
if (!legacy)
{
ToastNotificationManagerCompat.OnActivated += toastArgs =>
{
var actionId = toastArgs.Argument; // Or use toastArgs.UserInput if using input
if (_notificationActions.TryGetValue(actionId, out var action))
{
action?.Invoke();
}
};
}
}
internal static void Uninstall()
{
if (!legacy)
{
_notificationActions.Clear();
ToastNotificationManagerCompat.Uninstall();
}
}
public static void Show(string title, string subTitle, string iconPath = null)
@ -67,5 +88,58 @@ namespace Flow.Launcher
var msg = new Msg();
msg.Show(title, subTitle, iconPath);
}
public static void ShowWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath = null)
{
Application.Current.Dispatcher.Invoke(() =>
{
ShowInternalWithButton(title, buttonText, buttonAction, subTitle, iconPath);
});
}
private static void ShowInternalWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath = null)
{
// Handle notification for win7/8/early win10
if (legacy)
{
LegacyShowWithButton(title, buttonText, buttonAction, subTitle, iconPath);
return;
}
// Using Windows Notification System
var Icon = !File.Exists(iconPath)
? Path.Combine(Constant.ProgramDirectory, "Images\\app.png")
: iconPath;
try
{
var guid = Guid.NewGuid().ToString();
new ToastContentBuilder()
.AddText(title, hintMaxLines: 1)
.AddText(subTitle)
.AddButton(buttonText, ToastActivationType.Background, guid)
.AddAppLogoOverride(new Uri(Icon))
.Show();
_notificationActions.AddOrUpdate(guid, buttonAction, (key, oldValue) => buttonAction);
}
catch (InvalidOperationException e)
{
// Temporary fix for the Windows 11 notification issue
// Possibly from 22621.1413 or 22621.1485, judging by post time of #2024
App.API.LogException(ClassName, "Notification InvalidOperationException Error", e);
LegacyShowWithButton(title, buttonText, buttonAction, subTitle, iconPath);
}
catch (Exception e)
{
App.API.LogException(ClassName, "Notification Error", e);
LegacyShowWithButton(title, buttonText, buttonAction, subTitle, iconPath);
}
}
private static void LegacyShowWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath)
{
var msg = new MsgWithButton();
msg.Show(title, buttonText, buttonAction, subTitle, iconPath);
}
}
}

View file

@ -123,6 +123,9 @@ namespace Flow.Launcher
public void ShowMsgError(string title, string subTitle = "") =>
ShowMsg(title, subTitle, Constant.ErrorIcon, true);
public void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "") =>
ShowMsgWithButton(title, buttonText, buttonAction, subTitle, Constant.ErrorIcon, true);
public void ShowMsg(string title, string subTitle = "", string iconPath = "") =>
ShowMsg(title, subTitle, iconPath, true);
@ -131,6 +134,14 @@ namespace Flow.Launcher
Notification.Show(title, subTitle, iconPath);
}
public void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle = "", string iconPath = "") =>
ShowMsgWithButton(title, buttonText, buttonAction, subTitle, iconPath, true);
public void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
{
Notification.ShowWithButton(title, buttonText, buttonAction, subTitle, iconPath);
}
public void OpenSettingDialog()
{
Application.Current.Dispatcher.Invoke(() =>

View file

@ -0,0 +1,227 @@
<Window
x:Class="Flow.Launcher.ReleaseNotesWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Title="{DynamicResource releaseNotes}"
Width="940"
Height="600"
MinWidth="940"
MinHeight="600"
Background="{DynamicResource PopuBGColor}"
Closed="Window_Closed"
Foreground="{DynamicResource PopupTextColor}"
Loaded="Window_Loaded"
ResizeMode="CanResize"
StateChanged="Window_StateChanged"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="Close" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="Close" Executed="OnCloseExecuted" />
</Window.CommandBindings>
<Grid>
<Border Style="{StaticResource WindowMainPanelStyle}">
<Grid Background="{DynamicResource Color01B}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="24" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- TitleBar and Control -->
<Image
Grid.Row="0"
Grid.Column="0"
Width="16"
Height="16"
Margin="10 4 4 4"
RenderOptions.BitmapScalingMode="HighQuality"
Source="/Images/app.png" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="4 0 0 0"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource Color05B}"
Text="{DynamicResource releaseNotes}" />
<Button
Grid.Row="0"
Grid.Column="2"
Click="OnMinimizeButtonClick"
RenderOptions.EdgeMode="Aliased"
Style="{DynamicResource TitleBarButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,15 H 28"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button
Name="MaximizeButton"
Grid.Row="0"
Grid.Column="3"
Click="OnMaximizeRestoreButtonClick"
Style="{StaticResource TitleBarButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18.5,10.5 H 27.5 V 19.5 H 18.5 Z"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button
Name="RestoreButton"
Grid.Row="0"
Grid.Column="3"
Click="OnMaximizeRestoreButtonClick"
Style="{StaticResource TitleBarButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18.5,12.5 H 25.5 V 19.5 H 18.5 Z M 20.5,12.5 V 10.5 H 27.5 V 17.5 H 25.5"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button
Grid.Row="0"
Grid.Column="4"
Click="OnCloseButtonClick"
Style="{StaticResource TitleBarCloseButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,11 27,20 M 18,20 27,11"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Grid
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="5"
Margin="18 0 18 0">
<cc:HyperLink x:Name="SeeMore" Text="{DynamicResource seeMoreReleaseNotes}" />
</Grid>
<!-- Do not use scroll function of MarkdownViewer because it does not support smooth scroll -->
<ScrollViewer
x:Name="MarkdownScrollViewer"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="5"
Width="500"
Height="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- This row is for bottom margin -->
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<mdxam:MarkdownScrollViewer
x:Name="MarkdownViewer"
Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClickAction="SafetyDisplayWithRelativePath"
HorizontalScrollBarVisibility="Disabled"
Loaded="MarkdownViewer_Loaded"
MouseWheel="MarkdownViewer_MouseWheel"
Plugins="{StaticResource MdXamlPlugins}"
PreviewMouseWheel="MarkdownViewer_PreviewMouseWheel"
VerticalScrollBarVisibility="Disabled"
Visibility="Collapsed" />
</Grid>
</ScrollViewer>
<!-- This Grid is for display progress ring and refresh button. -->
<!-- And it is also for changing the size of the MarkdownViewer. -->
<!-- Because VerticalAlignment="Stretch" can cause size issue with MarkdownScrollViewer. -->
<Grid
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="5"
Margin="15 0 20 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SizeChanged="Grid_SizeChanged">
<ui:ProgressRing
x:Name="RefreshProgressRing"
Width="32"
Height="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="True" />
<Button
x:Name="RefreshButton"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="RefreshButton_Click"
Content="{DynamicResource refresh}"
Visibility="Collapsed" />
</Grid>
</Grid>
</Border>
</Grid>
</Window>

View file

@ -0,0 +1,255 @@
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;
namespace Flow.Launcher
{
public partial class ReleaseNotesWindow : Window
{
private static readonly string ReleaseNotes = Properties.Settings.Default.GithubRepo + "/releases";
public ReleaseNotesWindow()
{
InitializeComponent();
SeeMore.Uri = ReleaseNotes;
ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged += ThemeManager_ActualApplicationThemeChanged;
}
#region Window Events
private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (ModernWpf.ThemeManager.Current.ActualApplicationTheme == ModernWpf.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)
{
ModernWpf.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(
App.API.GetTranslation("checkNetworkConnectionTitle"),
App.API.GetTranslation("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;
MarkdownScrollViewer.Width = e.NewSize.Width;
}
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);
// Build the release notes in Markdown format
var releaseNotesHtmlBuilder = new StringBuilder(string.Empty);
foreach (var release in latestReleases)
{
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();
}
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
}
}

View file

@ -1,6 +1,10 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mdagif="clr-namespace:MdXaml.AnimatedGif;assembly=MdXaml.AnimatedGif"
xmlns:mdhtml="clr-namespace:MdXaml.Html;assembly=MdXaml.Html"
xmlns:mdplugins="clr-namespace:MdXaml.Plugins;assembly=MdXaml.Plugins"
xmlns:mdsvg="clr-namespace:MdXaml.Svg;assembly=MdXaml.Svg"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019">
@ -2408,12 +2412,15 @@
</Setter>
</Style>
<!-- Explorer Plugin Expander -->
<!-- Explorer Plugin Expander -->
<Style x:Key="ExpanderHeaderRightArrowStyle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="RootBorder" Background="Transparent" Padding="16,15,16,15">
<Border
x:Name="RootBorder"
Padding="16 15 16 15"
Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -2421,41 +2428,43 @@
</Grid.ColumnDefinitions>
<ContentPresenter
Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
RecognizesAccessKey="True"
SnapsToDevicePixels="True"
Content="{TemplateBinding Content}"
Margin="8 0 0 0"
ContentTemplate="{TemplateBinding ContentTemplate}" />
Grid.Column="0"
Margin="8 0 0 0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
SnapsToDevicePixels="True" />
<Grid Grid.Column="1"
Width="20" Height="20"
Margin="8 0 4 0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Background="Transparent"
RenderTransformOrigin="0.5,0.5"
x:Name="ChevronGrid">
<Grid
x:Name="ChevronGrid"
Grid.Column="1"
Width="20"
Height="20"
Margin="8 0 4 0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Background="Transparent"
RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<RotateTransform Angle="0"/>
<RotateTransform Angle="0" />
</Grid.RenderTransform>
<Ellipse
x:Name="circle"
Width="19"
Height="19"
Stroke="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
x:Name="circle"
Width="19"
Height="19"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stroke="Transparent" />
<Path
x:Name="arrow"
Data="M 1,1.5 L 4.5,5 L 8,1.5"
Stroke="#666"
StrokeThickness="1"
SnapsToDevicePixels="False"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
x:Name="arrow"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 1,1.5 L 4.5,5 L 8,1.5"
SnapsToDevicePixels="False"
Stroke="#666"
StrokeThickness="1" />
</Grid>
</Grid>
</Border>
@ -5754,4 +5763,340 @@
<Setter Property="Margin" Value="0 2 0 0" />
<Setter Property="Foreground" Value="{DynamicResource Color04B}" />
</Style>
<!-- Release Notes -->
<mdplugins:MdXamlPlugins x:Key="MdXamlPlugins">
<mdhtml:HtmlPluginSetup />
<mdsvg:SvgPluginSetup />
<mdagif:AnimatedGifPluginSetup />
</mdplugins:MdXamlPlugins>
<Style x:Key="DocumentStyleGithubLikeLight" TargetType="FlowDocument">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="TextAlignment" Value="Left" />
<Setter Property="PagePadding" Value="0" />
<Setter Property="FontSize" Value="14" />
<Style.Resources>
<Style TargetType="Section">
<Style.Triggers>
<Trigger Property="Tag" Value="Blockquote">
<Setter Property="Padding" Value="10 5" />
<Setter Property="BorderBrush" Value="#DEDEDE" />
<Setter Property="BorderThickness" Value="5 0 0 0" />
</Trigger>
</Style.Triggers>
</Style>
<Style xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" TargetType="avalonEdit:TextEditor">
<Setter Property="Background" Value="#EEEEEE" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="Margin" Value="2 0 2 0" />
<Setter Property="Padding" Value="3" />
</Style>
<Style TargetType="Paragraph">
<Setter Property="Margin" Value="0 7 0 0" />
<Style.Triggers>
<Trigger Property="Tag" Value="Heading1">
<Setter Property="Margin" Value="0 0 15 0" />
<Setter Property="Foreground" Value="#ff000000" />
<Setter Property="FontSize" Value="28" />
<Setter Property="FontWeight" Value="UltraBold" />
</Trigger>
<Trigger Property="Tag" Value="Heading2">
<Setter Property="Margin" Value="0 0 15 0" />
<Setter Property="Foreground" Value="#ff000000" />
<Setter Property="FontSize" Value="21" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="Tag" Value="Heading3">
<Setter Property="Margin" Value="0 0 10 0" />
<Setter Property="Foreground" Value="#ff000000" />
<Setter Property="FontSize" Value="17.5" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="Tag" Value="Heading4">
<Setter Property="Margin" Value="0 0 5 0" />
<Setter Property="Foreground" Value="#ff000000" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="Tag" Value="CodeBlock">
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="11.9" />
<Setter Property="Background" Value="#12181F25" />
<Setter Property="Padding" Value="20 10" />
</Trigger>
<Trigger Property="Tag" Value="Note">
<Setter Property="Margin" Value="5 0 5 0" />
<Setter Property="Padding" Value="10 5" />
<Setter Property="BorderBrush" Value="#DEDEDE" />
<Setter Property="BorderThickness" Value="3 3 3 3" />
<Setter Property="Background" Value="#FAFAFA" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Run">
<Style.Triggers>
<Trigger Property="Tag" Value="CodeSpan">
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="11.9" />
<Setter Property="Background" Value="#12181F25" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Span">
<Style.Triggers>
<Trigger Property="Tag" Value="CodeSpan">
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="11.9" />
<Setter Property="Background" Value="#12181F25" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Hyperlink">
<Setter Property="TextDecorations" Value="None" />
</Style>
<Style TargetType="Image">
<Setter Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor" />
<Style.Triggers>
<Trigger Property="Tag" Value="imageright">
<Setter Property="Margin" Value="20 0 0 0" />
</Trigger>
</Style.Triggers>
</Style>
<!--
The Table's style don't seem to support border-collapse.
By making the ruled line width 0.5 and applying it to cell and table,
it looks like the ruled lines are not doubled.
-->
<Style TargetType="Table">
<Setter Property="CellSpacing" Value="0" />
<Setter Property="BorderThickness" Value="0.5" />
<Setter Property="BorderBrush" Value="#DFE2E5" />
<Style.Resources>
<Style TargetType="TableCell">
<Setter Property="BorderThickness" Value="0.5" />
<Setter Property="BorderBrush" Value="#DFE2E5" />
<Setter Property="Padding" Value="13 6" />
</Style>
</Style.Resources>
</Style>
<Style TargetType="TableRowGroup">
<Style.Triggers>
<Trigger Property="Tag" Value="TableHeader">
<Setter Property="FontWeight" Value="DemiBold" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="#FFFFFF" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TableRow">
<Style.Triggers>
<Trigger Property="Tag" Value="EvenTableRow">
<Setter Property="Background" Value="#F6F8FA" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="BlockUIContainer">
<Style.Triggers>
<Trigger Property="Tag" Value="RuleSingle">
<Setter Property="Margin" Value="0 3" />
</Trigger>
<Trigger Property="Tag" Value="RuleDouble">
<Setter Property="Margin" Value="0 3" />
</Trigger>
<Trigger Property="Tag" Value="RuleBold">
<Setter Property="Margin" Value="0 3" />
</Trigger>
<Trigger Property="Tag" Value="RuleBoldWithSingle">
<Setter Property="Margin" Value="0 3" />
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
<Style x:Key="DocumentStyleGithubLikeDark" TargetType="FlowDocument">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="TextAlignment" Value="Left" />
<Setter Property="PagePadding" Value="0" />
<Setter Property="FontSize" Value="14" />
<Style.Resources>
<Style TargetType="Section">
<Style.Triggers>
<Trigger Property="Tag" Value="Blockquote">
<Setter Property="Padding" Value="10 5" />
<Setter Property="BorderBrush" Value="#212121" />
<Setter Property="BorderThickness" Value="5 0 0 0" />
</Trigger>
</Style.Triggers>
</Style>
<Style xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" TargetType="avalonEdit:TextEditor">
<Setter Property="Background" Value="#111111" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="Margin" Value="2 0 2 0" />
<Setter Property="Padding" Value="3" />
</Style>
<Style TargetType="Paragraph">
<Setter Property="Margin" Value="0 7 0 0" />
<Style.Triggers>
<Trigger Property="Tag" Value="Heading1">
<Setter Property="Margin" Value="0 0 15 0" />
<Setter Property="Foreground" Value="#FFFFFFFF" />
<Setter Property="FontSize" Value="28" />
<Setter Property="FontWeight" Value="UltraBold" />
</Trigger>
<Trigger Property="Tag" Value="Heading2">
<Setter Property="Margin" Value="0 0 15 0" />
<Setter Property="Foreground" Value="#FFFFFFFF" />
<Setter Property="FontSize" Value="21" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="Tag" Value="Heading3">
<Setter Property="Margin" Value="0 0 10 0" />
<Setter Property="Foreground" Value="#FFFFFFFF" />
<Setter Property="FontSize" Value="17.5" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="Tag" Value="Heading4">
<Setter Property="Margin" Value="0 0 5 0" />
<Setter Property="Foreground" Value="#FFFFFFFF" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="Tag" Value="CodeBlock">
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="11.9" />
<Setter Property="Background" Value="#E0E7DFDA" />
<Setter Property="Padding" Value="20 10" />
</Trigger>
<Trigger Property="Tag" Value="Note">
<Setter Property="Margin" Value="5 0 5 0" />
<Setter Property="Padding" Value="10 5" />
<Setter Property="BorderBrush" Value="#212121" />
<Setter Property="BorderThickness" Value="3 3 3 3" />
<Setter Property="Background" Value="#050505" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Run">
<Style.Triggers>
<Trigger Property="Tag" Value="CodeSpan">
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="11.9" />
<Setter Property="Background" Value="#E0E7DFDA" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Span">
<Style.Triggers>
<Trigger Property="Tag" Value="CodeSpan">
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="11.9" />
<Setter Property="Background" Value="#E0E7DFDA" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Hyperlink">
<Setter Property="TextDecorations" Value="None" />
</Style>
<Style TargetType="Image">
<Setter Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor" />
<Style.Triggers>
<Trigger Property="Tag" Value="imageright">
<Setter Property="Margin" Value="20 0 0 0" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Table">
<Setter Property="CellSpacing" Value="0" />
<Setter Property="BorderThickness" Value="0.5" />
<Setter Property="BorderBrush" Value="#202020" />
<Style.Resources>
<Style TargetType="TableCell">
<Setter Property="BorderThickness" Value="0.5" />
<Setter Property="BorderBrush" Value="#202020" />
<Setter Property="Padding" Value="13 6" />
</Style>
</Style.Resources>
</Style>
<Style TargetType="TableRowGroup">
<Style.Triggers>
<Trigger Property="Tag" Value="TableHeader">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="#000000" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TableRow">
<Style.Triggers>
<Trigger Property="Tag" Value="EvenTableRow">
<Setter Property="Background" Value="#090708" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="BlockUIContainer">
<Style.Triggers>
<Trigger Property="Tag" Value="RuleSingle">
<Setter Property="Margin" Value="0 3" />
</Trigger>
<Trigger Property="Tag" Value="RuleDouble">
<Setter Property="Margin" Value="0 3" />
</Trigger>
<Trigger Property="Tag" Value="RuleBold">
<Setter Property="Margin" Value="0 3" />
</Trigger>
<Trigger Property="Tag" Value="RuleBoldWithSingle">
<Setter Property="Margin" Value="0 3" />
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</ResourceDictionary>

View file

@ -314,4 +314,11 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{
SettingWindowFont = Win32Helper.GetSystemDefaultFont(false);
}
[RelayCommand]
private void OpenReleaseNotes()
{
var releaseNotesWindow = new ReleaseNotesWindow();
releaseNotesWindow.Show();
}
}

View file

@ -55,7 +55,7 @@
</cc:Card>
<cc:Card Title="{DynamicResource releaseNotes}" Icon="&#xe8fd;">
<cc:HyperLink Text="{DynamicResource releaseNotes}" Uri="{Binding ReleaseNotes}" />
<Button Command="{Binding OpenReleaseNotesCommand}" Content="{DynamicResource releaseNotes}" />
</cc:Card>