update shortcuts, add background task runner

This commit is contained in:
Zhe Fang
2025-08-16 20:23:11 -04:00
parent 133acf5592
commit f681b43e96
41 changed files with 982 additions and 488 deletions

View File

@@ -12,7 +12,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.52.0" />
Version="1.0.53.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -10,10 +10,10 @@
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Merged dictionaries here -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Theme -->
@@ -54,6 +54,7 @@
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />

View File

@@ -30,6 +30,7 @@
<None Remove="Controls\LyricsSettingsControl.xaml" />
<None Remove="Controls\MediaSettingsControl.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
@@ -58,7 +59,6 @@
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="DevWinUI.Controls" Version="8.9.1" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
@@ -186,6 +186,11 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ShortcutTextBox.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ExtendedSlider.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -90,6 +90,10 @@
<TextBlock x:Uid="SettingsPageAppDesktop" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageToggleHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.DesktopModeSettings.ToggleShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DesktopModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
@@ -103,46 +107,17 @@
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLockHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Center"
Text="Ctrl + Alt + " />
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DesktopModeSettings.LockHotKeyIndex, Mode=TwoWay}">
<ComboBoxItem Content="A" />
<ComboBoxItem Content="B" />
<ComboBoxItem Content="C" />
<ComboBoxItem Content="D" />
<ComboBoxItem Content="E" />
<ComboBoxItem Content="F" />
<ComboBoxItem Content="G" />
<ComboBoxItem Content="H" />
<ComboBoxItem Content="I" />
<ComboBoxItem Content="J" />
<ComboBoxItem Content="K" />
<ComboBoxItem Content="L" />
<ComboBoxItem Content="M" />
<ComboBoxItem Content="N" />
<ComboBoxItem Content="O" />
<ComboBoxItem Content="P" />
<ComboBoxItem Content="Q" />
<ComboBoxItem Content="R" />
<ComboBoxItem Content="S" />
<ComboBoxItem Content="T" />
<ComboBoxItem Content="U" />
<ComboBoxItem Content="V" />
<ComboBoxItem Content="W" />
<ComboBoxItem Content="X" />
<ComboBoxItem Content="Y" />
<ComboBoxItem Content="Z" />
</ComboBox>
</StackPanel>
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.DesktopModeSettings.LockShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Dock mode -->
<TextBlock x:Uid="SettingsPageAppDock" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageToggleHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.DockModeSettings.ToggleShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DockModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
@@ -184,6 +159,10 @@
<TextBlock x:Uid="SettingsPageAppPictureInPicture" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageToggleHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.PictureInPictureModeSettings.ToggleShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.PictureInPictureModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
@@ -192,6 +171,22 @@
</ComboBox>
</controls:SettingsCard>
<!-- Playback shortcut -->
<TextBlock x:Uid="SettingsPagePlaybackShortcut" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPagePlayOrPauseSongHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.PlayOrPauseShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageNextSongHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.NextSongShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPagePreviousSongHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.PreviousSongShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -55,7 +55,7 @@
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF207;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"

View File

@@ -220,7 +220,7 @@
Value="{x:Bind LyricsEffectSettings.LyricsVerticalEdgeOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF207;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsBlurAmountExtendedSlider"
Default="5"

View File

@@ -14,71 +14,74 @@
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<StackPanel>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageMusicLib"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8B7;}"
IsExpanded="True"
ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}">
<controls:SettingsExpander.ItemTemplate>
<controls:SettingsCard x:Uid="SettingsPageMusicLib" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B7;}" />
<InfoBar
x:Uid="SettingsPageRemoveInfo"
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="True"
Severity="Success">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</InfoBar>
<ListView
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<controls:SettingsCard>
<controls:SettingsCard.Header>
<controls:SettingsExpander>
<controls:SettingsExpander.Header>
<HyperlinkButton
Click="LocalFolderHyperlinkButton_Click"
Content="{Binding Path, Mode=OneWay}"
Tag="{Binding Path, Mode=OneWay}" />
</controls:SettingsCard.Header>
<StackPanel Orientation="Horizontal">
<HyperlinkButton
x:Uid="SettingsPageRemovePath"
Click="SettingsPageRemovePathButton_Click"
Tag="{Binding}" />
<ToggleSwitch DataContext="{Binding}" IsOn="{Binding IsEnabled, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
</controls:SettingsExpander.Header>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard>
<controls:SettingsCard.Header>
<HyperlinkButton
x:Uid="SettingsPageRemovePath"
Click="SettingsPageRemovePathButton_Click"
Tag="{Binding}" />
</controls:SettingsCard.Header>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageMusicLibRealTimeWatch">
<ToggleSwitch IsOn="{Binding IsRealTimeWatchEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</DataTemplate>
</controls:SettingsExpander.ItemTemplate>
<controls:SettingsExpander.ItemsHeader>
<InfoBar
x:Uid="SettingsPageRemoveInfo"
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="True"
Severity="Success">
</ListView.ItemTemplate>
</ListView>
<interactivity:Interaction.Behaviors>
<controls:SettingsCard x:Uid="SettingsPageAddFolder" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<Button
x:Uid="SettingsPageAddFolderButton"
Command="{x:Bind ViewModel.SelectAndAddFolderCommand}"
CommandParameter="{Binding ElementName=RootGrid}" />
</controls:SettingsCard>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</InfoBar>
</controls:SettingsExpander.ItemsHeader>
<controls:SettingsExpander.ItemsFooter>
<controls:SettingsCard x:Uid="SettingsPageAddFolder" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<Button
x:Uid="SettingsPageAddFolderButton"
Command="{x:Bind ViewModel.SelectAndAddFolderCommand}"
CommandParameter="{Binding ElementName=RootGrid}" />
</controls:SettingsCard>
</controls:SettingsExpander.ItemsFooter>
</controls:SettingsExpander>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.ShortcutTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBox
x:Name="TextBox"
IsReadOnly="True"
KeyDown="TextBox_KeyDown"
Loaded="TextBox_Loaded" />
<Button
Margin="3,0,0,0"
HorizontalAlignment="Right"
Click="ClearButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE894;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,95 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class ShortcutTextBox : UserControl
{
public ShortcutTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty ShortcutProperty =
DependencyProperty.Register(nameof(Shortcut), typeof(List<string>), typeof(ShortcutTextBox), new PropertyMetadata(default));
public List<string> Shortcut
{
get => (List<string>)GetValue(ShortcutProperty);
set => SetValue(ShortcutProperty, value);
}
private void TextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
List<string> shortcut = [];
bool ctrl = InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
bool shift = InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
bool alt = InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
bool win = InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
if (ctrl)
{
shortcut.Add("Ctrl");
}
if (shift)
{
shortcut.Add("Shift");
}
if (alt)
{
shortcut.Add("Alt");
}
if (win)
{
shortcut.Add("Win");
}
if (e.Key != Windows.System.VirtualKey.Control &&
e.Key != Windows.System.VirtualKey.Shift &&
e.Key != Windows.System.VirtualKey.Menu &&
e.Key != Windows.System.VirtualKey.LeftWindows &&
e.Key != Windows.System.VirtualKey.RightWindows)
{
shortcut.Add(e.Key.ToString());
}
Shortcut = shortcut;
UpdateTextBox();
}
private void UpdateTextBox()
{
TextBox.Text = string.Join(" + ", Shortcut);
}
private void TextBox_Loaded(object sender, RoutedEventArgs e)
{
UpdateTextBox();
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
Shortcut = [];
UpdateTextBox();
}
}
}

View File

@@ -32,34 +32,41 @@
<MenuFlyoutItem
x:Uid="SystemTrayLyrics"
Command="{x:Bind ViewModel.OpenLyricsCommand}"
Icon="{ui:FontIcon Glyph=&#xE90B;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE90B;}" />
<MenuFlyoutItem
x:Uid="SystemTrayMusicGallery"
Command="{x:Bind ViewModel.OpenMusicGalleryCommand}"
Icon="{ui:FontIcon Glyph=&#xEA69;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEA69;}" />
<MenuFlyoutItem
x:Uid="SystemTraySettings"
Command="{x:Bind ViewModel.OpenSettingsCommand}"
Icon="{ui:FontIcon Glyph=&#xE713;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="SystemTrayResetWindowPosition"
Command="{x:Bind ViewModel.ResetWindowPositionCommand}"
Icon="{ui:FontIcon Glyph=&#xE923;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE923;}" />
<MenuFlyoutItem
x:Uid="SystemTrayUnlock"
Command="{x:Bind ViewModel.UnlockWindowCommand}"
Icon="{ui:FontIcon Glyph=&#xE785;}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE785;}"
Visibility="{x:Bind ViewModel.IsLyricsWindowLocked, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="SystemTrayRestart"
Command="{x:Bind ViewModel.RestartAppCommand}"
Icon="{ui:FontIcon Glyph=&#xE777;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE777;}" />
<MenuFlyoutItem
x:Uid="SystemTrayExit"
Command="{x:Bind ViewModel.ExitAppCommand}"
Icon="{ui:FontIcon Glyph=&#xE7E8;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7E8;}" />
</MenuFlyout>
</tb:TaskbarIcon.ContextFlyout>
</tb:TaskbarIcon>

View File

@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class ShortcutToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is List<string> shortcut)
{
return string.Join(" + ", shortcut);
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum ShortcutID
{
DesktopLock,
DesktopToggle,
DockToggle,
PictureInPictureToggle,
PlayOrPauseSong,
NextSong,
PreviousSong,
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class BackgroundTaskRunner
{
private CancellationTokenSource? _cts;
public void Run(Func<CancellationToken, Task> taskFactory)
{
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
var token = _cts.Token;
_ = Task.Run(async () =>
{
try
{
await taskFactory(token);
}
catch (OperationCanceledException)
{
}
catch (Exception)
{
}
}, token);
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class DirectoryHelper
{
/// <summary>
/// 递归查找指定文件夹下所有文件(包括子文件夹)。
/// </summary>
/// <param name="folderPath">要查找的文件夹路径</param>
/// <returns>所有文件的完整路径列表</returns>
public static List<string> GetAllFiles(string folderPath, string searchPattern = "*")
{
var files = new List<string>();
if (!Directory.Exists(folderPath))
return files;
try
{
files.AddRange(Directory.GetFiles(folderPath, searchPattern));
foreach (var dir in Directory.GetDirectories(folderPath))
{
files.AddRange(GetAllFiles(dir, searchPattern));
}
}
catch (Exception)
{
// 可根据需要处理异常,如权限不足等
}
return files;
}
}
}
}

View File

@@ -4,6 +4,7 @@ using BetterLyrics.WinUI3.Enums;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Ude;
@@ -78,5 +79,14 @@ namespace BetterLyrics.WinUI3.Helper
return normFileName == normQ1 + normQ2
|| normFileName == normQ2 + normQ1;
}
public static readonly string[] MusicExtensions = {
".mp3", ".aac", ".m4a", ".ogg", ".opus", ".wma", ".amr",
".flac", ".alac", ".ape", ".wv", ".tak",
".wav", ".aiff", ".aif", ".pcm", ".cda", ".dsf", ".dff", ".au", ".snd",
".mid", ".midi", ".mod", ".xm", ".it", ".s3m"
};
public static string MusicSearchPattern => string.Join("|", MusicExtensions.Select(x => $"*{x}"));
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.UI.Xaml;
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,24 +14,70 @@ namespace BetterLyrics.WinUI3.Helper
public class GlobalHotKeyHelper
{
private static Dictionary<int, Action> _hotKeyActions = [];
private static int _nextId = 0;
public static void RegisterHotKey(Window window, User32.HotKeyModifiers modifiers, uint key, Action action)
/// <summary>
/// Register a global hotkey for a specific window type
/// </summary>
/// <typeparam name="T">Target window type</typeparam>
/// <param name="id"></param>
/// <param name="keys"></param>
/// <param name="action"></param>
public static void RegisterHotKey<T>(ShortcutID id, List<string> keys, Action action)
{
if (keys.Count == 0) return;
var window = WindowHelper.GetWindowByWindowType<T>();
if (window == null) return;
HWND hwnd = WindowNative.GetWindowHandle(window);
int id = _nextId++;
User32.RegisterHotKey(hwnd, id, modifiers, key);
_hotKeyActions[id] = action;
User32.HotKeyModifiers modifiers = User32.HotKeyModifiers.MOD_NONE;
VirtualKey key = VirtualKey.None;
foreach (var item in keys)
{
if (item == "Ctrl")
{
modifiers |= User32.HotKeyModifiers.MOD_CONTROL;
}
else if (item == "Shift")
{
modifiers |= User32.HotKeyModifiers.MOD_SHIFT;
}
else if (item == "Alt")
{
modifiers |= User32.HotKeyModifiers.MOD_ALT;
}
else if (item == "Win")
{
modifiers |= User32.HotKeyModifiers.MOD_WIN;
}
else
{
key = (VirtualKey)Enum.Parse(typeof(VirtualKey), item, true);
}
}
User32.RegisterHotKey(hwnd, (int)id, modifiers, (uint)key);
_hotKeyActions[(int)id] = action;
}
public static void UnregisterAllHotKeys(Window window)
public static void UnregisterHotKey<T>(ShortcutID id)
{
var window = WindowHelper.GetWindowByWindowType<T>();
if (window == null) return;
HWND hwnd = WindowNative.GetWindowHandle(window);
foreach (var id in _hotKeyActions.Keys.ToList())
{
User32.UnregisterHotKey(hwnd, id);
_hotKeyActions.Remove(id);
}
User32.UnregisterHotKey(hwnd, (int)id);
_hotKeyActions.Remove((int)id);
}
public static void UpdateHotKey<T>(ShortcutID id, List<string> keys, Action action)
{
UnregisterHotKey<T>(id);
RegisterHotKey<T>(id, keys, action);
}
public static bool TryInvokeAction(ShortcutID id)
{
return TryInvokeAction((int)id);
}
public static bool TryInvokeAction(int id)

View File

@@ -1,43 +0,0 @@
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class LatestOnlyTaskRunner
{
private readonly AsyncLock _mutex = new();
private CancellationTokenSource _cts;
public async Task RunAsync(Func<CancellationToken, Task> action)
{
CancellationTokenSource oldCts;
// 使用 AsyncLock 保证线程安全
using (await _mutex.LockAsync())
{
// 取消旧的
oldCts = _cts;
_cts = new CancellationTokenSource();
}
oldCts?.Cancel();
oldCts?.Dispose();
CancellationToken token = _cts.Token;
try
{
await action(token);
}
catch (OperationCanceledException)
{
// 可以选择忽略取消异常
}
}
}
}

View File

@@ -1,17 +1,9 @@
// 2025/6/23 by Zhe Fang
using Windows.ApplicationModel;
namespace BetterLyrics.WinUI3.Helper
{
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.FileProperties;
public static class MetadataHelper
{
public static string AppVersion

View File

@@ -71,7 +71,7 @@ namespace BetterLyrics.WinUI3.Helper
if (typeof(T) == typeof(LyricsWindow))
{
var lyricsWindow = (LyricsWindow)window;
lyricsWindow.ViewModel.InitLockHotKey();
lyricsWindow.ViewModel.InitShortcuts();
lyricsWindow.AutoSelectLyricsMode();
}
}

View File

@@ -6,15 +6,15 @@ namespace BetterLyrics.WinUI3.Models
{
public partial class LocalMediaFolder : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsRealTimeWatchEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Path { get; set; }
public LocalMediaFolder() { }
public LocalMediaFolder(string path, bool isEnabled)
public LocalMediaFolder(string path)
{
Path = path;
IsEnabled = isEnabled;
}
}
}

View File

@@ -13,7 +13,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 400, 200);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoLockOnDesktopMode { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LockHotKeyIndex { get; set; } = 'U' - 'A'; // Default to 'U' key
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> LockShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "U" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "D" };
public DesktopModeSettings()
{

View File

@@ -14,7 +14,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockPlacement DockPlacement { get; set; } = DockPlacement.Top;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int DockWindowHeight { get; set; } = 64;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string DockMonitorDeviceName { get; set; } = MonitorHelper.GetPrimaryMonitorDeviceName();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string> { "Ctrl", "Shift", "D" };
public DockModeSettings()
{
LyricsDisplayType = LyricsDisplayType.LyricsOnly;

View File

@@ -18,6 +18,9 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDragEverywhereEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsImmersiveMode { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> PlayOrPauseShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "P" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> NextSongShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "Right" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> PreviousSongShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "Left" };
public GeneralSettings() { }
}

View File

@@ -1,4 +1,5 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -8,6 +9,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class PictureInPictureModeSettings : BaseModeSettings
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string>() { "Ctrl", "Shift", "P" };
public PictureInPictureModeSettings()
{
LyricsDisplayType = Enums.LyricsDisplayType.SplitView;

View File

@@ -1,6 +1,7 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -33,34 +34,41 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
byte[]? result = null;
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.AlbumArtSearchProvidersInfo ?? [])
try
{
if (!provider.IsEnabled)
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.AlbumArtSearchProvidersInfo ?? [])
{
continue;
}
if (!provider.IsEnabled)
{
continue;
}
switch (provider.Provider)
{
case AlbumArtSearchProvider.Local:
result = SearchFile(artist, title);
break;
case AlbumArtSearchProvider.SMTC:
result = bytesFromSMTC;
break;
case AlbumArtSearchProvider.iTunes:
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
{
result = await SearchiTunesAsync(artist, album, title, countryCode);
if (result != null) break;
}
break;
default:
break;
}
switch (provider.Provider)
{
case AlbumArtSearchProvider.Local:
result = SearchFile(artist, title);
break;
case AlbumArtSearchProvider.SMTC:
result = bytesFromSMTC;
break;
case AlbumArtSearchProvider.iTunes:
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
{
result = await SearchiTunesAsync(artist, album, title, countryCode);
if (result != null) break;
}
break;
default:
break;
}
if (result != null) return result;
if (result != null) return result;
}
}
catch (Exception)
{
}
return null;
}
@@ -70,9 +78,9 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
try
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path))
{
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
Track track = new(file);
if ((track.Title == title && track.Artist == artist) || FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), artist, title))
@@ -85,9 +93,6 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
}
}
}
catch (Exception)
{
}
}
}
return null;

View File

@@ -52,7 +52,7 @@ namespace BetterLyrics.WinUI3.Services.LibWatcherService
// 移除不再监听的
foreach (var key in _watchers.Keys.ToList())
{
if (!folders.Any(x => x.Path == key && x.IsEnabled))
if (!folders.Any(x => x.Path == key && x.IsEnabled && x.IsRealTimeWatchEnabled))
{
_watchers[key].Dispose();
_watchers.Remove(key);
@@ -62,11 +62,7 @@ namespace BetterLyrics.WinUI3.Services.LibWatcherService
// 添加新的监听
foreach (var folder in folders)
{
if (
!_watchers.ContainsKey(folder.Path)
&& Directory.Exists(folder.Path)
&& folder.IsEnabled
)
if (!_watchers.ContainsKey(folder.Path) && Directory.Exists(folder.Path) && folder.IsEnabled && folder.IsRealTimeWatchEnabled)
{
var watcher = new FileSystemWatcher(folder.Path)
{

View File

@@ -3,6 +3,7 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Providers.Web.Kugou;
@@ -175,7 +176,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
try
{
foreach (var file in Directory.GetFiles(folder.Path, $"*{format.ToFileExtension()}", SearchOption.AllDirectories))
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
{
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
@@ -201,30 +202,21 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
try
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path))
{
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
if ((track.Title == title && track.Artist == artist) || FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
try
{
var plain = TagLib.File.Create(file).Tag.Lyrics;
if (!plain.IsNullOrEmpty())
{
return plain;
}
}
catch (Exception)
var plain = TagLib.File.Create(file).Tag.Lyrics;
if (!plain.IsNullOrEmpty())
{
return plain;
}
}
}
}
catch (Exception)
{
}
}
}
return null;

View File

@@ -10,6 +10,7 @@ using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
@@ -36,6 +37,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<List<string>>>,
IRecipient<PropertyChangedMessage<FullyObservableCollection<AlbumArtSearchProviderInfo>>>
{
private readonly IAlbumArtSearchService _albumArtSearchService;
@@ -52,8 +54,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private readonly MediaManager _mediaManager = new();
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new();
private readonly BackgroundTaskRunner _albumArtRefreshRunner = new();
private readonly BackgroundTaskRunner _onAnyMediaPropertyChangedRunner = new();
private SongInfo? _cachedSongInfo;
private byte[]? _SMTCAlbumArtBytes = null;
@@ -75,22 +77,55 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
InitMediaManager();
InitPlaybackShortcuts();
}
private void InitPlaybackShortcuts()
{
UpdatePlayOrPauseSongShortcut();
UpdatePreviousSongShortcut();
UpdateNextSongShortcut();
}
private void UpdatePlayOrPauseSongShortcut()
{
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.PlayOrPauseSong, _settingsService.AppSettings.GeneralSettings.PlayOrPauseShortcut, () =>
{
if (_cachedIsPlaying)
{
_ = PauseAsync();
}
else
{
_ = PlayAsync();
}
});
}
private void UpdatePreviousSongShortcut()
{
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.PreviousSong, _settingsService.AppSettings.GeneralSettings.PreviousSongShortcut, () =>
{
_ = PreviousAsync();
});
}
private void UpdateNextSongShortcut()
{
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.NextSong, _settingsService.AppSettings.GeneralSettings.NextSongShortcut, () =>
{
_ = NextAsync();
});
}
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
_ = _albumArtRefreshRunner.RunAsync(async token =>
{
await UpdateAlbumArtRelated(token);
});
UpdateAlbumArtRelated();
}
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
_ = _albumArtRefreshRunner.RunAsync(async token =>
{
await UpdateAlbumArtRelated(token);
});
UpdateAlbumArtRelated();
}
private void MediaSourceProvidersInfo_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
@@ -98,10 +133,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
switch (e.PropertyName)
{
case nameof(MediaSourceProviderInfo.AlbumArtSearchProvidersInfo):
_ = _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
UpdateAlbumArtRelated();
break;
default:
break;
@@ -213,7 +245,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_cachedSongInfo = null;
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
_onAnyMediaPropertyChangedRunner.Run(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
@@ -225,10 +257,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_SMTCAlbumArtBytes = null;
await _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
UpdateAlbumArtRelated();
if (!token.IsCancellationRequested)
{
@@ -237,7 +266,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
});
}
}).ConfigureAwait(false);
});
}
else
{
@@ -261,7 +290,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
_onAnyMediaPropertyChangedRunner.Run(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
@@ -284,10 +313,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_SMTCAlbumArtBytes = null;
}
await _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
UpdateAlbumArtRelated();
if (!token.IsCancellationRequested)
{
@@ -296,7 +322,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
});
}
}).ConfigureAwait(false);
});
}
}
@@ -361,47 +387,51 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
MediaManager_OnAnyPlaybackStateChanged(focusedSession, focusedSession.ControlSession.GetPlaybackInfo());
}
private async Task UpdateAlbumArtRelated(CancellationToken token)
private void UpdateAlbumArtRelated()
{
if (_cachedSongInfo == null)
_albumArtRefreshRunner.Run(async (token) =>
{
_logger.LogWarning("Cached song info is null, cannot update album art.");
return;
}
if (_cachedSongInfo == null)
{
_logger.LogWarning("Cached song info is null, cannot update album art.");
return;
}
byte[]? bytes = await _albumArtSearchService.SearchAsync(
SongInfo?.SourceAppUserModelId ?? "",
_cachedSongInfo.Title,
_cachedSongInfo.Artist,
_cachedSongInfo?.Album ?? string.Empty,
_SMTCAlbumArtBytes
);
token.ThrowIfCancellationRequested();
if (bytes == null)
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
byte[]? bytes = await _albumArtSearchService.SearchAsync(
SongInfo?.SourceAppUserModelId ?? "",
_cachedSongInfo.Title,
_cachedSongInfo.Artist,
_cachedSongInfo?.Album ?? string.Empty,
_SMTCAlbumArtBytes
);
token.ThrowIfCancellationRequested();
}
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
if (bytes == null)
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
token.ThrowIfCancellationRequested();
}
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
}
@@ -517,7 +547,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
return _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == _cachedSongInfo?.SourceAppUserModelId)?.FirstOrDefault();
}
public async void Receive(PropertyChangedMessage<bool> message)
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is MediaSourceProviderInfo)
{
@@ -530,10 +560,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(AlbumArtSearchProviderInfo.IsEnabled))
{
await _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
UpdateAlbumArtRelated();
}
}
}
@@ -541,5 +568,24 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public void Receive(PropertyChangedMessage<FullyObservableCollection<AlbumArtSearchProviderInfo>> message)
{
}
public void Receive(PropertyChangedMessage<List<string>> message)
{
if (message.Sender is GeneralSettings)
{
if (message.PropertyName == nameof(GeneralSettings.PlayOrPauseShortcut))
{
UpdatePlayOrPauseSongShortcut();
}
else if (message.PropertyName == nameof(GeneralSettings.PreviousSongShortcut))
{
UpdatePreviousSongShortcut();
}
else if (message.PropertyName == nameof(GeneralSettings.NextSongShortcut))
{
UpdateNextSongShortcut();
}
}
}
}
}

View File

@@ -178,7 +178,7 @@
<value>Dock mode</value>
</data>
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
<value>To unlock after locking, go to the system tray to unlock or press</value>
<value>Lock</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>More</value>
@@ -871,6 +871,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
<value>Local media library</value>
</data>
<data name="SettingsPageMusicLibRealTimeWatch.Header" xml:space="preserve">
<value>Listen to file changes in real time</value>
</data>
<data name="SettingsPageNextSongHotKey.Header" xml:space="preserve">
<value>Next track shortcut keys</value>
</data>
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
<value>None</value>
</data>
@@ -895,9 +901,18 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>No playback source captured</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>Play</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
</data>
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
<value>Play and Pause Shortcuts</value>
</data>
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
<value>Shortcut keys for the previous track</value>
</data>
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
<value>QQ feedback &amp; chat group</value>
</data>
@@ -982,6 +997,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
<value>Title bar size</value>
</data>
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>Switch in and cut out shortcut keys</value>
</data>
<data name="SettingsPageTranslation.Text" xml:space="preserve">
<value>Lyrics translation</value>
</data>

View File

@@ -178,7 +178,7 @@
<value>ドックモード</value>
</data>
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除または押します</value>
<value>ロック</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>もっと</value>
@@ -871,6 +871,12 @@
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
<value>地元のメディア図書館</value>
</data>
<data name="SettingsPageMusicLibRealTimeWatch.Header" xml:space="preserve">
<value>ファイルの変更をリアルタイムで聞いてください</value>
</data>
<data name="SettingsPageNextSongHotKey.Header" xml:space="preserve">
<value>次のトラックショートカットキー</value>
</data>
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
<value>なし</value>
</data>
@@ -895,9 +901,18 @@
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>キャプチャされた再生ソースはありません</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>遊ぶ</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</value>
</data>
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
<value>ショートカットを再生して一時停止します</value>
</data>
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
<value>前のトラックのショートカットキー</value>
</data>
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
<value>QQフィードバックチャットグループ</value>
</data>
@@ -982,6 +997,9 @@
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
<value>タイトルバーサイズ</value>
</data>
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>ショートカットキーを切り込んで切り取ります</value>
</data>
<data name="SettingsPageTranslation.Text" xml:space="preserve">
<value>歌詞翻訳</value>
</data>

View File

@@ -178,7 +178,7 @@
<value>도크 모드</value>
</data>
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하거나 누릅니다.</value>
<value>잠금</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>더</value>
@@ -871,6 +871,12 @@
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
<value>로컬 미디어 라이브러리</value>
</data>
<data name="SettingsPageMusicLibRealTimeWatch.Header" xml:space="preserve">
<value>실시간으로 파일 변경을 듣습니다</value>
</data>
<data name="SettingsPageNextSongHotKey.Header" xml:space="preserve">
<value>다음 트랙 바로 가기 키</value>
</data>
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
<value>없음</value>
</data>
@@ -895,9 +901,18 @@
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>재생 소스가 캡처되지 않았습니다</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>놀다</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</value>
</data>
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
<value>바로 가기를 재생하고 일시 중지합니다</value>
</data>
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
<value>이전 트랙의 바로 가기 키</value>
</data>
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
<value>QQ 피드백 및 채팅 그룹</value>
</data>
@@ -982,6 +997,9 @@
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
<value>제목 바 크기</value>
</data>
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>바로 가기 키를 자르고 잘라냅니다</value>
</data>
<data name="SettingsPageTranslation.Text" xml:space="preserve">
<value>가사 번역</value>
</data>

View File

@@ -178,7 +178,7 @@
<value>停靠模式</value>
</data>
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
<value>锁定后解锁,请转到系统托盘解锁或按下</value>
<value>锁定</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>更多</value>
@@ -871,6 +871,12 @@
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
<value>本地媒体库</value>
</data>
<data name="SettingsPageMusicLibRealTimeWatch.Header" xml:space="preserve">
<value>实时监听文件变化</value>
</data>
<data name="SettingsPageNextSongHotKey.Header" xml:space="preserve">
<value>下一首曲目快捷键</value>
</data>
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
<value>无</value>
</data>
@@ -895,9 +901,18 @@
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>没有捕获的播放源</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>播放</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
<value>播放与暂停快捷键</value>
</data>
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
<value>上一首曲目快捷键</value>
</data>
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
<value>QQ 反馈交流群</value>
</data>
@@ -982,6 +997,9 @@
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
<value>标题栏大小</value>
</data>
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>切入与切出快捷键</value>
</data>
<data name="SettingsPageTranslation.Text" xml:space="preserve">
<value>歌词翻译</value>
</data>

View File

@@ -178,7 +178,7 @@
<value>停靠模式</value>
</data>
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
<value>鎖定後解鎖,請轉到系統托盤解鎖或按下</value>
<value>鎖定</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>更多</value>
@@ -871,6 +871,12 @@
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
<value>本地音樂資料庫</value>
</data>
<data name="SettingsPageMusicLibRealTimeWatch.Header" xml:space="preserve">
<value>實時監聽文件變化</value>
</data>
<data name="SettingsPageNextSongHotKey.Header" xml:space="preserve">
<value>下一首曲目快捷鍵</value>
</data>
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
<value>無</value>
</data>
@@ -895,9 +901,18 @@
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>沒有捕獲的播放源</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>播放</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
<value>播放與暫停快捷鍵</value>
</data>
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
<value>上一首曲目快捷鍵</value>
</data>
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
<value>QQ 回饋交流群</value>
</data>
@@ -982,6 +997,9 @@
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
<value>標題列大小</value>
</data>
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>切入與切出快捷鍵</value>
</data>
<data name="SettingsPageTranslation.Text" xml:space="preserve">
<value>歌詞翻譯</value>
</data>

View File

@@ -64,7 +64,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
_ = _refreshLyricsRunner.RunAsync(async tokne =>
_refreshLyricsRunner.Run(async tokne =>
{
await RefreshLyricsAsync(tokne);
});
@@ -74,7 +74,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
_ = _refreshLyricsRunner.RunAsync(async tokne =>
_refreshLyricsRunner.Run(async tokne =>
{
await RefreshLyricsAsync(tokne);
});
@@ -86,7 +86,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
case nameof(MediaSourceProviderInfo.LyricsSearchProvidersInfo):
_logger.LogInformation("MediaSourceProviderInfo.LyricsSearchProvidersInfo changed, refreshing lyrics.");
_ = _refreshLyricsRunner.RunAsync(async token =>
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});

View File

@@ -94,7 +94,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
if (message.PropertyName == nameof(LyricsSearchProviderInfo.IsEnabled))
{
_logger.LogInformation("LyricsSearchProviderInfo.IsEnabled changed, refreshing lyrics.");
_ = _refreshLyricsRunner.RunAsync(async token =>
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});

View File

@@ -178,8 +178,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
FontWeight = FontWeights.ExtraBlack,
};
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
private LatestOnlyTaskRunner _showTranslationsRunner = new();
private BackgroundTaskRunner _refreshLyricsRunner = new();
private BackgroundTaskRunner _showTranslationsRunner = new();
private LyricsLayoutOrientation _lyricsLayoutOrientation;
@@ -322,7 +322,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
{
_logger.LogInformation("Music library files changed: {ChangeType} {FilePath}, refreshing lyrics...", e.ChangeType, e.FilePath);
_ = _refreshLyricsRunner.RunAsync(async token =>
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});
@@ -374,7 +374,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_songInfoOpacityTransition.StartTransition(1f);
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
_ = _refreshLyricsRunner.RunAsync(async token =>
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});
@@ -430,7 +430,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
IsTranslating = true;
if (_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled)
{
_ = _refreshLyricsRunner.RunAsync(async token =>
_refreshLyricsRunner.Run(async token =>
{
await SetDisplayedAlongWithTranslationsAsync(token);
IsTranslating = false;

View File

@@ -18,6 +18,8 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using Vanara.PInvoke;
using Windows.System;
using Windows.UI;
@@ -29,6 +31,7 @@ namespace BetterLyrics.WinUI3
public partial class LyricsWindowViewModel
: BaseWindowViewModel,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<List<string>>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
@@ -52,6 +55,7 @@ namespace BetterLyrics.WinUI3
_mediaSessionsService = mediaSessionsService;
_liveStatesService = liveStatesService;
AppSettings = _settingsService.AppSettings;
LiveStates = _liveStatesService.LiveStates;
_dockMonitorDeviceName = _settingsService.AppSettings.DockModeSettings.DockMonitorDeviceName;
@@ -70,6 +74,9 @@ namespace BetterLyrics.WinUI3
UpdateDockOrDesktopWindow();
}
[ObservableProperty]
public partial AppSettings AppSettings { get; set; }
[ObservableProperty]
public partial LiveStates LiveStates { get; set; }
@@ -98,8 +105,18 @@ namespace BetterLyrics.WinUI3
[NotifyPropertyChangedRecipients]
public partial bool IsMouseWithinWindow { get; set; } = false;
[ObservableProperty]
public partial string LockHotKey { get; set; } = "";
[ObservableProperty] public partial Visibility AOTFlyoutItemVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial Visibility FullScreenFlyoutItemVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial Visibility LockButtonVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial Visibility DesktopFlyoutItemVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial Visibility PIPFlyoutItemVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial Visibility DockFlyoutItemVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial bool IsAOTFlyoutItemChecked { get; set; } = false;
[ObservableProperty] public partial bool IsFullScreenFlyoutItemChecked { get; set; } = false;
[ObservableProperty] public partial bool IsDesktopFlyoutItemChecked { get; set; } = false;
[ObservableProperty] public partial bool IsPIPFlyoutItemChecked { get; set; } = false;
[ObservableProperty] public partial bool IsDockFlyoutItemChecked { get; set; } = false;
private void UpdateDockOrDesktopWindow()
{
@@ -188,34 +205,129 @@ namespace BetterLyrics.WinUI3
UpdateDockOrDesktopWindow();
}
}
else if (message.Sender is DesktopModeSettings)
{
if (message.PropertyName == nameof(DesktopModeSettings.LockHotKeyIndex))
{
UpdateLockHotKey(message.NewValue);
}
}
}
private void UpdateLockHotKey(int hotKeyIndex)
public void InitShortcuts()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
UpdateDesktopLockShortcut();
UpdateDesktopToggleShortcut();
UpdateDockToggleShortcut();
UpdatePictureInPictureToggleShortcut();
}
GlobalHotKeyHelper.UnregisterAllHotKeys(window);
GlobalHotKeyHelper.RegisterHotKey(
window,
User32.HotKeyModifiers.MOD_CONTROL | User32.HotKeyModifiers.MOD_ALT,
(uint)(hotKeyIndex + (int)VirtualKey.A),
private void UpdateDesktopLockShortcut()
{
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.DesktopLock);
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.DesktopLock,
_settingsService.AppSettings.DesktopModeSettings.LockShortcut,
() =>
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DesktopMode)
{
ToggleLockWindowCommand.Execute(null);
ToggleLockWindow();
}
}
);
LockHotKey = ((VirtualKey)(hotKeyIndex + (int)VirtualKey.A)).ToString();
}
private void UpdateDesktopToggleShortcut()
{
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.DesktopToggle);
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.DesktopToggle,
_settingsService.AppSettings.DesktopModeSettings.ToggleShortcut,
() =>
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DesktopMode ||
LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.StandardMode)
{
ToggleDesktopMode();
}
}
);
}
private void UpdateDockToggleShortcut()
{
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.DockToggle);
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.DockToggle,
_settingsService.AppSettings.DockModeSettings.ToggleShortcut,
() =>
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode ||
LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.StandardMode)
{
ToggleDockMode();
}
}
);
}
private void UpdatePictureInPictureToggleShortcut()
{
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.PictureInPictureToggle);
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.PictureInPictureToggle,
_settingsService.AppSettings.PictureInPictureModeSettings.ToggleShortcut,
() =>
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.PictureInPictureMode ||
LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.StandardMode)
{
TogglePictureInPictureMode();
}
}
);
}
private void SetFullscreenTitleBarControlsStatus()
{
AOTFlyoutItemVisibility = LockButtonVisibility = DesktopFlyoutItemVisibility = PIPFlyoutItemVisibility = DockFlyoutItemVisibility = Visibility.Collapsed;
IsFullScreenFlyoutItemChecked = true;
IsImmersiveMode = true;
}
private void SetPIPModeTitleBarControlsStatus()
{
AOTFlyoutItemVisibility = DesktopFlyoutItemVisibility = FullScreenFlyoutItemVisibility = DockFlyoutItemVisibility = LockButtonVisibility = Visibility.Collapsed;
IsImmersiveMode = true;
IsPIPFlyoutItemChecked = true;
}
private void SetDockModeTitleBarControlsStatus()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
var overlappedPresenter = (OverlappedPresenter)window.AppWindow.Presenter;
overlappedPresenter.IsMinimizable = overlappedPresenter.IsMaximizable = false;
AOTFlyoutItemVisibility = DesktopFlyoutItemVisibility = LockButtonVisibility = FullScreenFlyoutItemVisibility = PIPFlyoutItemVisibility = Visibility.Collapsed;
IsImmersiveMode = true;
IsDockFlyoutItemChecked = true;
}
private void SetDesktopModeTitleBarControlsStatus()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
var overlappedPresenter = (OverlappedPresenter)window.AppWindow.Presenter;
overlappedPresenter.IsMinimizable = overlappedPresenter.IsMaximizable = false;
DockFlyoutItemVisibility = AOTFlyoutItemVisibility = FullScreenFlyoutItemVisibility = PIPFlyoutItemVisibility = Visibility.Collapsed;
LockButtonVisibility = Visibility.Visible;
IsDesktopFlyoutItemChecked = true;
}
public void SetStandardModeTitleBarControlsStatus()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
var overlappedPresenter = (OverlappedPresenter)window.AppWindow.Presenter;
overlappedPresenter.IsMinimizable = overlappedPresenter.IsMaximizable = true;
AOTFlyoutItemVisibility = DesktopFlyoutItemVisibility = DockFlyoutItemVisibility = PIPFlyoutItemVisibility = FullScreenFlyoutItemVisibility = Visibility.Visible;
LockButtonVisibility = Visibility.Collapsed;
IsFullScreenFlyoutItemChecked = IsDesktopFlyoutItemChecked = IsDockFlyoutItemChecked = IsPIPFlyoutItemChecked = false;
IsAOTFlyoutItemChecked = overlappedPresenter.IsAlwaysOnTop;
IsImmersiveMode = _settingsService.AppSettings.GeneralSettings.IsImmersiveMode;
}
public void StartWatchWindowColorChange()
@@ -267,13 +379,7 @@ namespace BetterLyrics.WinUI3
}
}
public void InitLockHotKey()
{
UpdateLockHotKey(_settingsService.AppSettings.DesktopModeSettings.LockHotKeyIndex);
}
[RelayCommand]
private void ToggleLockWindow()
public void ToggleLockWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
@@ -294,8 +400,7 @@ namespace BetterLyrics.WinUI3
UpdateDockOrDesktopWindow();
}
[RelayCommand]
private void ToggleDesktopMode()
public void ToggleDesktopMode()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
@@ -307,15 +412,24 @@ namespace BetterLyrics.WinUI3
{
DesktopModeHelper.Enable(window);
StartWatchWindowColorChange();
if (_settingsService.AppSettings.DesktopModeSettings.AutoLockOnDesktopMode)
{
ToggleLockWindow();
}
SetDesktopModeTitleBarControlsStatus();
}
else
{
if (IsLyricsWindowLocked)
{
ToggleLockWindow();
}
DesktopModeHelper.Disable(window);
SetStandardModeTitleBarControlsStatus();
}
}
[RelayCommand]
private void ToggleDockMode()
public void ToggleDockMode()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
@@ -328,19 +442,65 @@ namespace BetterLyrics.WinUI3
window.Restore();
DockModeHelper.Enable(window, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
StartWatchWindowColorChange();
SetDockModeTitleBarControlsStatus();
}
else
{
DockModeHelper.Disable(window);
SetStandardModeTitleBarControlsStatus();
}
UpdateDockOrDesktopWindow();
}
[RelayCommand]
private void TogglePictureInPictureMode()
public void TogglePictureInPictureMode()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
LiveStates.ToggleLyricsWindowMode(LyricsWindowMode.PictureInPictureMode);
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.PictureInPictureMode)
{
window.AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
SetPIPModeTitleBarControlsStatus();
}
else
{
window.AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
SetStandardModeTitleBarControlsStatus();
}
}
public void ToggleAlwaysOnTop()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
if (window.AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.IsAlwaysOnTop = !presenter.IsAlwaysOnTop;
IsAOTFlyoutItemChecked = presenter.IsAlwaysOnTop;
}
}
public void ToggleFullscreen()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
switch (window.AppWindow.Presenter.Kind)
{
case AppWindowPresenterKind.FullScreen:
window.AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
SetStandardModeTitleBarControlsStatus();
break;
case AppWindowPresenterKind.Overlapped:
window.AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
SetFullscreenTitleBarControlsStatus();
break;
default:
break;
}
}
[RelayCommand]
@@ -372,5 +532,37 @@ namespace BetterLyrics.WinUI3
}
}
}
public void Receive(PropertyChangedMessage<List<string>> message)
{
if (message.Sender is DesktopModeSettings)
{
if (message.PropertyName == nameof(DesktopModeSettings.LockShortcut))
{
UpdateDesktopLockShortcut();
}
}
else if (message.Sender is DockModeSettings)
{
if (message.PropertyName == nameof(DockModeSettings.ToggleShortcut))
{
UpdateDockToggleShortcut();
}
}
else if (message.Sender is PictureInPictureModeSettings)
{
if (message.PropertyName == nameof(PictureInPictureModeSettings.ToggleShortcut))
{
UpdatePictureInPictureToggleShortcut();
}
}
else if (message.Sender is DesktopModeSettings)
{
if (message.PropertyName == nameof(DesktopModeSettings.ToggleShortcut))
{
UpdateDesktopToggleShortcut();
}
}
}
}
}

View File

@@ -72,7 +72,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
AppSettings.LocalMediaFolders.Add(new LocalMediaFolder(path, true));
AppSettings.LocalMediaFolders.Add(new LocalMediaFolder(path));
}
}

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services;
@@ -265,31 +266,27 @@ namespace BetterLyrics.WinUI3.ViewModels
Task.Run(() =>
{
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
try
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
{
try
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path))
{
try
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
Track track = new(file);
if (track.Duration <= 0) continue;
_tracks.Add(track);
}
catch (Exception)
{
continue;
}
}
}
catch (Exception)
{
}
}
}
catch (Exception)
{
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{

View File

@@ -48,7 +48,8 @@
<Button
x:Name="ClickThroughButton"
Click="ClickThroughButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
Style="{StaticResource TitleBarButtonStyle}"
Visibility="{x:Bind ViewModel.LockButtonVisibility, Mode=OneWay}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
@@ -58,15 +59,6 @@
<ToolTip.Content>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="HostWindowLockToolTip" />
<TextBlock
Margin="6,0"
VerticalAlignment="Center"
Opacity="0.7"
Text="Ctrl + Alt + " />
<TextBlock
VerticalAlignment="Center"
Opacity="0.7"
Text="{x:Bind ViewModel.LockHotKey, Mode=OneWay}" />
</StackPanel>
</ToolTip.Content>
</ToolTip>
@@ -87,46 +79,62 @@
x:Name="AOTFlyoutItem"
x:Uid="BaseWindowAOTFlyoutItem"
Click="AOTFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xE718;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE718;}"
IsChecked="{x:Bind ViewModel.IsAOTFlyoutItemChecked, Mode=OneWay}"
Visibility="{x:Bind ViewModel.AOTFlyoutItemVisibility, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="FullScreenFlyoutItem"
x:Uid="BaseWindowFullScreenFlyoutItem"
Click="FullScreenFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xE740;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE740;}"
IsChecked="{x:Bind ViewModel.IsFullScreenFlyoutItemChecked, Mode=OneWay}"
Visibility="{x:Bind ViewModel.FullScreenFlyoutItemVisibility, Mode=OneWay}" />
<MenuFlyoutSeparator />
<ToggleMenuFlyoutItem
x:Name="DockFlyoutItem"
x:Uid="HostWindowDockFlyoutItem"
Click="DockFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xE66A;}">
</ToggleMenuFlyoutItem>
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7B5;}"
IsChecked="{x:Bind ViewModel.IsDockFlyoutItemChecked, Mode=OneWay}"
Visibility="{x:Bind ViewModel.DockFlyoutItemVisibility, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="DesktopFlyoutItem"
x:Uid="HostWindowDesktopFlyoutItem"
Click="DesktopFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xE66C;}">
</ToggleMenuFlyoutItem>
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE75A;}"
IsChecked="{x:Bind ViewModel.IsDesktopFlyoutItemChecked, Mode=OneWay}"
Visibility="{x:Bind ViewModel.DesktopFlyoutItemVisibility, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="MiniFlyoutItem"
x:Name="PIPFlyoutItem"
x:Uid="BaseWindowMiniFlyoutItem"
Click="MiniFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xEE49;}" />
Click="PIPFlyoutItem_Click"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEE49;}"
IsChecked="{x:Bind ViewModel.IsPIPFlyoutItemChecked, Mode=OneWay}"
Visibility="{x:Bind ViewModel.PIPFlyoutItemVisibility, Mode=OneWay}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Name="MusicGalleryFlyoutItem"
x:Uid="SystemTrayMusicGallery"
Click="MusicGalleryMenuFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xEA69;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEA69;}" />
<MenuFlyoutItem
x:Name="SettingsFlyoutItem"
x:Uid="SystemTraySettings"
Click="SettingsMenuFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xE713;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="SystemTrayExit"
Click="ExitAppMenuFlyoutItem_Click"
Icon="{ui:FontIcon Glyph=&#xE7E8;}" />
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7E8;}" />
</MenuFlyout>
</Button.Flyout>
</Button>

View File

@@ -69,25 +69,20 @@ namespace BetterLyrics.WinUI3.Views
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
public void AutoSelectLyricsMode(LyricsWindowMode? type = null, bool? autoLook = null)
public void AutoSelectLyricsMode(LyricsWindowMode? type = null)
{
type ??= _settingsService.AppSettings.GeneralSettings.AutoStartWindowType;
switch (type!)
{
case LyricsWindowMode.StandardMode:
AppWindow.MoveAndResize(_settingsService.AppSettings.StandardModeSettings.WindowBounds.ToRectInt32());
ViewModel.SetStandardModeTitleBarControlsStatus();
break;
case LyricsWindowMode.DockMode:
DockFlyoutItem.IsChecked = true;
ViewModel.ToggleDockModeCommand.Execute(null);
ViewModel.ToggleDockMode();
break;
case LyricsWindowMode.DesktopMode:
DesktopFlyoutItem.IsChecked = true;
ViewModel.ToggleDesktopModeCommand.Execute(null);
if (autoLook == null && _settingsService.AppSettings.DesktopModeSettings.AutoLockOnDesktopMode)
{
ViewModel.ToggleLockWindowCommand.Execute(null);
}
ViewModel.ToggleDesktopMode();
break;
default:
break;
@@ -96,15 +91,11 @@ namespace BetterLyrics.WinUI3.Views
private void AOTFlyoutItem_Click(object sender, RoutedEventArgs e)
{
var overlappedPresenter = (OverlappedPresenter)AppWindow.Presenter;
overlappedPresenter.IsAlwaysOnTop = !overlappedPresenter.IsAlwaysOnTop;
ViewModel.ToggleAlwaysOnTop();
}
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
if (args.DidPresenterChange)
UpdateTitleBarWindowButtonsVisibility();
if (args.DidPositionChange || args.DidSizeChange)
{
var size = AppWindow.Size;
@@ -120,10 +111,7 @@ namespace BetterLyrics.WinUI3.Views
{
_settingsService.AppSettings.DesktopModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
}
else if (ViewModel.LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode)
{
}
else if (ViewModel.LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode) { }
else
{
_settingsService.AppSettings.StandardModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
@@ -134,34 +122,13 @@ namespace BetterLyrics.WinUI3.Views
private void FullScreenFlyoutItem_Click(object sender, RoutedEventArgs e)
{
switch (AppWindow.Presenter.Kind)
{
case AppWindowPresenterKind.Default:
break;
case AppWindowPresenterKind.CompactOverlay:
break;
case AppWindowPresenterKind.FullScreen:
AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
break;
case AppWindowPresenterKind.Overlapped:
AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
break;
default:
break;
}
ViewModel.ToggleFullscreen();
}
private void MiniFlyoutItem_Click(object sender, RoutedEventArgs e)
private void PIPFlyoutItem_Click(object sender, RoutedEventArgs e)
{
ViewModel.TogglePictureInPictureModeCommand.Execute(null);
if (MiniFlyoutItem.IsChecked)
{
AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
}
else
{
AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
}
ViewModel.TogglePictureInPictureMode();
}
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
@@ -169,82 +136,6 @@ namespace BetterLyrics.WinUI3.Views
WindowHelper.OpenWindow<SettingsWindow>();
}
private void UpdateTitleBarWindowButtonsVisibility()
{
switch (AppWindow.Presenter.Kind)
{
case AppWindowPresenterKind.Default:
break;
case AppWindowPresenterKind.CompactOverlay:
AOTFlyoutItem.Visibility = DesktopFlyoutItem.Visibility = FullScreenFlyoutItem.Visibility = DockFlyoutItem.Visibility =
ClickThroughButton.Visibility = Visibility.Collapsed;
ViewModel.IsImmersiveMode = true;
break;
case AppWindowPresenterKind.FullScreen:
AOTFlyoutItem.Visibility =
ClickThroughButton.Visibility =
DesktopFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
Visibility.Collapsed;
FullScreenFlyoutItem.IsChecked = true;
ViewModel.IsImmersiveMode = true;
break;
case AppWindowPresenterKind.Overlapped:
DockFlyoutItem.Visibility = Visibility.Visible;
var overlappedPresenter = (OverlappedPresenter)AppWindow.Presenter;
if (DockFlyoutItem.IsChecked)
{
overlappedPresenter.IsMinimizable =
overlappedPresenter.IsMaximizable = false;
AOTFlyoutItem.Visibility =
DesktopFlyoutItem.Visibility =
ClickThroughButton.Visibility =
FullScreenFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
Visibility.Collapsed;
ViewModel.IsImmersiveMode = true;
}
else if (DesktopFlyoutItem.IsChecked)
{
overlappedPresenter.IsMinimizable =
overlappedPresenter.IsMaximizable = false;
DockFlyoutItem.Visibility =
AOTFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
Visibility.Collapsed;
ClickThroughButton.Visibility = Visibility.Visible;
}
else
{
overlappedPresenter.IsMinimizable =
overlappedPresenter.IsMaximizable = true;
AOTFlyoutItem.Visibility =
DesktopFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
Visibility.Visible;
FullScreenFlyoutItem.IsChecked = false;
ClickThroughButton.Visibility = Visibility.Collapsed;
AOTFlyoutItem.IsChecked = overlappedPresenter.IsAlwaysOnTop;
ViewModel.IsImmersiveMode = _settingsService.AppSettings.GeneralSettings.IsImmersiveMode;
}
break;
default:
break;
}
}
private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (ViewModel.IsImmersiveMode)
@@ -279,18 +170,17 @@ namespace BetterLyrics.WinUI3.Views
private void ClickThroughButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.ToggleLockWindowCommand.Execute(null);
ViewModel.ToggleLockWindow();
}
private void DockFlyoutItem_Click(object sender, RoutedEventArgs e)
{
ViewModel.ToggleDockModeCommand.Execute(null);
ViewModel.ToggleDockMode();
}
private void DesktopFlyoutItem_Click(object sender, RoutedEventArgs e)
{
ViewModel.ToggleDesktopModeCommand.Execute(null);
UpdateTitleBarWindowButtonsVisibility();
ViewModel.ToggleDesktopMode();
}
private void MusicGalleryMenuFlyoutItem_Click(object sender, RoutedEventArgs e)