diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest index 71bbdd0..2edf4f4 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="1.0.5.0" /> @@ -30,6 +30,8 @@ + + diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml index 2e54d77..87900e9 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml @@ -45,6 +45,7 @@ + diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs index 610ebdc..ae0af5f 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs @@ -1,12 +1,11 @@ -using System.Text; +using System; +using System.Text; +using System.Threading.Tasks; using BetterInAppLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.Helper; -using BetterLyrics.WinUI3.Rendering; -using BetterLyrics.WinUI3.Services.Database; -using BetterLyrics.WinUI3.Services.Playback; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; +using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services; using BetterLyrics.WinUI3.ViewModels; -using BetterLyrics.WinUI3.Views; using CommunityToolkit.Mvvm.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -14,7 +13,6 @@ using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.Windows.ApplicationModel.Resources; using Serilog; -using WinUIEx; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -48,19 +46,46 @@ namespace BetterLyrics.WinUI3 ResourceLoader = new ResourceLoader(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - Helper.AppInfo.EnsureDirectories(); + AppInfo.EnsureDirectories(); ConfigureServices(); _logger = Ioc.Default.GetService>()!; UnhandledException += App_UnhandledException; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException; + TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; + } + + private void CurrentDomain_FirstChanceException( + object? sender, + System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e + ) + { + _logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException"); + } + + private void TaskScheduler_UnobservedTaskException( + object? sender, + UnobservedTaskExceptionEventArgs e + ) + { + _logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException"); + } + + private void CurrentDomain_UnhandledException( + object sender, + System.UnhandledExceptionEventArgs e + ) + { + _logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException"); } private static void ConfigureServices() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.File(Helper.AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day) + .WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day) .CreateLogger(); // Register services @@ -73,8 +98,9 @@ namespace BetterLyrics.WinUI3 }) // Services .AddSingleton() - .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() // ViewModels .AddTransient() .AddSingleton() diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/CustomTransform.bin b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/CustomTransform.bin deleted file mode 100644 index 20ae6e7..0000000 Binary files a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/CustomTransform.bin and /dev/null differ diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj index 20c02a8..37e72f5 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj @@ -10,9 +10,6 @@ enable preview - - - @@ -21,32 +18,36 @@ - + - + - - - + - + @@ -67,6 +68,10 @@ + + + + False diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Converter/LyricsSearchProviderToDisplayNameConverter.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Converter/LyricsSearchProviderToDisplayNameConverter.cs new file mode 100644 index 0000000..36e0772 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Converter/LyricsSearchProviderToDisplayNameConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BetterLyrics.WinUI3.Enums; +using Microsoft.UI.Xaml.Data; + +namespace BetterLyrics.WinUI3.Converter +{ + public class LyricsSearchProviderToDisplayNameConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is LyricsSearchProvider provider) + { + return provider switch + { + LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString( + "LyricsSearchProviderLocalLrcFile" + ), + LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString( + "LyricsSearchProviderLocalMusicFile" + ), + LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString( + "LyricsSearchProviderLrcLib" + ), + LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString( + "LyricsSearchProviderEslrcFile" + ), + LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString( + "LyricsSearchProviderTtmlFile" + ), + _ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null), + }; + } + return ""; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs index 7b03a91..8a31c33 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs @@ -12,5 +12,7 @@ namespace BetterLyrics.WinUI3.Enums English, SimplifiedChinese, TraditionalChinese, + Japanese, + Korean, } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LocalSearchTargetProps.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LocalSearchTargetProps.cs new file mode 100644 index 0000000..4372b21 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LocalSearchTargetProps.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Enums +{ + public enum LocalSearchTargetProps + { + LyricsOnly, + LyricsAndAlbumArt, + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsFormat.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsFormat.cs new file mode 100644 index 0000000..17313cb --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsFormat.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Enums +{ + public enum LyricsFormat + { + Lrc, + Eslrc, + Ttml, + } + + public static class LyricsFormatExtensions + { + public static string ToFileExtension(this LyricsFormat format) + { + return format switch + { + LyricsFormat.Lrc => ".lrc", + LyricsFormat.Eslrc => ".eslrc", + LyricsFormat.Ttml => ".ttml", + _ => throw new ArgumentOutOfRangeException(nameof(format), format, null), + }; + } + + public static LyricsFormat? Detect(string content) + { + if ( + content.StartsWith("" + ) + ) + { + return LyricsFormat.Lrc; + } + else + { + return null; + } + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs new file mode 100644 index 0000000..97adfa7 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs @@ -0,0 +1,11 @@ +namespace BetterLyrics.WinUI3.Enums +{ + public enum LyricsSearchProvider + { + LrcLib, + LocalMusicFile, + LocalLrcFile, + LocalEslrcFile, + LocalTtmlFile, + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsStatus.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsStatus.cs new file mode 100644 index 0000000..e9a6418 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Enums +{ + public enum LyricsStatus + { + NotFound, + Found, + Loading, + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/MusicSearchMatchMode.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/MusicSearchMatchMode.cs new file mode 100644 index 0000000..874c083 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/MusicSearchMatchMode.cs @@ -0,0 +1,8 @@ +namespace BetterLyrics.WinUI3.Enums +{ + public enum MusicSearchMatchMode + { + TitleAndArtist, + TitleArtistAlbumAndDuration, + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs index 347fda4..f865dce 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs @@ -9,5 +9,5 @@ namespace BetterLyrics.WinUI3.Events public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs { public bool IsPlaying { get; set; } = isPlaying; - } +} } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/LibChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/LibChangedEventArgs.cs new file mode 100644 index 0000000..7c45539 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/LibChangedEventArgs.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Events +{ + public class LibChangedEventArgs : EventArgs + { + public string Folder { get; } + public string FilePath { get; } + public WatcherChangeTypes ChangeType { get; } + + public LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType) + { + Folder = folder; + FilePath = filePath; + ChangeType = changeType; + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs index e42add5..ef247af 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs @@ -9,5 +9,5 @@ namespace BetterLyrics.WinUI3.Events public class PositionChangedEventArgs(TimeSpan position) : EventArgs() { public TimeSpan Position { get; set; } = position; - } +} } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs index 325648d..417ae57 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs @@ -10,5 +10,5 @@ namespace BetterLyrics.WinUI3.Events public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs { public SongInfo? SongInfo { get; set; } = songInfo; - } +} } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs index 3ff4df1..d6e06dd 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs @@ -1,13 +1,79 @@ using System; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Media.Animation; namespace BetterLyrics.WinUI3.Helper { - public static class AnimationHelper + public class AnimationHelper { public const int StackedNotificationsShowingDuration = 3900; public const int StoryboardDefaultDuration = 200; public const int DebounceDefaultDuration = 200; } + + public class ValueTransition + where T : struct + { + private T _currentValue; + private T _startValue; + private T _targetValue; + private float _progress; + private float _durationSeconds; + private bool _isTransitioning; + private Func _interpolator; + + public ValueTransition( + T initialValue, + float durationSeconds, + Func interpolator + ) + { + _currentValue = initialValue; + _startValue = initialValue; + _targetValue = initialValue; + _durationSeconds = durationSeconds; + _progress = 1f; + _isTransitioning = false; + _interpolator = interpolator; + } + + public T Value => _currentValue; + public bool IsTransitioning => _isTransitioning; + + public void StartTransition(T targetValue) + { + if (!targetValue.Equals(_currentValue)) + { + _startValue = _currentValue; + _targetValue = targetValue; + _progress = 0f; + _isTransitioning = true; + } + } + + public void Update(TimeSpan elapsedTime) + { + if (!_isTransitioning) + return; + + _progress += (float)elapsedTime.TotalSeconds / _durationSeconds; + if (_progress >= 1f) + { + _progress = 1f; + _currentValue = _targetValue; + _isTransitioning = false; + } + else + { + _currentValue = _interpolator(_startValue, _targetValue, _progress); + } + } + + public void Reset(T value) + { + _currentValue = value; + _startValue = value; + _targetValue = value; + _progress = 0f; + _isTransitioning = false; + } + } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs index 3133d8a..9af52b4 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs @@ -41,22 +41,21 @@ namespace BetterLyrics.WinUI3.Helper public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets"); // Data Files - private static string DatabaseFileName => "database.db"; - public static string DatabasePath => Path.Combine(LocalFolder, DatabaseFileName); public static string LogDirectory => Path.Combine(CacheFolder, "logs"); public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt"); + public static string OnlineLyricsCacheDirectory => + Path.Combine(CacheFolder, "online-lyrics"); + private static string TestMusicFileName => "AI - 甜度爆表.mp3"; public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName); - private static string CustomShaderFileName => "CustomTransform.bin"; - public static string CustomShaderPath => Path.Combine(AssetsFolder, CustomShaderFileName); - public static void EnsureDirectories() { - Directory.CreateDirectory(LogDirectory); Directory.CreateDirectory(LocalFolder); + Directory.CreateDirectory(LogDirectory); + Directory.CreateDirectory(OnlineLyricsCacheDirectory); } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs index 49816dc..36c3c25 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs @@ -4,6 +4,11 @@ using System.Linq; using System.Threading.Tasks; using Windows.Graphics.Imaging; +// This file is a fork of https://github.com/KSemenenko/ColorThief +// and is used to extract dominant colors from images in WinUI3 application +// I have modified it to fit the WinUI3 environment and removed unnecessary parts +// Credits to KSemenenko for the original work. + namespace BetterLyrics.WinUI3.Helper { /// diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs index e29cc95..6d15847 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs @@ -23,6 +23,7 @@ namespace BetterLyrics.WinUI3.Helper { window.SetIsShownInSwitchers(true); window.ExtendsContentIntoTitleBar = true; + window.SetIsAlwaysOnTop(false); IntPtr hwnd = WindowNative.GetWindowHandle(window); @@ -43,8 +44,6 @@ namespace BetterLyrics.WinUI3.Helper _originalPositions.Remove(hwnd); } - window.SetIsAlwaysOnTop(false); - UnregisterAppBar(hwnd); } @@ -52,6 +51,7 @@ namespace BetterLyrics.WinUI3.Helper { window.SetIsShownInSwitchers(false); window.ExtendsContentIntoTitleBar = false; + window.SetIsAlwaysOnTop(true); IntPtr hwnd = WindowNative.GetWindowHandle(window); @@ -82,8 +82,6 @@ namespace BetterLyrics.WinUI3.Helper appBarHeight, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW ); - - window.SetIsAlwaysOnTop(true); } [DllImport("user32.dll", SetLastError = true)] diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs new file mode 100644 index 0000000..1d920dd --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ude; + +namespace BetterLyrics.WinUI3.Helper +{ + public class FileHelper + { + public static Encoding GetEncoding(string filename) + { + var bytes = File.ReadAllBytes(filename); + var cdet = new CharsetDetector(); + cdet.Feed(bytes, 0, bytes.Length); + cdet.DataEnd(); + var encoding = cdet.Charset; + if (encoding == null) + { + return Encoding.UTF8; + } + return Encoding.GetEncoding(encoding); + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs index bf34dbe..8df79d7 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs @@ -2,8 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Text; +using Microsoft.UI; +using Microsoft.UI.Text; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Windows.Graphics.Imaging; @@ -71,5 +76,81 @@ namespace BetterLyrics.WinUI3.Helper public static async Task GetDecoderFromByte(byte[] bytes) => await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes)); + + public static async Task CreateTextPlaceholderBytesAsync( + string text, + int width, + int height + ) + { + var device = CanvasDevice.GetSharedDevice(); + var renderTarget = new CanvasRenderTarget(device, width, height, 96); + + // 居中绘制文字 + using (var ds = renderTarget.CreateDrawingSession()) + { + // 背景色 + ds.Clear(Colors.LightGray); + + // 文字格式 + var format = new CanvasTextFormat + { + FontSize = Math.Min(width, height) / 6f, + FontWeight = Microsoft.UI.Text.FontWeights.SemiBold, + HorizontalAlignment = CanvasHorizontalAlignment.Center, + VerticalAlignment = CanvasVerticalAlignment.Center, + WordWrapping = CanvasWordWrapping.Wrap, + TrimmingGranularity = CanvasTextTrimmingGranularity.Character, + Options = CanvasDrawTextOptions.Default, + }; + + // 设定边距 + float margin = Math.Min(width, height) / 12f; + float availableWidth = width - 2 * margin; + float availableHeight = height - 2 * margin; + + // 计算合适的字体大小以适应内容区域 + float fontSize = format.FontSize; + float minFontSize = 8f; + float maxFontSize = format.FontSize; + CanvasTextLayout layout; + do + { + format.FontSize = fontSize; + layout = new CanvasTextLayout( + ds, + text, + format, + availableWidth, + availableHeight + ); + if ( + layout.LayoutBounds.Width <= availableWidth + && layout.LayoutBounds.Height <= availableHeight + ) + break; + fontSize -= 1f; + } while (fontSize >= minFontSize); + + // 居中绘制文字(在内容区域内居中) + var bounds = layout.LayoutBounds; + var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X; + var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y; + ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray); + } + + // 保存为 PNG 并转为 byte[] + using (var stream = new InMemoryRandomAccessStream()) + { + await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png); + var buffer = new byte[stream.Size]; + using (var reader = new DataReader(stream.GetInputStreamAt(0))) + { + await reader.LoadAsync((uint)stream.Size); + reader.ReadBytes(buffer); + } + return buffer; + } + } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs new file mode 100644 index 0000000..e43f094 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Models; + +namespace BetterLyrics.WinUI3.Helper +{ + public class LyricsParser + { + private List> _multiLangLyricsLines = []; + + public List> Parse( + string raw, + LyricsFormat? lyricsFormat = null, + string? title = null, + string? artist = null, + int durationMs = 0 + ) + { + _multiLangLyricsLines = []; + switch (lyricsFormat) + { + case LyricsFormat.Lrc: + case LyricsFormat.Eslrc: + ParseLrc(raw, durationMs); + break; + case LyricsFormat.Ttml: + ParseTtml(raw, durationMs); + break; + default: + break; + } + return _multiLangLyricsLines; + } + + private void PostProcessLyricsLines(List lines) + { + if (lines.Count > 0 && lines[0].StartMs > 0) + { + lines.Insert( + 0, + new LyricsLine + { + StartMs = 0, + EndMs = lines[0].StartMs, + Text = "", + CharTimings = [], + } + ); + } + } + + private void ParseLrc(string raw, int durationMs) + { + var lines = raw.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); + var lrcLines = + new List<(int time, string text, List<(int time, string text)> syllables)>(); + + // 支持 [mm:ss.xx]字、字,毫秒两位或三位 + var syllableRegex = new Regex( + @"(\[|\<)(\d{2}):(\d{2})\.(\d{2,3})(\]|\>)([^\[\]\<\>]*)" + ); + + foreach (var line in lines) + { + var matches = syllableRegex.Matches(line); + var syllables = new List<(int, string)>(); + for (int i = 0; i < matches.Count; i++) + { + var m = matches[i]; + int min = int.Parse(m.Groups[2].Value); + int sec = int.Parse(m.Groups[3].Value); + int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0')); + int totalMs = min * 60_000 + sec * 1000 + ms; + string text = m.Groups[6].Value; + + syllables.Add((totalMs, text)); + } + if (syllables.Count > 0) + { + lrcLines.Add( + ( + syllables[0].Item1, + string.Concat(syllables.Select(s => s.Item2)), + syllables + ) + ); + } + else + { + // 普通LRC行 + var bracketRegex = new Regex(@"\[(\d{2}):(\d{2})\.(\d{2,3})\]"); + var bracketMatches = bracketRegex.Matches(line); + string content = line; + int? lineStartTime = null; + if (bracketMatches.Count > 0) + { + var m = bracketMatches[0]; + int min = int.Parse(m.Groups[1].Value); + int sec = int.Parse(m.Groups[2].Value); + int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0')); + lineStartTime = min * 60_000 + sec * 1000 + ms; + content = bracketRegex.Replace(line, "").Trim(); + lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>())); + } + } + } + + // 按时间分组 + var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList(); + int languageCount = grouped.Max(g => g.Count()); + + // 初始化每种语言的歌词列表 + _multiLangLyricsLines.Clear(); + for (int i = 0; i < languageCount; i++) + _multiLangLyricsLines.Add(new List()); + + // 遍历每个时间分组 + foreach (var group in grouped) + { + var linesInGroup = group.ToList(); + for (int langIdx = 0; langIdx < languageCount; langIdx++) + { + // 如果该语言有翻译,取对应行,否则用原文(第一行) + var (start, text, syllables) = + langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0]; + var line = new LyricsLine + { + StartMs = start, + EndMs = 0, // 稍后统一修正 + Text = text, + CharTimings = [], + }; + if (syllables != null && syllables.Count > 0) + { + for (int j = 0; j < syllables.Count; j++) + { + var (charStart, charText) = syllables[j]; + int charEnd = (j + 1 < syllables.Count) ? syllables[j + 1].Item1 : 0; + line.CharTimings.Add( + new CharTiming { StartMs = charStart, EndMs = charEnd } + ); + } + } + _multiLangLyricsLines[langIdx].Add(line); + } + } + + // 修正 EndMs + for (int langIdx = 0; langIdx < languageCount; langIdx++) + { + var linesInSingleLang = _multiLangLyricsLines[langIdx]; + for (int i = 0; i < linesInSingleLang.Count; i++) + { + if (i + 1 < linesInSingleLang.Count) + linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs; + else + linesInSingleLang[i].EndMs = durationMs; + + // 修正 CharTimings 的最后一个 EndMs + var timings = linesInSingleLang[i].CharTimings; + if (timings.Count > 0) + { + for (int j = 0; j < timings.Count; j++) + { + if (j + 1 < timings.Count) + timings[j].EndMs = timings[j + 1].StartMs; + else + timings[j].EndMs = linesInSingleLang[i].EndMs; + } + } + } + PostProcessLyricsLines(linesInSingleLang); + } + } + + private void ParseTtml(string raw, int durationMs) + { + try + { + List singleLangLyricsLine = []; + var xdoc = XDocument.Parse(raw); + var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body"); + if (body == null) + return; + var ps = body.Descendants().Where(e => e.Name.LocalName == "p"); + foreach (var p in ps) + { + // 句级时间 + string? pBegin = p.Attribute("begin")?.Value; + string? pEnd = p.Attribute("end")?.Value; + int pStartMs = ParseTtmlTime(pBegin); + int pEndMs = ParseTtmlTime(pEnd); + + // 处理分词分时 + var spans = p.Elements() + .Where(s => + s.Name.LocalName == "span" + && s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata")) + == null + ) + .ToList(); + + string text = string.Concat(spans.Select(s => s.Value)); + var charTimings = new List(); + + for (int i = 0; i < spans.Count; i++) + { + var span = spans[i]; + string? sBegin = span.Attribute("begin")?.Value; + string? sEnd = span.Attribute("end")?.Value; + int sStartMs = ParseTtmlTime(sBegin); + int sEndMs = ParseTtmlTime(sEnd); + + if (sStartMs == 0 && sEndMs == 0) + continue; + + if (sEndMs == 0) + sEndMs = + (i + 1 < spans.Count) + ? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value) + : pEndMs; + + charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = sEndMs }); + } + + if (spans.Count == 0) + text = p.Value.Trim(); + + singleLangLyricsLine.Add( + new LyricsLine + { + StartMs = pStartMs, + EndMs = pEndMs, + Text = text, + CharTimings = charTimings, + } + ); + } + PostProcessLyricsLines(singleLangLyricsLine); + _multiLangLyricsLines.Add(singleLangLyricsLine); + } + catch + { + // 解析失败,忽略 + } + } + + private int ParseTtmlTime(string? t) + { + if (string.IsNullOrWhiteSpace(t)) + return 0; + + t = t.Trim(); + + // 支持 "1.000s" + if (t.EndsWith("s")) + { + if ( + double.TryParse( + t.TrimEnd('s'), + System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, + out double seconds + ) + ) + return (int)(seconds * 1000); + } + else + { + var parts = t.Split(':'); + if (parts.Length == 3) + { + // hh:mm:ss.xxx + int h = int.Parse(parts[0]); + int m = int.Parse(parts[1]); + double s = double.Parse( + parts[2], + System.Globalization.CultureInfo.InvariantCulture + ); + return (int)((h * 3600 + m * 60 + s) * 1000); + } + else if (parts.Length == 2) + { + // mm:ss.xxx + int m = int.Parse(parts[0]); + double s = double.Parse( + parts[1], + System.Globalization.CultureInfo.InvariantCulture + ); + return (int)((m * 60 + s) * 1000); + } + else if (parts.Length == 1) + { + // ss.xxx + if ( + double.TryParse( + parts[0], + System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, + out double s + ) + ) + return (int)(s * 1000); + } + } + return 0; + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs index e720895..5d14b06 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs @@ -11,5 +11,6 @@ using Microsoft.UI.Xaml.Controls; namespace BetterLyrics.WinUI3.Messages { public class ShowNotificatonMessage(Notification value) - : ValueChangedMessage(value) { } + : ValueChangedMessage(value) + { } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs new file mode 100644 index 0000000..d719c94 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Models +{ + public class CharTiming + { + public int StartMs { get; set; } + public int EndMs { get; set; } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LocalLyricsFolder.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LocalLyricsFolder.cs new file mode 100644 index 0000000..3b0b066 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LocalLyricsFolder.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace BetterLyrics.WinUI3.Models +{ + public partial class LocalLyricsFolder : ObservableObject + { + [ObservableProperty] + public partial string Path { get; set; } + + [ObservableProperty] + public partial bool IsEnabled { get; set; } + + public LocalLyricsFolder() { } + + public LocalLyricsFolder(string path, bool isEnabled) + { + Path = path; + IsEnabled = isEnabled; + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs new file mode 100644 index 0000000..c176122 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Models +{ + public class LyricsData + { + public int LanguageIndex { get; set; } = 0; + + public List LyricsLines => MultiLangLyricsLines[LanguageIndex]; + public List> MultiLangLyricsLines { get; set; } = []; + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs index b6bc69b..82794f8 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs @@ -1,23 +1,22 @@ using System.Collections.Generic; using System.Numerics; using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Helper; namespace BetterLyrics.WinUI3.Models { public class LyricsLine { - public List Texts { get; set; } = []; + public string Text { get; set; } = ""; - public int LanguageIndex { get; set; } = 0; + public List CharTimings { get; set; } = []; - public string Text => Texts[LanguageIndex]; - - public int StartPlayingTimestampMs { get; set; } - public int EndPlayingTimestampMs { get; set; } + public int StartMs { get; set; } + public int EndMs { get; set; } public LyricsPlayingState PlayingState { get; set; } - public int DurationMs => EndPlayingTimestampMs - StartPlayingTimestampMs; + public int DurationMs => EndMs - StartMs; public float EnteringProgress { get; set; } @@ -37,10 +36,10 @@ namespace BetterLyrics.WinUI3.Models { return new LyricsLine { - Texts = new List(this.Texts), - LanguageIndex = this.LanguageIndex, - StartPlayingTimestampMs = this.StartPlayingTimestampMs, - EndPlayingTimestampMs = this.EndPlayingTimestampMs, + Text = this.Text, + CharTimings = this.CharTimings, + StartMs = this.StartMs, + EndMs = this.EndMs, PlayingState = this.PlayingState, EnteringProgress = this.EnteringProgress, ExitingProgress = this.ExitingProgress, diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSearchProviderInfo.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSearchProviderInfo.cs new file mode 100644 index 0000000..9836e07 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSearchProviderInfo.cs @@ -0,0 +1,22 @@ +using BetterLyrics.WinUI3.Enums; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace BetterLyrics.WinUI3.Models +{ + public partial class LyricsSearchProviderInfo : ObservableObject + { + [ObservableProperty] + public partial LyricsSearchProvider Provider { get; set; } + + [ObservableProperty] + public partial bool IsEnabled { get; set; } + + public LyricsSearchProviderInfo() { } + + public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled) + { + Provider = provider; + IsEnabled = isEnabled; + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MetadataIndex.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MetadataIndex.cs deleted file mode 100644 index 0e2ec9d..0000000 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MetadataIndex.cs +++ /dev/null @@ -1,12 +0,0 @@ -using SQLite; - -namespace BetterLyrics.WinUI3.Models -{ - public class MetadataIndex - { - [PrimaryKey] - public string? Path { get; set; } - public string? Title { get; set; } - public string? Artist { get; set; } - } -} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs index 67cee90..c9dd233 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs @@ -12,19 +12,19 @@ namespace BetterLyrics.WinUI3.Models public partial class Notification : ObservableObject { [ObservableProperty] - private InfoBarSeverity _severity; + public partial InfoBarSeverity Severity { get; set; } [ObservableProperty] - private string? _message; + public partial string? Message { get; set; } [ObservableProperty] - private bool _isForeverDismissable; + public partial bool IsForeverDismissable { get; set; } [ObservableProperty] - private Visibility _visibility; + public partial Visibility Visibility { get; set; } [ObservableProperty] - private string? _relatedSettingsKeyName; + public partial string? RelatedSettingsKeyName { get; set; } public Notification( string? message = null, diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs index 549fa7d..6c46e33 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs @@ -1,106 +1,29 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using ATL; -using BetterLyrics.WinUI3.Helper; -using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.UI; -using Windows.UI; -using static ATL.LyricsInfo; +using CommunityToolkit.Mvvm.ComponentModel; namespace BetterLyrics.WinUI3.Models { public partial class SongInfo : ObservableObject { [ObservableProperty] - public partial string? Title { get; set; } + public partial string Title { get; set; } [ObservableProperty] - public partial string? Artist { get; set; } + public partial string Artist { get; set; } [ObservableProperty] - public partial ObservableCollection? FilesFound { get; set; } + public partial string? Album { get; set; } + /// + /// In milliseconds + /// [ObservableProperty] - public partial bool IsLyricsExisted { get; set; } = false; + public partial double? DurationMs { get; set; } [ObservableProperty] public partial string? SourceAppUserModelId { get; set; } = null; - [ObservableProperty] - public partial List? LyricsLines { get; set; } = null; public byte[]? AlbumArt { get; set; } = null; - [ObservableProperty] - public partial List? CoverImageDominantColors { get; set; } = null; - public SongInfo() { } - - /// - /// Try to parse lyrics from the track, optionally override the raw lyrics string. - /// - /// - /// - public void ParseLyrics(Track track, string? overrideRaw = null) - { - List? result = null; - - if (overrideRaw != null) - track.Lyrics.ParseLRC(overrideRaw); - - var lyricsPhrases = track.Lyrics.SynchronizedLyrics; - - if (lyricsPhrases?.Count > 0) - { - if (lyricsPhrases[0].TimestampMs > 0) - { - var placeholder = new LyricsPhrase(0, $"{track.Artist} - {track.Title}"); - lyricsPhrases.Insert(0, placeholder); - lyricsPhrases.Insert(0, placeholder); - } - } - - LyricsLine? lyricsLine = null; - - for (int i = 0; i < lyricsPhrases?.Count; i++) - { - var lyricsPhrase = lyricsPhrases[i]; - int startTimestampMs = lyricsPhrase.TimestampMs; - int endTimestampMs; - - if (i + 1 < lyricsPhrases.Count) - { - endTimestampMs = lyricsPhrases[i + 1].TimestampMs; - } - else - { - endTimestampMs = (int)track.DurationMs; - } - - lyricsLine ??= new LyricsLine { StartPlayingTimestampMs = startTimestampMs }; - - lyricsLine.Texts.Add(lyricsPhrase.Text); - - if (endTimestampMs == startTimestampMs) - { - continue; - } - else - { - lyricsLine.EndPlayingTimestampMs = endTimestampMs; - result ??= []; - result.Add(lyricsLine); - lyricsLine = null; - } - } - - if (result != null && result.Count == 0) - { - result = null; - } - - LyricsLines = result; - IsLyricsExisted = result != null; - } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml index 4921722..d99aaa8 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml @@ -14,7 +14,6 @@ x:Name="LyricsCanvas" Draw="LyricsCanvas_Draw" Loaded="LyricsCanvas_Loaded" - Paused="{x:Bind ViewModel.IsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" Update="LyricsCanvas_Update" /> diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs index 11276a5..2658b6e 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs @@ -32,7 +32,7 @@ namespace BetterLyrics.WinUI3.Renderer Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args ) { - ViewModel.Calculate(sender, args); + ViewModel.Update(sender, args); } private void LyricsCanvas_Loaded(object sender, RoutedEventArgs e) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Serialization/SourceGenerationContext.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Serialization/SourceGenerationContext.cs new file mode 100644 index 0000000..57536a6 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Serialization/SourceGenerationContext.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using BetterLyrics.WinUI3.Models; + +namespace BetterLyrics.WinUI3.Serialization +{ + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(JsonElement))] + [JsonSourceGenerationOptions(WriteIndented = true)] + internal partial class SourceGenerationContext : JsonSerializerContext { } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/DatabaseService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/DatabaseService.cs deleted file mode 100644 index fa388fd..0000000 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/DatabaseService.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using ATL; -using BetterLyrics.WinUI3.Helper; -using BetterLyrics.WinUI3.Models; -using SQLite; -using Ude; -using Windows.Media.Control; -using Windows.Storage.Streams; - -namespace BetterLyrics.WinUI3.Services.Database -{ - public class DatabaseService : IDatabaseService - { - private readonly SQLiteConnection _connection; - - private readonly CharsetDetector _charsetDetector = new(); - - public DatabaseService() - { - _connection = new SQLiteConnection(AppInfo.DatabasePath); - if (_connection.GetTableInfo("MetadataIndex").Count == 0) - { - _connection.CreateTable(); - } - } - - public async Task RebuildDatabaseAsync(IList paths) - { - await Task.Run(() => - { - _connection.DeleteAll(); - - HashSet insertedPaths = new(); - - foreach (var path in paths) - { - if (Directory.Exists(path)) - { - foreach ( - var file in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories) - ) - { - if (!insertedPaths.Contains(file)) - { - var track = new Track(file); - _connection.Insert( - new MetadataIndex - { - Path = file, - Title = track.Title, - Artist = track.Artist, - } - ); - insertedPaths.Add(file); - } - } - } - } - }); - } - - public async Task FindSongInfoAsync( - GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps - ) - { - if (mediaProps == null || mediaProps.Title == null || mediaProps.Artist == null) - return new(); - - var songInfo = new SongInfo { Title = mediaProps?.Title, Artist = mediaProps?.Artist }; - - // App.ResourceLoader!.GetString("MainPageNoLocalFilesMatched"); - - if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference) - { - songInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference); - } - - return await FindSongInfoAsync(songInfo, mediaProps!.Title, mediaProps!.Artist); - } - - public async Task FindSongInfoAsync( - SongInfo initSongInfo, - string searchTitle, - string searchArtist - ) - { - var founds = _connection - .Table() - // Look up by Title and Artist (these two props were fetched by reading metadata in music file befoe) first - // then by Path (music file name usually contains song name and artist so this can be a second way to look up for) - // Please note for .lrc file, only the second way works for it - .Where(m => - ( - m.Title != null - && m.Artist != null - && m.Title.Contains(searchTitle) - && m.Artist.Contains(searchArtist) - ) - || ( - m.Path != null - && m.Path.Contains(searchTitle) - && m.Path.Contains(searchArtist) - ) - ) - .ToList(); - - foreach (var found in founds) - { - initSongInfo.FilesFound ??= []; - initSongInfo.FilesFound.Add(found.Path!); - if (initSongInfo.LyricsLines == null || initSongInfo.AlbumArt == null) - { - Track track = new(found.Path); - initSongInfo.ParseLyrics(track); - // Successfully parse lyrics info from metadata in music file - if (initSongInfo.LyricsLines != null) - { - // Used as lyrics source - } - // Find lyrics file - if (initSongInfo.LyricsLines == null && found?.Path?.EndsWith(".lrc") == true) - { - using (FileStream fs = File.OpenRead(found.Path)) - { - _charsetDetector.Feed(fs); - _charsetDetector.DataEnd(); - } - - string content; - if (_charsetDetector.Charset != null) - { - Encoding encoding = Encoding.GetEncoding(_charsetDetector.Charset); - content = File.ReadAllText(found.Path, encoding); - } - else - { - content = File.ReadAllText(found.Path, Encoding.UTF8); - } - initSongInfo.ParseLyrics(track, content); - // Used as lyrics source - } - - // Finf album art - if (initSongInfo.AlbumArt == null) - { - if (track.EmbeddedPictures.Count > 0) - { - initSongInfo.AlbumArt = track.EmbeddedPictures[0].PictureData; - // Used as album art source - } - } - } - else - break; - } - - if (initSongInfo.AlbumArt == null) - { - initSongInfo.CoverImageDominantColors = null; - } - else - { - initSongInfo.CoverImageDominantColors = await ImageHelper.GetAccentColorsFromByte( - initSongInfo.AlbumArt - ); - } - - if (initSongInfo.LyricsLines == null) { } - - return initSongInfo; - } - } -} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/IDatabaseService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/IDatabaseService.cs deleted file mode 100644 index 91355d4..0000000 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/IDatabaseService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using BetterLyrics.WinUI3.Models; -using Windows.Media.Control; - -namespace BetterLyrics.WinUI3.Services.Database -{ - public interface IDatabaseService - { - Task RebuildDatabaseAsync(IList paths); - - Task FindSongInfoAsync( - GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps - ); - - Task FindSongInfoAsync( - SongInfo initSongInfo, - string searchTitle, - string searchArtist - ); - } -} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ILibWatcherService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ILibWatcherService.cs new file mode 100644 index 0000000..0457e9b --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ILibWatcherService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BetterLyrics.WinUI3.Events; +using BetterLyrics.WinUI3.Models; + +namespace BetterLyrics.WinUI3.Services +{ + public interface ILibWatcherService + { + event EventHandler? MusicLibraryFilesChanged; + public void UpdateWatchers(List folders); + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IMusicSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IMusicSearchService.cs new file mode 100644 index 0000000..84f5be3 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IMusicSearchService.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using BetterLyrics.WinUI3.Enums; + +namespace BetterLyrics.WinUI3.Services +{ + public interface IMusicSearchService + { + Task<(string?, LyricsFormat?)> SearchLyricsAsync( + string title, + string artist, + string album = "", + double durationMs = 0.0, + MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist + ); + + byte[]? SearchAlbumArtAsync(string title, string artist); + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/IPlaybackService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs similarity index 84% rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/IPlaybackService.cs rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs index 658ec2c..9dbb1b6 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/IPlaybackService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs @@ -2,7 +2,7 @@ using BetterLyrics.WinUI3.Events; using BetterLyrics.WinUI3.Models; -namespace BetterLyrics.WinUI3.Services.Playback +namespace BetterLyrics.WinUI3.Services { public interface IPlaybackService { @@ -10,7 +10,6 @@ namespace BetterLyrics.WinUI3.Services.Playback event EventHandler? IsPlayingChanged; event EventHandler? PositionChanged; - void ReSendingMessages(); SongInfo? SongInfo { get; } bool IsPlaying { get; } TimeSpan Position { get; } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/ISettingsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs similarity index 86% rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/ISettingsService.cs rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs index fd3e979..0e8da2a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/ISettingsService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs @@ -1,17 +1,19 @@ using System.Collections.Generic; using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Models; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Windows.UI.Text; -namespace BetterLyrics.WinUI3.Services.Settings +namespace BetterLyrics.WinUI3.Services { public interface ISettingsService { bool IsFirstRun { get; set; } // Lyrics lib - List MusicLibraries { get; set; } + List LocalLyricsFolders { get; set; } + List LyricsSearchProvidersInfo { get; set; } // App appearance ElementTheme ThemeType { get; set; } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LibWatcherService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LibWatcherService.cs new file mode 100644 index 0000000..3891d06 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LibWatcherService.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Services +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using global::BetterLyrics.WinUI3.Events; + using global::BetterLyrics.WinUI3.Models; + + namespace BetterLyrics.WinUI3.Services + { + public class LibWatcherService : IDisposable, ILibWatcherService + { + private readonly ISettingsService _settingsService; + private readonly Dictionary _watchers = []; + + public event EventHandler? MusicLibraryFilesChanged; + + public LibWatcherService(ISettingsService settingsService) + { + _settingsService = settingsService; + UpdateWatchers(_settingsService.LocalLyricsFolders); + } + + public void UpdateWatchers(List folders) + { + // 移除不再监听的 + foreach (var key in _watchers.Keys.ToList()) + { + if (!folders.Any(x => x.Path == key && x.IsEnabled)) + { + _watchers[key].Dispose(); + _watchers.Remove(key); + } + } + + // 添加新的监听 + foreach (var folder in folders) + { + if ( + !_watchers.ContainsKey(folder.Path) + && Directory.Exists(folder.Path) + && folder.IsEnabled + ) + { + var watcher = new FileSystemWatcher(folder.Path) + { + IncludeSubdirectories = true, + EnableRaisingEvents = true, + }; + watcher.Created += (s, e) => OnChanged(folder.Path, e); + watcher.Changed += (s, e) => OnChanged(folder.Path, e); + watcher.Deleted += (s, e) => OnChanged(folder.Path, e); + watcher.Renamed += (s, e) => OnChanged(folder.Path, e); + _watchers[folder.Path] = watcher; + } + } + } + + private void OnChanged(string folder, FileSystemEventArgs e) + { + App.DispatcherQueue!.TryEnqueue( + Microsoft.UI.Dispatching.DispatcherQueuePriority.High, + () => + { + MusicLibraryFilesChanged?.Invoke( + this, + new LibChangedEventArgs(folder, e.FullPath, e.ChangeType) + ); + } + ); + } + + public void Dispose() + { + foreach (var watcher in _watchers.Values) + { + watcher.Dispose(); + } + _watchers.Clear(); + } + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs new file mode 100644 index 0000000..c2707b3 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs @@ -0,0 +1,354 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using ATL; +using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Helper; +using Windows.Storage; +using Windows.Storage.FileProperties; + +namespace BetterLyrics.WinUI3.Services +{ + public class MusicSearchService : IMusicSearchService + { + private readonly HttpClient _httpClient; + private readonly ISettingsService _settingsService; + + public MusicSearchService(ISettingsService settingsService) + { + _settingsService = settingsService; + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Add( + "User-Agent", + $"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})" + ); + } + + public byte[]? SearchAlbumArtAsync(string title, string artist) + { + foreach (var folder in _settingsService.LocalLyricsFolders) + { + if (Directory.Exists(folder.Path) && folder.IsEnabled) + { + foreach ( + var file in Directory.GetFiles( + folder.Path, + $"*.*", + SearchOption.AllDirectories + ) + ) + { + if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist)) + { + Track track = new(file); + var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData; + if (bytes != null) + { + return bytes; + } + } + } + } + } + + return null; + } + + public async Task<(string?, LyricsFormat?)> SearchLyricsAsync( + string title, + string artist, + string album = "", + double durationMs = 0.0, + MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist + ) + { + foreach (var provider in _settingsService.LyricsSearchProvidersInfo) + { + if (!provider.IsEnabled) + { + continue; + } + + switch (provider.Provider) + { + case LyricsSearchProvider.LrcLib: + // Check cache first + var cachedLyrics = ReadCache(title, artist, LyricsFormat.Lrc); + if (!string.IsNullOrWhiteSpace(cachedLyrics)) + { + return (cachedLyrics, LyricsFormat.Lrc); + } + break; + default: + break; + } + + string? searchedLyrics = null; + + switch (provider.Provider) + { + case LyricsSearchProvider.LocalMusicFile: + searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist); + break; + case LyricsSearchProvider.LocalLrcFile: + searchedLyrics = await LocalLyricsSearchInLyricsFiles( + title, + artist, + LyricsFormat.Lrc + ); + break; + case LyricsSearchProvider.LocalEslrcFile: + searchedLyrics = await LocalLyricsSearchInLyricsFiles( + title, + artist, + LyricsFormat.Eslrc + ); + break; + case LyricsSearchProvider.LocalTtmlFile: + searchedLyrics = await LocalLyricsSearchInLyricsFiles( + title, + artist, + LyricsFormat.Ttml + ); + break; + case LyricsSearchProvider.LrcLib: + searchedLyrics = await SearchLrcLib( + title, + artist, + album, + (int)(durationMs / 1000), + matchMode + ); + break; + default: + break; + } + + if (!string.IsNullOrWhiteSpace(searchedLyrics)) + { + switch (provider.Provider) + { + case LyricsSearchProvider.LrcLib: + WriteCache(title, artist, searchedLyrics, LyricsFormat.Lrc); + return (searchedLyrics, LyricsFormat.Lrc); + case LyricsSearchProvider.LocalMusicFile: + return (searchedLyrics, LyricsFormatExtensions.Detect(searchedLyrics)); + case LyricsSearchProvider.LocalLrcFile: + return (searchedLyrics, LyricsFormat.Lrc); + case LyricsSearchProvider.LocalEslrcFile: + return (searchedLyrics, LyricsFormat.Eslrc); + case LyricsSearchProvider.LocalTtmlFile: + return (searchedLyrics, LyricsFormat.Ttml); + default: + break; + } + } + } + + return (null, null); + } + + private static int LevenshteinDistance(string a, string b) + { + if (string.IsNullOrEmpty(a)) + return b.Length; + if (string.IsNullOrEmpty(b)) + return a.Length; + int[,] d = new int[a.Length + 1, b.Length + 1]; + for (int i = 0; i <= a.Length; i++) + d[i, 0] = i; + for (int j = 0; j <= b.Length; j++) + d[0, j] = j; + for (int i = 1; i <= a.Length; i++) + for (int j = 1; j <= b.Length; j++) + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1) + ); + return d[a.Length, b.Length]; + } + + // 判断相似度 + private static bool FuzzyMatch(string fileName, string title, string artist) + { + var normFile = Normalize(fileName); + var normTarget1 = Normalize(title + artist); + var normTarget2 = Normalize(artist + title); + + int dist1 = LevenshteinDistance(normFile, normTarget1); + int dist2 = LevenshteinDistance(normFile, normTarget2); + + return dist1 <= 3 || dist2 <= 3; // 阈值可调整 + } + + private static string Normalize(string s) + { + if (string.IsNullOrWhiteSpace(s)) + return ""; + var sb = new StringBuilder(); + foreach (var c in s.ToLowerInvariant()) + { + if (char.IsLetterOrDigit(c)) + sb.Append(c); + } + return sb.ToString(); + } + + private string? LocalLyricsSearchInMusicFiles(string title, string artist) + { + foreach (var folder in _settingsService.LocalLyricsFolders) + { + if (Directory.Exists(folder.Path) && folder.IsEnabled) + { + foreach ( + var file in Directory.GetFiles( + folder.Path, + $"*.*", + SearchOption.AllDirectories + ) + ) + { + if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist)) + { + //Track track = new(file); + //var plain = track.Lyrics.UnsynchronizedLyrics; + + try + { + var plain = TagLib.File.Create(file).Tag.Lyrics; + if (plain != null && plain != string.Empty) + { + return plain; + } + } + catch (Exception) { } + } + } + } + } + + return null; + } + + private async Task LocalLyricsSearchInLyricsFiles( + string title, + string artist, + LyricsFormat format + ) + { + foreach (var folder in _settingsService.LocalLyricsFolders) + { + if (Directory.Exists(folder.Path) && folder.IsEnabled) + { + foreach ( + var file in Directory.GetFiles( + folder.Path, + $"*{format.ToFileExtension()}", + SearchOption.AllDirectories + ) + ) + { + if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist)) + { + string? raw = await File.ReadAllTextAsync( + file, + FileHelper.GetEncoding(file) + ); + if (raw != null) + { + return raw; + } + } + } + } + } + return null; + } + + private async Task SearchLrcLib( + string title, + string artist, + string album, + int duration, + MusicSearchMatchMode matchMode + ) + { + // Build API query URL + var url = + $"https://lrclib.net/api/search?" + + $"track_name={Uri.EscapeDataString(title)}&" + + $"artist_name={Uri.EscapeDataString(artist)}"; + + if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration) + { + url += + $"&album_name={Uri.EscapeDataString(album)}" + + $"&durationMs={Uri.EscapeDataString(duration.ToString())}"; + } + + var response = await _httpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + return null; + + var json = await response.Content.ReadAsStringAsync(); + + var jArr = JsonSerializer.Deserialize( + json, + Serialization.SourceGenerationContext.Default.JsonElement + ); + if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0) + { + var first = jArr[0]; + var syncedLyrics = first.GetProperty("syncedLyrics").GetString(); + var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics; + if (!string.IsNullOrWhiteSpace(result)) + { + return result; + } + } + + return null; + } + + private void WriteCache(string title, string artist, string lyrics, LyricsFormat format) + { + var safeArtist = SanitizeFileName(artist); + var safeTitle = SanitizeFileName(title); + var cacheFilePath = Path.Combine( + AppInfo.OnlineLyricsCacheDirectory, + $"{safeArtist} - {safeTitle}{format.ToFileExtension()}" + ); + File.WriteAllText(cacheFilePath, lyrics); + } + + private string? ReadCache(string title, string artist, LyricsFormat format) + { + var safeArtist = SanitizeFileName(artist); + var safeTitle = SanitizeFileName(title); + var cacheFilePath = Path.Combine( + AppInfo.OnlineLyricsCacheDirectory, + $"{safeArtist} - {safeTitle}{format.ToFileExtension()}" + ); + if (File.Exists(cacheFilePath)) + { + return File.ReadAllText(cacheFilePath); + } + return null; + } + + private static string SanitizeFileName(string fileName, char replacement = '_') + { + var invalidChars = Path.GetInvalidFileNameChars(); + var sb = new StringBuilder(fileName.Length); + foreach (var c in fileName) + { + sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c); + } + return sb.ToString(); + } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/PlaybackService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs similarity index 61% rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/PlaybackService.cs rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs index 0b0252b..502ca5c 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/PlaybackService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs @@ -1,15 +1,20 @@ using System; -using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; using System.Threading.Tasks; +using ATL; +using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Events; using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Models; -using BetterLyrics.WinUI3.Services.Database; using CommunityToolkit.WinUI; using Microsoft.UI.Dispatching; +using Windows.ApplicationModel; using Windows.Media.Control; +using Windows.Storage.Streams; -namespace BetterLyrics.WinUI3.Services.Playback +namespace BetterLyrics.WinUI3.Services { public partial class PlaybackService : IPlaybackService { @@ -26,23 +31,17 @@ namespace BetterLyrics.WinUI3.Services.Playback public bool IsPlaying { get; private set; } public TimeSpan Position { get; private set; } - private readonly IDatabaseService _databaseService; + private readonly IMusicSearchService _musicSearchService; - public PlaybackService(IDatabaseService databaseService) + public PlaybackService( + ISettingsService settingsService, + IMusicSearchService musicSearchService + ) { - _databaseService = databaseService; + _musicSearchService = musicSearchService; InitMediaManager().ConfigureAwait(true); } - private async Task GetSongInfoAsync() - { - var songInfo = await _databaseService.FindSongInfoAsync( - await _currentSession?.TryGetMediaPropertiesAsync() - ); - songInfo.SourceAppUserModelId = _currentSession?.SourceAppUserModelId; - return songInfo; - } - private async Task InitMediaManager() { _sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); @@ -51,14 +50,6 @@ namespace BetterLyrics.WinUI3.Services.Playback SessionManager_CurrentSessionChanged(_sessionManager, null); } - public void ReSendingMessages() - { - // Re-send messages to update UI - CurrentSession_MediaPropertiesChanged(_currentSession, null); - CurrentSession_PlaybackInfoChanged(_currentSession, null); - CurrentSession_TimelinePropertiesChanged(_currentSession, null); - } - /// /// Note: Non-UI thread /// @@ -94,10 +85,13 @@ namespace BetterLyrics.WinUI3.Services.Playback break; } } - _dispatcherQueue.TryEnqueue(() => - { - IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying)); - }); + _dispatcherQueue.TryEnqueue( + DispatcherQueuePriority.High, + () => + { + IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying)); + } + ); } private void SessionManager_CurrentSessionChanged( @@ -126,7 +120,9 @@ namespace BetterLyrics.WinUI3.Services.Playback CurrentSession_TimelinePropertiesChanged; } - ReSendingMessages(); + CurrentSession_MediaPropertiesChanged(_currentSession, null); + CurrentSession_PlaybackInfoChanged(_currentSession, null); + CurrentSession_TimelinePropertiesChanged(_currentSession, null); } /// @@ -134,31 +130,78 @@ namespace BetterLyrics.WinUI3.Services.Playback /// /// /// - private void CurrentSession_MediaPropertiesChanged( + private async void CurrentSession_MediaPropertiesChanged( GlobalSystemMediaTransportControlsSession? sender, MediaPropertiesChangedEventArgs? args ) { - App.DispatcherQueueTimer!.Debounce( - async () => + GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null; + if (sender == null) + { + SongInfo = null; + } + else + { + try { - // _logger.LogDebug("CurrentSession_MediaPropertiesChanged"); - if (sender == null) - SongInfo = null; + mediaProps = await sender.TryGetMediaPropertiesAsync(); + } + catch (Exception) { } + + if (mediaProps == null) + { + SongInfo = null; + } + else + { + SongInfo = new SongInfo + { + Title = mediaProps.Title, + Artist = mediaProps.Artist, + Album = mediaProps?.AlbumTitle ?? string.Empty, + DurationMs = _currentSession + ?.GetTimelineProperties() + .EndTime.TotalMilliseconds, + SourceAppUserModelId = _currentSession?.SourceAppUserModelId, + }; + + if ( + SongInfo.SourceAppUserModelId?.Contains(Package.Current.Id.FamilyName) + ?? false + ) + { + SongInfo.Title = "甜度爆表"; + SongInfo.Artist = "AI"; + } + + if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference) + { + SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference); + } else { - try + SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync( + SongInfo.Title, + SongInfo.Artist + ); + + if (SongInfo.AlbumArt == null) { - SongInfo = await GetSongInfoAsync(); + SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync( + $"{SongInfo.Artist} - {SongInfo.Title}", + 400, + 400 + ); } - catch (Exception) { } } - _dispatcherQueue.TryEnqueue(() => - { - SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo)); - }); - }, - TimeSpan.FromMilliseconds(AnimationHelper.DebounceDefaultDuration) + } + } + _dispatcherQueue.TryEnqueue( + DispatcherQueuePriority.High, + () => + { + SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo)); + } ); } @@ -175,10 +218,13 @@ namespace BetterLyrics.WinUI3.Services.Playback { Position = sender.GetTimelineProperties().Position; } - _dispatcherQueue.TryEnqueue(() => - { - PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position)); - }); + _dispatcherQueue.TryEnqueue( + DispatcherQueuePriority.High, + () => + { + PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position)); + } + ); // _logger.LogDebug(_currentTime); } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/SettingsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs similarity index 74% rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/SettingsService.cs rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs index 992545e..acdf305 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/SettingsService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs @@ -1,15 +1,13 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using BetterLyrics.WinUI3.Enums; -using CommunityToolkit.Mvvm.ComponentModel; +using BetterLyrics.WinUI3.Models; +using BetterLyrics.WinUI3.Serialization; using Microsoft.UI.Xaml; -using Newtonsoft.Json; -using Windows.Media; using Windows.Storage; -namespace BetterLyrics.WinUI3.Services.Settings +namespace BetterLyrics.WinUI3.Services { public class SettingsService : ISettingsService { @@ -17,10 +15,13 @@ namespace BetterLyrics.WinUI3.Services.Settings private const string IsFirstRunKey = "IsFirstRun"; + // Lyrics lib + private const string LocalLyricsFoldersKey = "LocalLyricsFolders"; + private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo"; + // App appearance private const string ThemeTypeKey = "ThemeType"; private const string LanguageKey = "Language"; - private const string MusicLibrariesKey = "MusicLibraries"; private const string BackdropTypeKey = "BackdropType"; // App behavior @@ -49,6 +50,39 @@ namespace BetterLyrics.WinUI3.Services.Settings get => GetValue(IsFirstRunKey); set => SetValue(IsFirstRunKey, value); } + public List LocalLyricsFolders + { + get => + System.Text.Json.JsonSerializer.Deserialize( + GetValue(LocalLyricsFoldersKey) ?? "[]", + SourceGenerationContext.Default.ListLocalLyricsFolder + )!; + set => + SetValue( + LocalLyricsFoldersKey, + System.Text.Json.JsonSerializer.Serialize( + value, + SourceGenerationContext.Default.ListLocalLyricsFolder + ) + ); + } + + public List LyricsSearchProvidersInfo + { + get => + System.Text.Json.JsonSerializer.Deserialize( + GetValue(LyricsSearchProvidersInfoKey) ?? "[]", + SourceGenerationContext.Default.ListLyricsSearchProviderInfo + )!; + set => + SetValue( + LyricsSearchProvidersInfoKey, + System.Text.Json.JsonSerializer.Serialize( + value, + SourceGenerationContext.Default.ListLyricsSearchProviderInfo + ) + ); + } public ElementTheme ThemeType { @@ -74,15 +108,6 @@ namespace BetterLyrics.WinUI3.Services.Settings set => SetValue(AutoStartWindowTypeKey, (int)value); } - public List MusicLibraries - { - get => - JsonConvert.DeserializeObject>( - GetValue(MusicLibrariesKey) ?? "[]" - )!; - set => SetValue(MusicLibrariesKey, JsonConvert.SerializeObject(value)); - } - public bool IsCoverOverlayEnabled { get => GetValue(IsCoverOverlayEnabledKey); @@ -178,17 +203,39 @@ namespace BetterLyrics.WinUI3.Services.Settings _localSettings = ApplicationData.Current.LocalSettings; SetDefault(IsFirstRunKey, true); + // Lyrics lib + SetDefault(LocalLyricsFoldersKey, "[]"); + SetDefault( + LyricsSearchProvidersInfoKey, + System.Text.Json.JsonSerializer.Serialize( + Enum.GetValues() + .Select(p => new LyricsSearchProviderInfo(p, true)) + .ToList(), + SourceGenerationContext.Default.ListLyricsSearchProviderInfo + ) + ); + if (LyricsSearchProvidersInfo.Count != Enum.GetValues().Length) + { + LyricsSearchProvidersInfo = Enum.GetValues() + .Select(p => new LyricsSearchProviderInfo( + p, + LyricsSearchProvidersInfo + .Where(x => x.Provider == p) + .FirstOrDefault() + ?.IsEnabled ?? true + )) + .ToList(); + } // App appearance SetDefault(ThemeTypeKey, (int)ElementTheme.Default); SetDefault(LanguageKey, (int)Language.FollowSystem); - SetDefault(MusicLibrariesKey, "[]"); SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic); // App behavior SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode); // Album art SetDefault(IsCoverOverlayEnabledKey, true); SetDefault(IsDynamicCoverOverlayEnabledKey, true); - SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.1 + SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0 SetDefault(CoverOverlayBlurAmountKey, 200); SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact); SetDefault(CoverImageRadiusKey, 24); // 24 % diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw index 404a7b5..f3f2fa6 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw @@ -121,10 +121,7 @@ Local music libraries - Add folders storing music or lyrics to build lyrics index database - - - Open in file explorer + Add folders storing music or lyrics Open in file explorer @@ -294,18 +291,12 @@ Glow effect scope - - Rebuild lyrics index database - - - Rebuild + + Configure lyrics search providers Add - - Rebuilding the database, please wait... - Welcome to BetterLyrics @@ -468,4 +459,31 @@ Settings + + Loading lyrics... + + + Local .LRC files + + + Local music files + + + LRCLIB + + + 日本語 + + + 한국어 + + + Local .ESLRC files + + + Local .TTML files + + + This folder contains added folders, please delete these folders to add the folder + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw new file mode 100644 index 0000000..97251c5 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 地元の音楽図書館 + + + 音楽や歌詞を保存するフォルダーを追加します + + + ファイルエクスプローラーで開きます + + + アプリから削除します + + + 次のアイテムを削除しても安全です + + + このパスの元のファイルとフォルダーは、このアプリから削除するときに削除されません + + + フォルダーを追加します + + + テーマ + + + 言語 + + + システムをフォローします + + + ライト + + + 暗い + + + 简体中文 + + + 繁體中文 + + + English + + + これはオープンソースアプリです + + + 新しいウィンドウで開きます + + + GitHubのソースコードを参照してください + + + バージョン + + + なし + + + 雲母 + + + マイカル + + + デスクトップアクリル + + + アクリルベース + + + アクリル薄い + + + 透明 + + + 背景 + + + デフォルト + + + 変更を適用するためのアプリを再起動します + + + パスはコンピューターでは見つかりません + + + フォルダーが追加されました。二度と追加しないでください。 + + + オーバーレイアルバムアートの背景 + + + ダイナミックアルバムアートの背景 + + + アルバムアートの背景不透明 + + + 設定 - BetterLyrics + + + BetterLyrics + + + アライメント + + + 中心 + + + + + + + + + アルバムアートバックグラウンドブラー量 + + + ぼやけの量 + + + この値を調整すると、アルバム画像のバックグラウンドブラー強度も増加します。 + + + 現在の値: + + + ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0) + + + この機能を有効にすると、GPUの使用率がわずかに増加します + + + 上端と下端の不透明度 + + + ライン間隔 + + + x線の高さ + + + フォントサイズ + + + 歌詞のみ + + + 没入モード + + + アルバムの背景 + + + について + + + 歌詞ライブラリ + + + アプリの外観 + + + グロー効果 + + + グローエフェクトスコープ + + + 歌詞検索プロバイダーを構成します + + + 追加 + + + BetterLyrics へようこそ + + + 今すぐ歌詞データベースをセットアップしましょう + + + 今は音楽が再生されていません + + + 開発者オプション + + + テスト音楽を再生します + + + システムプレーヤーを使用して再生します + + + ログ + + + フォントカラー + + + デフォルト + + + アルバムアートアクセントカラー + + + アルバムアートスタイル + + + コーナー半径 + + + タイトルバーサイズ + + + コンパクト + + + 拡張 + + + 常にトップ + + + 全画面表示 + + + ESCを押して、フルスクリーンモードを終了します + + + 再びホバリングして、トグルボタンを表示します + + + このメッセージを二度と見せないでください + + + 一致するローカルファイルはありません + + + アルバムアートのみ + + + 分割ビュー + + + 表示タイプを変更します + + + デスクトップ歌詞モードに切り替えます + + + ピクチャーインピクチャーモード + + + ピクチャーインピクチャーモードを終了します + + + 歌詞が見つかりません + + + 歌詞効果 + + + 歌詞スタイル + + + このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません + + + アプリの動作 + + + アプリを起動するとき + + + 標準モードをアクティブにします + + + ドックモードをアクティブにします + + + 歌詞が見つかりません + + + システムトレイ - BetterLyrics + + + ドックモード + + + フォント重量 + + + 薄い + + + 余分な光 + + + ライト + + + 半光 + + + 普通 + + + 中くらい + + + セミボールド + + + 大胆な + + + 余分な太字 + + + + + + 余分な黒 + + + 歌詞全体 + + + 現在の行 + + + 現在の文字 + + + 設定 + + + 歌詞の読み込み... + + + ローカル.LRCファイル + + + ローカル音楽ファイル + + + LRCLIB + + + 日本語 + + + 한국어 + + + ローカル.ESLRCファイル + + + ローカル.TTMLファイル + + + このフォルダーには追加されたフォルダーが含まれています。これらのフォルダを削除してフォルダーを追加してください + + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw new file mode 100644 index 0000000..bb6a7b6 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 로컬 음악 도서관 + + + 음악이나 가사를 저장하는 폴더 추가 + + + 파일 탐색기에서 열립니다 + + + 앱에서 제거하십시오 + + + 다음 항목을 제거하는 것이 안전합니다 + + + 이 경로의 원본 파일과 폴더는이 앱에서 제거 할 때 삭제되지 않습니다. + + + 폴더를 추가하십시오 + + + 주제 + + + 언어 + + + 시스템을 따르십시오 + + + + + + 어두운 + + + 简体中文 + + + 繁體中文 + + + English + + + 이것은 오픈 소스 앱입니다 + + + 새 창에서 열립니다 + + + GitHub의 소스 코드를 참조하십시오 + + + 버전 + + + 없음 + + + 운모 + + + 운모 대체 + + + 데스크탑 아크릴 + + + 아크릴베이스 + + + 아크릴 얇은 + + + 투명한 + + + 배경 + + + 기본 + + + 변경 사항을 적용하려면 앱을 다시 시작하십시오 + + + 경로는 컴퓨터에서 찾을 수 없습니다 + + + 폴더가 추가되었습니다. 다시 추가하지 마십시오. + + + 오버레이 앨범 아트 배경 + + + 동적 앨범 아트 배경 + + + 앨범 아트 배경 불투명도 + + + 설정 - BetterLyrics + + + BetterLyrics + + + 조정 + + + 센터 + + + 왼쪽 + + + 오른쪽 + + + 앨범 아트 배경 흐림 금액 + + + 흐림 금액 + + + 이 값을 조정하면 앨범 이미지의 배경 흐림 강도가 증가합니다. + + + 현재 가치 : + + + Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0) + + + 이 기능을 활성화하면 GPU 사용률이 약간 증가합니다 + + + 상단 및 하단 가장자리 불투명도 + + + 라인 간격 + + + X 라인 높이 + + + 글꼴 크기 + + + 가사 만 + + + 몰입 형 모드 + + + 앨범 배경 + + + 에 대한 + + + 가사 도서관 + + + 앱 모양 + + + 글로우 효과 + + + 글로우 효과 범위 + + + 가사 검색 제공 업체를 구성하십시오 + + + 추가하다 + + + Betterlyrics에 오신 것을 환영합니다 + + + 지금 가사 데이터베이스를 설정합시다 + + + 지금 음악이 재생되지 않습니다 + + + 개발자 옵션 + + + 테스트 음악을 재생하십시오 + + + 시스템 플레이어를 사용하여 재생하십시오 + + + 통나무 + + + 글꼴 색상 + + + 기본 + + + 앨범 아트 악센트 색상 + + + 앨범 아트 스타일 + + + 코너 반경 + + + 제목 바 크기 + + + 콤팩트 + + + 펼친 + + + 항상 위에 + + + 전체 화면 + + + ESC를 눌러 전체 화면 모드를 종료하십시오 + + + 토글 버튼을 표시하려면 다시 다시 가져옵니다 + + + 이 메시지를 다시 표시하지 마십시오 + + + 로컬 파일이 일치하지 않습니다 + + + 앨범 아트 만 + + + 분할보기 + + + 디스플레이 유형을 변경하십시오 + + + 데스크탑 가사 모드로 전환하십시오 + + + 사진 인당 모드 + + + Picture-in-Picture 모드 종료 + + + 가사를 찾을 수 없습니다 + + + 가사 효과 + + + 가사 스타일 + + + 이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다. + + + 앱 동작 + + + 앱을 시작할 때 + + + 표준 모드를 ​​활성화합니다 + + + 도크 모드를 활성화하십시오 + + + 가사를 찾을 수 없습니다 + + + 시스템 트레이 - BetterLyrics + + + 도크 모드 + + + 글꼴 무게 + + + 얇은 + + + 여분의 빛 + + + + + + 반 빛 + + + 정상 + + + 중간 + + + 반 대담한 + + + 용감한 + + + 추가 대담한 + + + 검은색 + + + 여분의 검은 색 + + + 전체 가사 + + + 현재 라인 + + + 현재 숯 + + + 설정 + + + 가사로드 ... + + + 로컬 .LRC 파일 + + + 로컬 음악 파일 + + + LRCLIB + + + 日本語 + + + 한국어 + + + 로컬 .ESLRC 파일 + + + 로컬 .TTML 파일 + + + 이 폴더에는 추가 된 폴더가 포함되어 있습니다. 폴더를 추가하려면이 폴더를 삭제하십시오. + + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw index 0c0fcd9..fc94a83 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw @@ -121,10 +121,7 @@ 本地音乐媒体库 - 添加存放音乐或歌词的文件夹以构建歌词索引数据库 - - - 在文件资源管理器中打开 + 添加存放音乐或歌词的文件夹 在文件资源管理器中打开 @@ -294,18 +291,12 @@ 辉光效果作用范围 - - 重构歌词索引数据库 - - - 重构 + + 配置歌词搜索服务 添加 - - 重构数据库中,请稍候... - 欢迎使用 BetterLyrics @@ -468,4 +459,31 @@ 设置 + + 加载歌词中... + + + 本地 .LRC 文件 + + + 本地音乐文件 + + + LRCLIB + + + 日本語 + + + 한국어 + + + 本地 .ESLRC 文件 + + + 本地 .TTML 文件 + + + 该文件夹包含已添加文件夹,请删除这些文件夹以添加该文件夹 + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw index 7bf3dbf..fd74f82 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw @@ -121,10 +121,7 @@ 本地音樂媒體庫 - 新增存放音樂或歌詞的資料夾以建立歌詞索引資料庫 - - - 在檔案總管中開啟 + 新增存放音樂或歌詞的資料夾 在檔案總管中開啟 @@ -294,18 +291,12 @@ 輝光效果作用範圍 - - 重構歌詞索引資料庫 - - - 重構 + + 配置歌詞搜尋服務 添加 - - 重構資料庫中,請稍候... - 歡迎使用 BetterLyrics @@ -468,4 +459,31 @@ 設定 + + 載入歌詞中... + + + 本地 .lRC 文件 + + + 本地音樂文件 + + + LRCLIB + + + 日本語 + + + 한국어 + + + 本地 .ESLRC 文件 + + + 本地 .TTML 文件 + + + 該文件夾包含已添加文件夾,請刪除這些文件夾以添加該文件夾 + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseRendererViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseRendererViewModel.cs deleted file mode 100644 index 0c61d1b..0000000 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseRendererViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using BetterLyrics.WinUI3.Services.Settings; -using BetterLyrics.WinUI3.ViewModels; -using Microsoft.Graphics.Canvas.UI.Xaml; - -namespace BetterLyrics.WinUI3.Rendering -{ - public partial class BaseRendererViewModel(ISettingsService settingsService) - : BaseViewModel(settingsService) - { - public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; - public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero; - - public virtual void Calculate( - ICanvasAnimatedControl control, - CanvasAnimatedUpdateEventArgs args - ) - { - TotalTime += args.Timing.ElapsedTime; - ElapsedTime = args.Timing.ElapsedTime; - } - } -} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs index 093f299..b8f1332 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.CompilerServices; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Dispatching; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs index e5c7d88..c8faa18 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs @@ -5,13 +5,12 @@ using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Messages; using BetterLyrics.WinUI3.Models; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; using BetterLyrics.WinUI3.ViewModels; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; -using H.NotifyIcon.Interop; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Windows.UI; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs index 9f48f45..7e8e950 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading.Tasks; using BetterInAppLyrics.WinUI3.ViewModels; @@ -6,8 +7,7 @@ using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Messages; using BetterLyrics.WinUI3.Models; -using BetterLyrics.WinUI3.Services.Playback; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; @@ -21,7 +21,8 @@ namespace BetterLyrics.WinUI3.ViewModels public partial class LyricsPageViewModel : BaseViewModel, IRecipient>, - IRecipient> + IRecipient>, + IRecipient> { private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToDockMode; @@ -40,10 +41,16 @@ namespace BetterLyrics.WinUI3.ViewModels [ObservableProperty] public partial SongInfo? SongInfo { get; set; } = null; + [ObservableProperty] + public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading; + [ObservableProperty] public partial LyricsDisplayType? PreferredDisplayType { get; set; } = LyricsDisplayType.SplitView; + [ObservableProperty] + public partial int LyricsFontSize { get; set; } + [ObservableProperty] public partial bool AboutToUpdateUI { get; set; } @@ -73,6 +80,7 @@ namespace BetterLyrics.WinUI3.ViewModels ) : base(settingsService) { + LyricsFontSize = _settingsService.LyricsFontSize; CoverImageRadius = _settingsService.CoverImageRadius; _playbackService = playbackService; @@ -173,13 +181,20 @@ namespace BetterLyrics.WinUI3.ViewModels public void Receive(PropertyChangedMessage message) { - if (message.Sender.GetType() == typeof(SettingsViewModel)) + if (message.Sender is SettingsViewModel) { if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius)) { CoverImageRadius = message.NewValue; } } + if (message.Sender is LyricsSettingsControlViewModel) + { + if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize)) + { + LyricsFontSize = message.NewValue; + } + } } public void Receive(PropertyChangedMessage message) @@ -201,17 +216,15 @@ namespace BetterLyrics.WinUI3.ViewModels TrySwitchToPreferredDisplayType(SongInfo); } } - else if (message.Sender is SettingsViewModel) + } + + public void Receive(PropertyChangedMessage message) + { + if (message.Sender is LyricsRendererViewModel) { - if ( - message.PropertyName - == nameof(SettingsViewModel.IsRebuildingLyricsIndexDatabase) - ) + if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsStatus)) { - if (!message.NewValue) - { - _playbackService.ReSendingMessages(); - } + LyricsStatus = message.NewValue; } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs index 0b54bd8..44db46d 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs @@ -1,20 +1,16 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; -using System.IO; using System.Linq; using System.Numerics; -using System.Text; using System.Threading.Tasks; using BetterInAppLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Events; using BetterLyrics.WinUI3.Helper; -using BetterLyrics.WinUI3.Messages; using BetterLyrics.WinUI3.Models; -using BetterLyrics.WinUI3.Rendering; -using BetterLyrics.WinUI3.Services.Playback; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; @@ -24,11 +20,7 @@ using Microsoft.Graphics.Canvas.Effects; using Microsoft.Graphics.Canvas.Text; using Microsoft.Graphics.Canvas.UI.Xaml; using Microsoft.UI; -using Microsoft.UI.Dispatching; -using Microsoft.UI.Text; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Shapes; using Windows.Foundation; using Windows.Graphics.Imaging; using Windows.UI; @@ -36,7 +28,7 @@ using Windows.UI; namespace BetterLyrics.WinUI3.ViewModels { public partial class LyricsRendererViewModel - : BaseRendererViewModel, + : BaseViewModel, IRecipient>, IRecipient>, IRecipient>, @@ -47,7 +39,9 @@ namespace BetterLyrics.WinUI3.ViewModels IRecipient>, IRecipient>, IRecipient>, - IRecipient> + IRecipient>, + IRecipient>>, + IRecipient>> { private protected CanvasTextFormat _textFormat = new() { @@ -55,38 +49,27 @@ namespace BetterLyrics.WinUI3.ViewModels VerticalAlignment = CanvasVerticalAlignment.Top, }; + public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; + public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero; + public LyricsDisplayType DisplayType { get; set; } private float _rotateAngle = 0f; - private byte[] _shaderByteCode = File.ReadAllBytes(AppInfo.CustomShaderPath); - private Color ActivatedWindowAccentColor { get; set; } = Colors.Transparent; + private Color? _albumArtAccentColor = null; private bool IsDockMode { get; set; } = false; [ObservableProperty] public partial SongInfo? SongInfo { get; set; } + private List> _multiLangLyrics = []; + private int _langIndex = 0; + private List? _lyricsForGlowEffect = []; - private SoftwareBitmap? _lastSoftwareBitmap = null; - private SoftwareBitmap? _softwareBitmap = null; - private SoftwareBitmap? SoftwareBitmap - { - get => _softwareBitmap; - set - { - if (_softwareBitmap != null) - { - _lastSoftwareBitmap = _softwareBitmap; - _transitionStartTime = DateTimeOffset.Now; - _isTransitioning = true; - _transitionAlpha = 0f; - } - - _softwareBitmap = value; - } - } + private SoftwareBitmap? _lastAlbumArtBitmap = null; + private SoftwareBitmap? _albumArtBitmap = null; public int CoverImageRadius { get; set; } public bool IsCoverOverlayEnabled { get; set; } @@ -94,8 +77,11 @@ namespace BetterLyrics.WinUI3.ViewModels public int CoverOverlayOpacity { get; set; } public int CoverOverlayBlurAmount { get; set; } + private bool _isPlaying = true; + + [NotifyPropertyChangedRecipients] [ObservableProperty] - public partial bool IsPlaying { get; set; } + public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading; private protected Color _fontColor; @@ -110,24 +96,15 @@ namespace BetterLyrics.WinUI3.ViewModels private readonly int _lineEnteringDurationMs = 800; private readonly int _lineExitingDurationMs = 800; - private readonly int _lineScrollDurationMs = 800; - - private float _lastTotalYScroll = 0.0f; - private float _totalYScroll = 0.0f; private int _startVisibleLineIndex = -1; private int _endVisibleLineIndex = -1; - private bool _forceToScroll = false; - private readonly float _lyricsGlowEffectAmount = 6f; private readonly double _rightMargin = 36; private readonly float _topMargin = 0f; - [ObservableProperty] - public partial double LimitedLineWidth { get; set; } - private protected bool _isRelayoutNeeded = true; [ObservableProperty] @@ -152,26 +129,49 @@ namespace BetterLyrics.WinUI3.ViewModels public LyricsGlowEffectScope LyricsGlowEffectScope { get; set; } private protected readonly IPlaybackService _playbackService; - - private float _transitionAlpha = 1f; - private TimeSpan _transitionDuration = TimeSpan.FromMilliseconds(1000); - private DateTimeOffset _transitionStartTime; - private bool _isTransitioning = false; + private protected readonly IMusicSearchService _musicSearchService; + private readonly ILibWatcherService _libWatcherService; private readonly float _coverRotateSpeed = 0.003f; - private Color _currentBgColor; - private Color _targetBgColor; - private float _colorTransitionProgress = 1f; - private const float ColorTransitionDuration = 0.3f; // 秒 - private bool _isColorTransitioning = false; + private readonly ValueTransition _immersiveBgrTransition = new( + initialValue: Colors.Transparent, + durationSeconds: 0.3f, + interpolator: (from, to, progress) => + Helper.ColorHelper.GetInterpolatedColor(progress, from, to) + ); + + private readonly ValueTransition _albumArtBgTransition = new( + initialValue: 0f, + durationSeconds: 1.0f, + interpolator: (from, to, progress) => from + (to - from) * progress + ); + + private readonly ValueTransition _canvasYScrollTransition = new( + initialValue: 0f, + durationSeconds: 0.8f, + interpolator: (from, to, progress) => + from + (to - from) * EasingHelper.SmootherStep(progress) + ); + + private readonly ValueTransition _limitedLineWidthTransition = new( + initialValue: 0f, + durationSeconds: 0.8f, + interpolator: (from, to, progress) => to + ); public LyricsRendererViewModel( ISettingsService settingsService, - IPlaybackService playbackService + IPlaybackService playbackService, + IMusicSearchService musicSearchService, + ILibWatcherService libWatcherService ) : base(settingsService) { + _musicSearchService = musicSearchService; + _playbackService = playbackService; + _libWatcherService = libWatcherService; + CoverImageRadius = _settingsService.CoverImageRadius; IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled; IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled; @@ -188,16 +188,68 @@ namespace BetterLyrics.WinUI3.ViewModels IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled; LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope; - _playbackService = playbackService; + _libWatcherService.MusicLibraryFilesChanged += + LibWatcherService_MusicLibraryFilesChanged; + _playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged; _playbackService.SongInfoChanged += PlaybackService_SongInfoChanged; _playbackService.PositionChanged += PlaybackService_PositionChanged; RefreshPlaybackInfo(); - UpdateFontColor(); } + private void LibWatcherService_MusicLibraryFilesChanged( + object? sender, + Events.LibChangedEventArgs e + ) + { + RefreshLyricsAsync().ConfigureAwait(true); + } + + /// + /// Should invoke this function when: + /// 1. The song info is changed (new song is played). + /// 2. Lyrics search provider info is changed (change order, enable or disable any provider). + /// 3. Local music/lyrics files are changed (added, removed, renamed). + /// + /// + private async Task RefreshLyricsAsync() + { + _multiLangLyrics = []; + _isRelayoutNeeded = true; + LyricsStatus = LyricsStatus.Loading; + string? lyricsRaw = null; + LyricsFormat? lyricsFormat = null; + + if (SongInfo != null) + { + (lyricsRaw, lyricsFormat) = await _musicSearchService.SearchLyricsAsync( + SongInfo.Title, + SongInfo.Artist, + SongInfo.Album ?? "", + SongInfo.DurationMs ?? 0 + ); + } + + if (lyricsRaw == null) + { + LyricsStatus = LyricsStatus.NotFound; + } + else if (SongInfo != null) + { + _multiLangLyrics = new LyricsParser().Parse( + lyricsRaw, + lyricsFormat, + SongInfo.Title, + SongInfo.Artist, + (int)(SongInfo.DurationMs ?? 0) + ); + _isRelayoutNeeded = true; + LyricsStatus = LyricsStatus.Found; + } + } + public void RequestRelayout() { _isRelayoutNeeded = true; @@ -215,29 +267,43 @@ namespace BetterLyrics.WinUI3.ViewModels private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e) { - IsPlaying = e.IsPlaying; + _isPlaying = e.IsPlaying; } public void RefreshPlaybackInfo() { - IsPlaying = _playbackService.IsPlaying; + _isPlaying = _playbackService.IsPlaying; SongInfo = _playbackService.SongInfo; TotalTime = _playbackService.Position; } - partial void OnLimitedLineWidthChanged(double value) + async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue) { - _isRelayoutNeeded = true; - } + TotalTime = TimeSpan.Zero; - async partial void OnSongInfoChanged(SongInfo? value) - { - if (value?.AlbumArt is byte[] bytes) - SoftwareBitmap = await ( + _lastAlbumArtBitmap = _albumArtBitmap; + + if (newValue?.AlbumArt is byte[] bytes) + { + _albumArtBitmap = await ( await ImageHelper.GetDecoderFromByte(bytes) ).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); + _albumArtAccentColor = ( + await ImageHelper.GetAccentColorsFromByte(bytes) + ).FirstOrDefault(); + } + else + { + _albumArtBitmap = null; + _albumArtAccentColor = null; + } + UpdateFontColor(); - _isRelayoutNeeded = true; + + _albumArtBgTransition.Reset(0f); + _albumArtBgTransition.StartTransition(1f); + + await RefreshLyricsAsync(); } partial void OnLyricsFontSizeChanged(int value) @@ -267,36 +333,39 @@ namespace BetterLyrics.WinUI3.ViewModels private protected void UpdateFontColor() { - switch (LyricsFontColorType) + Color fallback = Colors.Transparent; + switch (Theme) { - case LyricsFontColorType.Default: - switch (Theme) + case ElementTheme.Default: + switch (Application.Current.RequestedTheme) { - case ElementTheme.Default: - switch (Application.Current.RequestedTheme) - { - case ApplicationTheme.Light: - _fontColor = _darkFontColor; - break; - case ApplicationTheme.Dark: - _fontColor = _lightFontColor; - break; - default: - break; - } + case ApplicationTheme.Light: + fallback = _darkFontColor; break; - case ElementTheme.Light: - _fontColor = _darkFontColor; - break; - case ElementTheme.Dark: - _fontColor = _lightFontColor; + case ApplicationTheme.Dark: + fallback = _lightFontColor; break; default: break; } break; + case ElementTheme.Light: + fallback = _darkFontColor; + break; + case ElementTheme.Dark: + fallback = _lightFontColor; + break; + default: + break; + } + + switch (LyricsFontColorType) + { + case LyricsFontColorType.Default: + _fontColor = fallback; + break; case LyricsFontColorType.Dominant: - _fontColor = SongInfo?.CoverImageDominantColors?[0] ?? _lightFontColor; + _fontColor = _albumArtAccentColor ?? fallback; break; default: break; @@ -305,10 +374,10 @@ namespace BetterLyrics.WinUI3.ViewModels private int GetCurrentPlayingLineIndex() { - for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++) + for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++) { - var line = SongInfo?.LyricsLines?[i]; - if (line.EndPlayingTimestampMs < TotalTime.TotalMilliseconds) + var line = _multiLangLyrics.SafeGet(_langIndex)?[i]; + if (line?.EndMs < TotalTime.TotalMilliseconds) { continue; } @@ -326,12 +395,16 @@ namespace BetterLyrics.WinUI3.ViewModels private Tuple GetMaxLyricsLineIndexBoundaries() { - if (SongInfo == null || SongInfo.LyricsLines == null || SongInfo.LyricsLines.Count == 0) + if ( + SongInfo == null + || _multiLangLyrics.SafeGet(_langIndex) == null + || _multiLangLyrics[_langIndex].Count == 0 + ) { return new Tuple(-1, -1); } - return new Tuple(0, SongInfo.LyricsLines.Count - 1); + return new Tuple(0, _multiLangLyrics[_langIndex].Count - 1); } private void DrawLyrics( @@ -345,8 +418,6 @@ namespace BetterLyrics.WinUI3.ViewModels var (displayStartLineIndex, displayEndLineIndex) = GetVisibleLyricsLineIndexBoundaries(); - var currentPlayingLineIndex = GetCurrentPlayingLineIndex(); - for ( int i = displayStartLineIndex; source?.Count > 0 && i >= 0 && i < source?.Count && i <= displayEndLineIndex; @@ -359,7 +430,7 @@ namespace BetterLyrics.WinUI3.ViewModels control, line?.Text, _textFormat, - (float)LimitedLineWidth, + (float)_limitedLineWidthTransition.Value, (float)control.Size.Height ); @@ -377,11 +448,11 @@ namespace BetterLyrics.WinUI3.ViewModels break; case LyricsAlignmentType.Center: textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center; - centerX += (float)LimitedLineWidth / 2; + centerX += (float)_limitedLineWidthTransition.Value / 2; break; case LyricsAlignmentType.Right: textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right; - centerX += (float)LimitedLineWidth; + centerX += (float)_limitedLineWidthTransition.Value; break; default: break; @@ -463,8 +534,10 @@ namespace BetterLyrics.WinUI3.ViewModels ds.Transform = Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY)) * Matrix3x2.CreateTranslation( - (float)(control.Size.Width - _rightMargin - LimitedLineWidth), - _totalYScroll + (float)(control.Size.Height / 2) + (float)( + control.Size.Width - _rightMargin - _limitedLineWidthTransition.Value + ), + _canvasYScrollTransition.Value + (float)(control.Size.Height / 2) ); ds.DrawTextLayout(textLayout, position, Colors.Transparent); @@ -533,13 +606,17 @@ namespace BetterLyrics.WinUI3.ViewModels Color = withGradient ? Color.FromArgb( 211, - _currentBgColor.R, - _currentBgColor.G, - _currentBgColor.B + _immersiveBgrTransition.Value.R, + _immersiveBgrTransition.Value.G, + _immersiveBgrTransition.Value.B ) - : _currentBgColor, + : _immersiveBgrTransition.Value, + }, + new CanvasGradientStop + { + Position = 1, + Color = _immersiveBgrTransition.Value, }, - new CanvasGradientStop { Position = 1, Color = _currentBgColor }, ] ) { @@ -556,14 +633,30 @@ namespace BetterLyrics.WinUI3.ViewModels var overlappedCovers = new CanvasCommandList(control.Device); using var overlappedCoversDs = overlappedCovers.CreateDrawingSession(); - if (_isTransitioning && _lastSoftwareBitmap != null) + if (_albumArtBgTransition.IsTransitioning) { - DrawImgae(control, overlappedCoversDs, _lastSoftwareBitmap, 1 - _transitionAlpha); - DrawImgae(control, overlappedCoversDs, SoftwareBitmap, _transitionAlpha); + if (_lastAlbumArtBitmap != null) + { + DrawImgae( + control, + overlappedCoversDs, + _lastAlbumArtBitmap, + 1 - _albumArtBgTransition.Value + ); + } + if (_albumArtBitmap != null) + { + DrawImgae( + control, + overlappedCoversDs, + _albumArtBitmap, + _albumArtBgTransition.Value + ); + } } - else + else if (_albumArtBitmap != null) { - DrawImgae(control, overlappedCoversDs, SoftwareBitmap, 1); + DrawImgae(control, overlappedCoversDs, _albumArtBitmap, 1f); } using var coverOverlayEffect = new OpacityEffect @@ -582,15 +675,14 @@ namespace BetterLyrics.WinUI3.ViewModels public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds) { - bool isAlbumArtOverlayDrawn = IsCoverOverlayEnabled && SoftwareBitmap != null; - if (isAlbumArtOverlayDrawn) + if (IsCoverOverlayEnabled) { DrawAlbumArtBackground(control, ds); } if (IsDockMode) { - DrawImmersiveBackground(control, ds, isAlbumArtOverlayDrawn); + DrawImmersiveBackground(control, ds, IsCoverOverlayEnabled); } // Original lyrics only layer @@ -607,7 +699,7 @@ namespace BetterLyrics.WinUI3.ViewModels DrawLyrics( control, lyricsDs, - SongInfo?.LyricsLines, + _multiLangLyrics.SafeGet(_langIndex), _defaultOpacity, LyricsHighlightType.LineByLine ); @@ -769,6 +861,10 @@ namespace BetterLyrics.WinUI3.ViewModels ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush); } + /// + /// Reassigns positions (x,y) to lyrics lines based on the current control size and font size. + /// + /// private void ReLayout(ICanvasAnimatedControl control) { if (control == null) @@ -779,16 +875,21 @@ namespace BetterLyrics.WinUI3.ViewModels float y = _topMargin; // Init Positions - for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++) + for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++) { - var line = SongInfo?.LyricsLines?[i]; + var line = _multiLangLyrics[_langIndex].SafeGet(i); + + if (line == null) + { + continue; + } // Calculate layout bounds using var textLayout = new CanvasTextLayout( control, line.Text, _textFormat, - (float)LimitedLineWidth, + (float)_limitedLineWidthTransition.Value, (float)control.Size.Height ); line.Position = new Vector2(0, y); @@ -800,47 +901,23 @@ namespace BetterLyrics.WinUI3.ViewModels } } - public override void Calculate( - ICanvasAnimatedControl control, - CanvasAnimatedUpdateEventArgs args - ) + public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args) { - base.Calculate(control, args); - - if (_isColorTransitioning) + if (_isPlaying) { - _colorTransitionProgress += - (float)ElapsedTime.TotalSeconds / ColorTransitionDuration; - if (_colorTransitionProgress >= 1f) - { - _colorTransitionProgress = 1f; - _isColorTransitioning = false; - _currentBgColor = _targetBgColor; - } - else - { - _currentBgColor = Helper.ColorHelper.GetInterpolatedColor( - _colorTransitionProgress, - _currentBgColor, - _targetBgColor - ); - } + TotalTime += args.Timing.ElapsedTime; } - if (_isTransitioning) - { - var elapsed = DateTimeOffset.Now - _transitionStartTime; - float progress = (float)( - elapsed.TotalMilliseconds / _transitionDuration.TotalMilliseconds - ); - _transitionAlpha = Math.Clamp(progress, 0f, 1f); + ElapsedTime = args.Timing.ElapsedTime; - if (_transitionAlpha >= 1f) - { - _isTransitioning = false; - _lastSoftwareBitmap?.Dispose(); - _lastSoftwareBitmap = null; - } + if (_immersiveBgrTransition.IsTransitioning) + { + _immersiveBgrTransition.Update(ElapsedTime); + } + + if (_albumArtBgTransition.IsTransitioning) + { + _albumArtBgTransition.Update(ElapsedTime); } if (IsDynamicCoverOverlayEnabled) @@ -849,31 +926,37 @@ namespace BetterLyrics.WinUI3.ViewModels _rotateAngle %= MathF.PI * 2; } + if (_limitedLineWidthTransition.IsTransitioning) + { + _limitedLineWidthTransition.Update(ElapsedTime); + _isRelayoutNeeded = true; + } + if (_isRelayoutNeeded) { ReLayout(control); _isRelayoutNeeded = false; - _forceToScroll = true; } - int currentPlayingLineIndex = GetCurrentPlayingLineIndex(); - - CalculateLinesProps(SongInfo?.LyricsLines, currentPlayingLineIndex, _defaultOpacity); - CalculateCanvasYScrollOffset(control, currentPlayingLineIndex); + UpdateLinesProps(_multiLangLyrics.SafeGet(_langIndex), _defaultOpacity); + UpdateCanvasYScrollOffset(control); if (IsLyricsGlowEffectEnabled) { // Deep copy lyrics lines for glow effect - _lyricsForGlowEffect = SongInfo?.LyricsLines?.Select(line => line.Clone()).ToList(); + _lyricsForGlowEffect = _multiLangLyrics + .SafeGet(_langIndex) + ?.Select(line => line.Clone()) + .ToList(); switch (LyricsGlowEffectScope) { case LyricsGlowEffectScope.WholeLyrics: break; case LyricsGlowEffectScope.CurrentLine: - CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0); + UpdateLinesProps(_lyricsForGlowEffect, 0); break; case LyricsGlowEffectScope.CurrentChar: - CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0); + UpdateLinesProps(_lyricsForGlowEffect, 0); break; default: break; @@ -881,17 +964,75 @@ namespace BetterLyrics.WinUI3.ViewModels } } - private void CalculateLinesProps( - List? source, - int currentPlayingLineIndex, - float defaultOpacity - ) + private float GetLinePlayingProgress(LyricsLine line) + { + float playProgress = 0f; + int now = (int)TotalTime.TotalMilliseconds; + + if (line.CharTimings != null && line.CharTimings.Count > 0) + { + int charIndex = 0; + for (; charIndex < line.CharTimings.Count; charIndex++) + { + var timing = line.CharTimings[charIndex]; + if (now < timing.StartMs) + { + // 当前时间还没到这个字,停在上一个字 + break; + } + if (now >= timing.StartMs && now <= timing.EndMs) + { + float charProgress = 1f; + if (timing.EndMs != timing.StartMs) + { + charProgress = + (now - timing.StartMs) / (float)(timing.EndMs - timing.StartMs); + } + // 当前时间在这个字的高亮区间 + playProgress = charIndex + charProgress; + playProgress /= line.CharTimings.Count; + return playProgress; + } + } + // 如果超出最后一个字的结束时间 + if (now > line.CharTimings[^1].EndMs) + { + // 如果还没到行尾,保持最后一个字高亮 + if (now < line.EndMs) + { + playProgress = 1f; // 全部字高亮 + } + else + { + playProgress = 1f; // 行已结束 + } + } + else if (charIndex == 0) + { + playProgress = 0f; // 还没到第一个字 + } + } + else + { + playProgress = (now - line.StartMs) / (float)(line.DurationMs); + } + return playProgress; + } + + private void UpdateLinesProps(List? source, float defaultOpacity) { var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries(); - for (int i = startLineIndex; source?.Count > 0 && i <= endLineIndex; i++) + var currentPlayingLineIndex = GetCurrentPlayingLineIndex(); + + for (int i = startLineIndex; i <= endLineIndex; i++) { - var line = source?[i]; + var line = source?.SafeGet(i); + + if (line == null) + { + continue; + } bool linePlaying = i == currentPlayingLineIndex; @@ -923,12 +1064,9 @@ namespace BetterLyrics.WinUI3.ViewModels scale = _highlightedScale; opacity = _highlightedOpacity; - playProgress = - ((float)TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs) - / line.DurationMs; + playProgress = GetLinePlayingProgress(line); - var durationFromStartMs = - TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs; + var durationFromStartMs = TotalTime.TotalMilliseconds - line.StartMs; lineEntering = durationFromStartMs <= lineEnteringDurationMs; if (lineEntering) { @@ -948,8 +1086,7 @@ namespace BetterLyrics.WinUI3.ViewModels line.PlayingState = LyricsPlayingState.Played; playProgress = 1; - var durationToEndMs = - TotalTime.TotalMilliseconds - line.EndPlayingTimestampMs; + var durationToEndMs = TotalTime.TotalMilliseconds - line.EndMs; lineExiting = durationToEndMs <= lineExitingDurationMs; if (lineExiting) { @@ -979,11 +1116,9 @@ namespace BetterLyrics.WinUI3.ViewModels } } - private void CalculateCanvasYScrollOffset( - ICanvasAnimatedControl control, - int currentPlayingLineIndex - ) + private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control) { + var currentPlayingLineIndex = GetCurrentPlayingLineIndex(); if (currentPlayingLineIndex < 0) { return; @@ -997,7 +1132,9 @@ namespace BetterLyrics.WinUI3.ViewModels } // Set _scrollOffsetY - LyricsLine? currentPlayingLine = SongInfo?.LyricsLines?[currentPlayingLineIndex]; + LyricsLine? currentPlayingLine = _multiLangLyrics + .SafeGet(_langIndex) + ?[currentPlayingLineIndex]; if (currentPlayingLine == null) { @@ -1008,63 +1145,49 @@ namespace BetterLyrics.WinUI3.ViewModels control, currentPlayingLine.Text, _textFormat, - (float)LimitedLineWidth, + (float)_limitedLineWidthTransition.Value, (float)control.Size.Height ); - var lineScrollingProgress = - (TotalTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs) - / Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs); - float targetYScrollOffset = (float?)( -currentPlayingLine.Position.Y - + SongInfo?.LyricsLines?[0].Position.Y + + _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2 - - _lastTotalYScroll ) ?? 0f; - var yScrollOffset = - targetYScrollOffset - * EasingHelper.SmootherStep((float)Math.Min(1, lineScrollingProgress)); - - bool isScrollingNow = lineScrollingProgress <= 1; - - if (isScrollingNow) + if (!_canvasYScrollTransition.IsTransitioning) { - _totalYScroll = _lastTotalYScroll + yScrollOffset; + _canvasYScrollTransition.StartTransition(targetYScrollOffset); } - else + + if (_canvasYScrollTransition.IsTransitioning) { - if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1) - { - _totalYScroll = _lastTotalYScroll + targetYScrollOffset; - _forceToScroll = false; - } - _lastTotalYScroll = _totalYScroll; + _canvasYScrollTransition.Update(ElapsedTime); } _startVisibleLineIndex = _endVisibleLineIndex = -1; // Update visible line indices - for ( - int i = startLineIndex; - i >= 0 && i <= endLineIndex && i < SongInfo?.LyricsLines?.Count; - i++ - ) + for (int i = startLineIndex; i <= endLineIndex; i++) { - var line = SongInfo?.LyricsLines?[i]; + var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i); + + if (line == null) + { + continue; + } using var textLayout = new CanvasTextLayout( control, - line.Text, + line?.Text, _textFormat, - (float)LimitedLineWidth, + (float)_limitedLineWidthTransition.Value, (float)control.Size.Height ); if ( - _totalYScroll + _canvasYScrollTransition.Value + (float)(control.Size.Height / 2) + line.Position.Y + textLayout.LayoutBounds.Height @@ -1077,7 +1200,7 @@ namespace BetterLyrics.WinUI3.ViewModels } } if ( - _totalYScroll + _canvasYScrollTransition.Value + (float)(control.Size.Height / 2) + line.Position.Y + textLayout.LayoutBounds.Height @@ -1261,7 +1384,7 @@ namespace BetterLyrics.WinUI3.ViewModels { if (message.PropertyName == nameof(LyricsPageViewModel.LimitedLineWidth)) { - LimitedLineWidth = message.NewValue; + _limitedLineWidthTransition.StartTransition((float)message.NewValue); } } } @@ -1302,17 +1425,33 @@ namespace BetterLyrics.WinUI3.ViewModels { if (message.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor)) { - _currentBgColor = _isColorTransitioning - ? Helper.ColorHelper.GetInterpolatedColor( - _colorTransitionProgress, - _currentBgColor, - _targetBgColor - ) - : ActivatedWindowAccentColor; - _targetBgColor = message.NewValue; - _colorTransitionProgress = 0f; - _isColorTransitioning = true; - ActivatedWindowAccentColor = message.NewValue; + _immersiveBgrTransition.StartTransition(message.NewValue); + } + } + } + + public void Receive( + PropertyChangedMessage> message + ) + { + if (message.Sender is SettingsViewModel) + { + if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo)) + { + // Lyrics search providers info changed, re-fetch lyrics + RefreshLyricsAsync().ConfigureAwait(true); + } + } + } + + public void Receive(PropertyChangedMessage> message) + { + if (message.Sender is SettingsViewModel) + { + if (message.PropertyName == nameof(SettingsViewModel.LocalLyricsFolders)) + { + // Music lib changed, re-fetch lyrics + RefreshLyricsAsync().ConfigureAwait(true); } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs index f3303d4..df156db 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using BetterLyrics.WinUI3.Enums; -using BetterLyrics.WinUI3.Helper; -using BetterLyrics.WinUI3.Services.Playback; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Services; using BetterLyrics.WinUI3.ViewModels; using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.UI; -using Windows.UI; namespace BetterInAppLyrics.WinUI3.ViewModels { diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs index 2eedff8..9ea8953 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs @@ -2,38 +2,30 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading.Tasks; using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Messages; using BetterLyrics.WinUI3.Models; -using BetterLyrics.WinUI3.Services.Database; -using BetterLyrics.WinUI3.Services.Playback; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; -using CommunityToolkit.Mvvm.Messaging.Messages; using Microsoft.UI.Xaml; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Microsoft.UI.Xaml.Controls; using Windows.ApplicationModel.Core; using Windows.Globalization; +using Windows.Media; using Windows.Media.Playback; using Windows.System; -using Windows.UI; using WinRT.Interop; -using WinUIEx.Messaging; namespace BetterLyrics.WinUI3.ViewModels { public partial class SettingsViewModel : ObservableRecipient { - [ObservableProperty] - [NotifyPropertyChangedRecipients] - public partial bool IsRebuildingLyricsIndexDatabase { get; set; } = false; - [ObservableProperty] [NotifyPropertyChangedRecipients] public partial ElementTheme ThemeType { get; set; } @@ -49,9 +41,12 @@ namespace BetterLyrics.WinUI3.ViewModels [ObservableProperty] public partial AutoStartWindowType AutoStartWindowType { get; set; } + [ObservableProperty] + public partial ObservableCollection LocalLyricsFolders { get; set; } + [ObservableProperty] [NotifyPropertyChangedRecipients] - public partial ObservableCollection MusicLibraries { get; set; } + public partial ObservableCollection LyricsSearchProvidersInfo { get; set; } [ObservableProperty] [NotifyPropertyChangedRecipients] @@ -73,26 +68,51 @@ namespace BetterLyrics.WinUI3.ViewModels [NotifyPropertyChangedRecipients] public partial int CoverOverlayBlurAmount { get; set; } - partial void OnMusicLibrariesChanged( - ObservableCollection oldValue, - ObservableCollection newValue - ) - { - if (oldValue != null) - { - oldValue.CollectionChanged -= (_, _) => - _settingsService.MusicLibraries = [.. MusicLibraries]; - } - if (newValue != null) - { - newValue.CollectionChanged += (_, _) => - _settingsService.MusicLibraries = [.. MusicLibraries]; - } - } - [ObservableProperty] public partial Enums.Language Language { get; set; } + public string Version { get; set; } = AppInfo.AppVersion; + + [ObservableProperty] + public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib"; + + [ObservableProperty] + public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0); + + private readonly MediaPlayer _mediaPlayer = new(); + private readonly ISettingsService _settingsService; + private readonly ILibWatcherService _libWatcherService; + private readonly IPlaybackService _playbackService; + + public SettingsViewModel( + ISettingsService settingsService, + ILibWatcherService libWatcherService, + IPlaybackService playbackService + ) + { + _settingsService = settingsService; + _libWatcherService = libWatcherService; + _playbackService = playbackService; + + RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0); + + LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders]; + LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo]; + + Language = _settingsService.Language; + CoverImageRadius = _settingsService.CoverImageRadius; + ThemeType = _settingsService.ThemeType; + BackdropType = _settingsService.BackdropType; + TitleBarType = _settingsService.TitleBarType; + + AutoStartWindowType = _settingsService.AutoStartWindowType; + + IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled; + IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled; + CoverOverlayOpacity = _settingsService.CoverOverlayOpacity; + CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount; + } + partial void OnLanguageChanged(Enums.Language value) { switch (value) @@ -109,52 +129,18 @@ namespace BetterLyrics.WinUI3.ViewModels case Enums.Language.TraditionalChinese: ApplicationLanguages.PrimaryLanguageOverride = "zh-TW"; break; + case Enums.Language.Japanese: + ApplicationLanguages.PrimaryLanguageOverride = "ja-JP"; + break; + case Enums.Language.Korean: + ApplicationLanguages.PrimaryLanguageOverride = "ko-KR"; + break; default: break; } _settingsService.Language = Language; } - private readonly MediaPlayer _mediaPlayer = new(); - - private readonly IDatabaseService _databaseService; - private readonly ISettingsService _settingsService; - - public string Version { get; set; } = AppInfo.AppVersion; - - [ObservableProperty] - public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib"; - - [ObservableProperty] - public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0); - - public SettingsViewModel(IDatabaseService databaseService, ISettingsService settingsService) - { - _databaseService = databaseService; - _settingsService = settingsService; - - RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0); - - MusicLibraries = [.. _settingsService.MusicLibraries]; - Language = _settingsService.Language; - CoverImageRadius = _settingsService.CoverImageRadius; - ThemeType = _settingsService.ThemeType; - BackdropType = _settingsService.BackdropType; - TitleBarType = _settingsService.TitleBarType; - - AutoStartWindowType = _settingsService.AutoStartWindowType; - - IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled; - IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled; - CoverOverlayOpacity = _settingsService.CoverOverlayOpacity; - CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount; - } - - partial void OnMusicLibrariesChanged(ObservableCollection value) - { - _settingsService.MusicLibraries = [.. value]; - } - partial void OnThemeTypeChanged(ElementTheme value) { _settingsService.ThemeType = value; @@ -201,18 +187,22 @@ namespace BetterLyrics.WinUI3.ViewModels _settingsService.CoverOverlayBlurAmount = value; } - [RelayCommand] - private async Task RebuildLyricsIndexDatabaseAsync() + public void RemoveFolderAsync(LocalLyricsFolder folder) { - IsRebuildingLyricsIndexDatabase = true; - await _databaseService.RebuildDatabaseAsync(MusicLibraries); - IsRebuildingLyricsIndexDatabase = false; + LocalLyricsFolders.Remove(folder); + _settingsService.LocalLyricsFolders = [.. LocalLyricsFolders]; + _libWatcherService.UpdateWatchers([.. LocalLyricsFolders]); + Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders)); } - public async Task RemoveFolderAsync(string path) + public void OnLyricsSearchProvidersReordered() { - MusicLibraries.Remove(path); - await RebuildLyricsIndexDatabaseAsync(); + _settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo]; + Broadcast( + LyricsSearchProvidersInfo, + LyricsSearchProvidersInfo, + nameof(LyricsSearchProvidersInfo) + ); } [RelayCommand] @@ -229,27 +219,26 @@ namespace BetterLyrics.WinUI3.ViewModels if (folder != null) { - if (MusicLibraries.Any((item) => folder.Path.StartsWith(item))) - { - WeakReferenceMessenger.Default.Send( - new ShowNotificatonMessage( - new Notification( - App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo") - ) - ) - ); - } - else - { - await AddFolderAsync(folder.Path); - } + AddFolderAsync(folder.Path); } } - private async Task AddFolderAsync(string path) + private void AddFolderAsync(string path) { - bool existed = MusicLibraries.Any((x) => x == path); - if (existed) + var normalizedPath = + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + + Path.DirectorySeparatorChar; + + if ( + LocalLyricsFolders.Any(x => + Path.GetFullPath(x.Path) + .TrimEnd(Path.DirectorySeparatorChar) + .Equals( + normalizedPath.TrimEnd(Path.DirectorySeparatorChar), + StringComparison.OrdinalIgnoreCase + ) + ) + ) { WeakReferenceMessenger.Default.Send( new ShowNotificatonMessage( @@ -259,17 +248,55 @@ namespace BetterLyrics.WinUI3.ViewModels ) ); } + else if ( + LocalLyricsFolders.Any(item => + normalizedPath.StartsWith( + Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + + Path.DirectorySeparatorChar, + StringComparison.OrdinalIgnoreCase + ) + ) + ) + { + // 添加的文件夹是现有文件夹的子文件夹 + WeakReferenceMessenger.Default.Send( + new ShowNotificatonMessage( + new Notification( + App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo") + ) + ) + ); + } + else if ( + LocalLyricsFolders.Any(item => + Path.GetFullPath(item.Path) + .TrimEnd(Path.DirectorySeparatorChar) + .StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase) + ) + ) + { + // 添加的文件夹是现有文件夹的父文件夹 + WeakReferenceMessenger.Default.Send( + new ShowNotificatonMessage( + new Notification( + App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo") + ) + ) + ); + } else { - MusicLibraries.Add(path); - await RebuildLyricsIndexDatabaseAsync(); + LocalLyricsFolders.Add(new LocalLyricsFolder(path, true)); + _settingsService.LocalLyricsFolders = [.. LocalLyricsFolders]; + _libWatcherService.UpdateWatchers([.. LocalLyricsFolders]); + Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders)); } } [RelayCommand] private async Task LaunchProjectGitHubPageAsync() { - await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl)); + await Launcher.LaunchUriAsync(new Uri(AppInfo.GithubUrl)); } private void OpenFolderInFileExplorer(string path) @@ -284,9 +311,9 @@ namespace BetterLyrics.WinUI3.ViewModels ); } - public void OpenMusicFolder(string path) + public void OpenMusicFolder(LocalLyricsFolder folder) { - OpenFolderInFileExplorer(path); + OpenFolderInFileExplorer(folder.Path); } [RelayCommand] @@ -311,9 +338,9 @@ namespace BetterLyrics.WinUI3.ViewModels } [RelayCommand] - private async Task PlayTestingMusicTask() + private void PlayTestingMusicTask() { - await AddFolderAsync(AppInfo.AssetsFolder); + AddFolderAsync(AppInfo.AssetsFolder); _mediaPlayer.SetUriSource(new Uri(AppInfo.TestMusicPath)); _mediaPlayer.Play(); } @@ -323,5 +350,21 @@ namespace BetterLyrics.WinUI3.ViewModels { OpenFolderInFileExplorer(AppInfo.LogDirectory); } + + public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo) + { + _settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo]; + Broadcast( + LyricsSearchProvidersInfo, + LyricsSearchProvidersInfo, + nameof(LyricsSearchProvidersInfo) + ); + } + + public void ToggleLocalLyricsFolder(LocalLyricsFolder folder) + { + _settingsService.LocalLyricsFolders = [.. LocalLyricsFolders]; + Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders)); + } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml index 97ae3ad..bd768fe 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml @@ -12,7 +12,10 @@ xmlns:ui="using:CommunityToolkit.WinUI" mc:Ignorable="d"> - + + PointerMoved="TopCommandGrid_PointerMoved"> diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs index e7f68bf..42746cc 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs @@ -3,7 +3,7 @@ using BetterInAppLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Messages; -using BetterLyrics.WinUI3.Services.Settings; +using BetterLyrics.WinUI3.Services; using BetterLyrics.WinUI3.ViewModels; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Messaging; @@ -162,6 +162,7 @@ namespace BetterLyrics.WinUI3.Views RestoreButton.Visibility = Visibility.Collapsed; } } + TopCommandGrid.Opacity = 0; break; default: break; @@ -220,21 +221,32 @@ namespace BetterLyrics.WinUI3.Views } } - private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e) - { - if (TopCommandGrid.Opacity == 0) - TopCommandGrid.Opacity = .5; - } - - private void TopCommandGrid_PointerExited(object sender, PointerRoutedEventArgs e) - { - if (TopCommandGrid.Opacity == .5) - TopCommandGrid.Opacity = 0; - } - private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e) { WindowHelper.OpenSettingsWindow(); } + + private void TopCommandGrid_PointerMoved(object sender, PointerRoutedEventArgs e) { } + + private void RootGrid_PointerMoved(object sender, PointerRoutedEventArgs e) + { + var point = e.GetCurrentPoint(RootGrid); + double y = point.Position.Y; + + if (y >= 0 && y <= TopCommandGrid.ActualHeight + 5) + { + if (TopCommandGrid.Opacity == 0) + { + TopCommandGrid.Opacity = .5; + } + } + else + { + if (TopCommandGrid.Opacity == .5) + { + TopCommandGrid.Opacity = 0; + } + } + } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml index b7f1c05..383bb45 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml @@ -26,7 +26,7 @@ - + @@ -45,24 +45,34 @@ - - + + + + + + + + - + @@ -311,42 +321,6 @@ - - - - - - - - - - - - - - - - @@ -449,34 +423,32 @@ - - - + + - + - - + + - - - - + - + - + + - + - + - + + diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs index bbafcec..15b11f2 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs @@ -64,11 +64,6 @@ namespace BetterLyrics.WinUI3.Views ViewModel.LimitedLineWidth = e.NewSize.Width; } - private void OpenMatchedFileButton_Click(object sender, RoutedEventArgs e) - { - ViewModel.OpenMatchedFileFolderInFileExplorer((string)(sender as HyperlinkButton)!.Tag); - } - private void CoverImageGrid_SizeChanged(object sender, SizeChangedEventArgs e) { ViewModel.CoverImageGridActualHeight = e.NewSize.Height; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml index 96c60f5..e02477f 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml @@ -8,6 +8,7 @@ xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:local="using:BetterLyrics.WinUI3.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:models="using:BetterLyrics.WinUI3.Models" xmlns:ui="using:CommunityToolkit.WinUI" xmlns:vm="using:BetterLyrics.WinUI3.ViewModels" mc:Ignorable="d"> @@ -73,21 +74,26 @@ + ItemsSource="{x:Bind ViewModel.LocalLyricsFolders, Mode=OneWay}"> - - + + + + + @@ -104,13 +110,13 @@ @@ -131,21 +137,38 @@ - - - - -