Compare commits

..

9 Commits

Author SHA1 Message Date
Zhe Fang
80ef86478b Merge pull request #2 from jayfunc/dev
Dev
2025-06-05 22:02:40 -04:00
Zhe Fang
9736a4c9cc add: user can set lyrics color now (choose one from album art accent colors) 2025-06-05 21:59:30 -04:00
Zhe Fang
d01be4b883 add support for editing album art corner radius 2025-06-05 17:44:04 -04:00
Zhe Fang
b470b91d0e format c# code 2025-06-05 15:36:31 -04:00
Zhe Fang
61f4e5706b update md 2025-06-05 10:40:38 -04:00
Zhe Fang
6f2d3a3505 update download methods 2025-06-05 10:34:33 -04:00
Zhe Fang
58b7cd2520 fix bug: when toggle to lyrics only mode and switch to song with no lyrics the album art is hidden 2025-06-05 10:04:13 -04:00
Zhe Fang
b2319e7983 fix lrc file supporting bug 2025-06-04 23:04:12 -04:00
Zhe Fang
a3b250dd46 update docs, fix a lot bugs 2025-06-04 20:12:45 -04:00
107 changed files with 2159 additions and 1105 deletions

View File

@@ -40,15 +40,16 @@
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
@@ -80,6 +81,7 @@
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -5,7 +5,8 @@
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18"
IgnorableNamespaces="uap rescap uap18">
<Identity
Name="37412.BetterLyrics"
@@ -26,7 +27,9 @@
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
<Resource Language="zh-TW"/>
</Resources>
<Applications>

View File

@@ -16,12 +16,8 @@
<!-- Theme -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Color x:Key="SemiTransparentSystemBaseHighColor">#80000000</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="SemiTransparentSystemBaseHighColor">#80FFFFFF</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Light" />
<ResourceDictionary x:Key="Dark" />
</ResourceDictionary.ThemeDictionaries>
<!-- Brush -->
@@ -48,6 +44,7 @@
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<x:Double x:Key="SettingsCardSpacing">4</x:Double>

View File

@@ -1,27 +1,41 @@
using BetterLyrics.WinUI3.Services.Database;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using System.Text;
using Microsoft.Windows.AppLifecycle;
using Newtonsoft.Json;
using Serilog;
using Serilog.Core;
using Windows.ApplicationModel.Core;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3 {
namespace BetterLyrics.WinUI3
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application {
public static App Current => (App)Application.Current;
public partial class App : Application
{
private readonly ILogger<App> _logger;
public static new App Current => (App)Application.Current;
public MainWindow? MainWindow { get; private set; }
public MainWindow? SettingsWindow { get; set; }
public static ResourceLoader ResourceLoader = new();
public static ResourceLoader? ResourceLoader { get; private set; }
public static DispatcherQueue DispatcherQueue => DispatcherQueue.GetForCurrentThread();
@@ -29,28 +43,61 @@ namespace BetterLyrics.WinUI3 {
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App() {
public App()
{
this.InitializeComponent();
App.ResourceLoader = new ResourceLoader();
Helper.AppInfo.EnsureDirectories();
ConfigureServices();
_logger = Ioc.Default.GetService<ILogger<App>>()!;
UnhandledException += App_UnhandledException;
}
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File(Helper.AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
.CreateLogger();
// Register services
Ioc.Default.ConfigureServices(
new ServiceCollection()
.AddSingleton(DispatcherQueue.GetForCurrentThread())
.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog();
})
// Services
.AddSingleton<SettingsService>()
.AddSingleton<DatabaseService>()
// ViewModels
.AddSingleton<MainViewModel>()
.AddSingleton<SettingsViewModel>()
.BuildServiceProvider()
);
}
private void App_UnhandledException(
object sender,
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
)
{
_logger.LogError(e.Exception, "App_UnhandledException");
e.Handled = true;
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) {
// Register services
Ioc.Default.ConfigureServices(
new ServiceCollection()
.AddSingleton(DispatcherQueue.GetForCurrentThread())
// Services
.AddSingleton<SettingsService>()
.AddSingleton<DatabaseService>()
// ViewModels
.AddSingleton<MainViewModel>()
.AddSingleton<SettingsViewModel>()
.BuildServiceProvider());
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// Activate the window
@@ -58,6 +105,5 @@ namespace BetterLyrics.WinUI3 {
MainWindow!.Navigate(typeof(MainPage));
MainWindow.Activate();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -2,42 +2,56 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="Logo.ico" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
<PackageReference
Include="CommunityToolkit.Labs.WinUI.OpacityMaskView"
Version="0.1.250513-build.2126"
/>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference
Include="CommunityToolkit.WinUI.Controls.SettingsControls"
Version="8.2.250402"
/>
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="DevWinUI" Version="8.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.5" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="z440.atl.core" Version="6.24.0" />
</ItemGroup>
<!-- Publish Properties -->
<ItemGroup>
<Content Update="Assets\AI - 甜度爆表.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
@@ -45,4 +59,9 @@
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
</PropertyGroup>
</Project>
<PropertyGroup>
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Logo.ico</ApplicationIcon>
</PropertyGroup>
</Project>

View File

@@ -1,22 +1,27 @@
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
namespace BetterLyrics.WinUI3.Converter {
public class ColorToBrushConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, string language) {
if (value is Color color) {
namespace BetterLyrics.WinUI3.Converter
{
public class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is Color color)
{
return new SolidColorBrush(color);
}
return new SolidColorBrush();
}
public object ConvertBack(object value, Type targetType, object parameter, string language) {
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -1,21 +1,26 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter {
internal class ThemeTypeToElementThemeConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, string language) {
if (value is int themeType) {
namespace BetterLyrics.WinUI3.Converter
{
internal class ThemeTypeToElementThemeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int themeType)
{
return (ElementTheme)themeType;
}
return ElementTheme.Default;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) {
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return 0;
}
}

View File

@@ -1,44 +1,13 @@
using Microsoft.UI.Xaml;
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Animation;
using System;
namespace BetterLyrics.WinUI3.Helper {
/// <summary>
/// Edited based on: https://stackoverflow.com/a/25236507/11048731
/// </summary>
public class AnimationHelper : DependencyObject {
public static int GetAnimationDuration(DependencyObject obj) {
return (int)obj.GetValue(AnimationDurationProperty);
}
public static void SetAnimationDuration(DependencyObject obj, int value) {
obj.SetValue(AnimationDurationProperty, value);
}
// Using a DependencyProperty as the backing store for AnimationDuration.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.RegisterAttached("AnimationDuration", typeof(int),
typeof(AnimationHelper), new PropertyMetadata(0,
OnAnimationDurationChanged));
private static void OnAnimationDurationChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
FrameworkElement element = d as FrameworkElement;
var ms = (int)e.NewValue;
if (ms < 0) return;
var key = "LyricsLineCharGradientInTextBlock";
foreach (var timeline in (element.Resources[key] as Storyboard).Children) {
foreach (var keyFrame in (timeline as DoubleAnimationUsingKeyFrames).KeyFrames) {
(keyFrame as LinearDoubleKeyFrame).KeyTime =
KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(ms));
}
}
}
namespace BetterLyrics.WinUI3.Helper
{
public static class AnimationHelper
{
public const int StackedNotificationsShowingDuration = 3900;
public const int StoryboardDefaultDuration = 200;
public const int DebounceDefaultDuration = 200;
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
using System;
using System.IO;
using Windows.ApplicationModel;
using Windows.Storage;
public static class AppInfo
{
// App Metadata
public const string AppName = "BetterLyrics";
public const string AppDisplayName = "Better Lyrics";
public const string AppAuthor = "Zhe Fang";
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public static string AppVersion
{
get
{
var version = Package.Current.Id.Version;
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
}
// Environment Info
public static bool IsDebug =>
#if DEBUG
true;
#else
false;
#endif
// Base Folders
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
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");
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
public static void EnsureDirectories()
{
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LocalFolder);
}
}
}

View File

@@ -4,9 +4,12 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper {
public static class CollectionHelper {
public static T? SafeGet<T>(this IList<T> list, int index) {
namespace BetterLyrics.WinUI3.Helper
{
public static class CollectionHelper
{
public static T? SafeGet<T>(this IList<T> list, int index)
{
if (list == null || index < 0 || index >= list.Count)
return default;
return list[index];

View File

@@ -1,12 +1,14 @@
namespace BetterLyrics.WinUI3.Helper {
public class ColorHelper {
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t) {
namespace BetterLyrics.WinUI3.Helper
{
public class ColorHelper
{
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t)
{
byte A = (byte)(a.A + (b.A - a.A) * t);
byte R = (byte)(a.R + (b.R - a.R) * t);
byte G = (byte)(a.G + (b.G - a.G) * t);
byte B = (byte)(a.B + (b.B - a.B) * t);
return Windows.UI.Color.FromArgb(A, R, G, B);
}
}
}

View File

@@ -12,7 +12,7 @@ namespace BetterLyrics.WinUI3.Helper
internal class CMap
{
private readonly List<VBox> vboxes = new List<VBox>();
private List<QuantizedColor> palette;
private List<QuantizedColor>? palette;
public void Push(VBox box)
{
@@ -24,10 +24,12 @@ namespace BetterLyrics.WinUI3.Helper
{
if (palette == null)
{
palette = (from vBox in vboxes
let rgb = vBox.Avg(false)
let color = FromRgb(rgb[0], rgb[1], rgb[2])
select new QuantizedColor(color, vBox.Count(false))).ToList();
palette = (
from vBox in vboxes
let rgb = vBox.Avg(false)
let color = FromRgb(rgb[0], rgb[1], rgb[2])
select new QuantizedColor(color, vBox.Count(false))
).ToList();
}
return palette;
@@ -38,7 +40,7 @@ namespace BetterLyrics.WinUI3.Helper
return vboxes.Count;
}
public int[] Map(int[] color)
public int[]? Map(int[] color)
{
foreach (var vbox in vboxes.Where(vbox => vbox.Contains(color)))
{
@@ -47,17 +49,19 @@ namespace BetterLyrics.WinUI3.Helper
return Nearest(color);
}
public int[] Nearest(int[] color)
public int[]? Nearest(int[] color)
{
var d1 = double.MaxValue;
int[] pColor = null;
int[]? pColor = null;
foreach (var t in vboxes)
{
var vbColor = t.Avg(false);
var d2 = Math.Sqrt(Math.Pow(color[0] - vbColor[0], 2)
+ Math.Pow(color[1] - vbColor[1], 2)
+ Math.Pow(color[2] - vbColor[2], 2));
var d2 = Math.Sqrt(
Math.Pow(color[0] - vbColor[0], 2)
+ Math.Pow(color[1] - vbColor[1], 2)
+ Math.Pow(color[2] - vbColor[2], 2)
);
if (d2 < d1)
{
d1 = d2;
@@ -67,9 +71,16 @@ namespace BetterLyrics.WinUI3.Helper
return pColor;
}
public VBox FindColor(double targetLuma, double minLuma, double maxLuma, double targetSaturation, double minSaturation, double maxSaturation)
public VBox FindColor(
double targetLuma,
double minLuma,
double maxLuma,
double targetSaturation,
double minSaturation,
double maxSaturation
)
{
VBox max = null;
VBox? max = null;
double maxValue = 0;
var highestPopulation = vboxes.Select(p => p.Count(false)).Max();
@@ -80,11 +91,21 @@ namespace BetterLyrics.WinUI3.Helper
var sat = hsl.S;
var luma = hsl.L;
if (sat >= minSaturation && sat <= maxSaturation &&
luma >= minLuma && luma <= maxLuma)
if (
sat >= minSaturation
&& sat <= maxSaturation
&& luma >= minLuma
&& luma <= maxLuma
)
{
var thisValue = Mmcq.CreateComparisonValue(sat, targetSaturation, luma, targetLuma,
swatch.Count(false), highestPopulation);
var thisValue = Mmcq.CreateComparisonValue(
sat,
targetSaturation,
luma,
targetLuma,
swatch.Count(false),
highestPopulation
);
if (max == null || thisValue > maxValue)
{
@@ -97,14 +118,14 @@ namespace BetterLyrics.WinUI3.Helper
return max;
}
public Color FromRgb(int red, int green, int blue)
public RGB FromRgb(int red, int green, int blue)
{
var color = new Color
var color = new RGB
{
A = 255,
R = (byte)red,
G = (byte)green,
B = (byte)blue
B = (byte)blue,
};
return color;
@@ -114,7 +135,7 @@ namespace BetterLyrics.WinUI3.Helper
/// <summary>
/// Defines a color in RGB space.
/// </summary>
public struct Color
public struct RGB
{
/// <summary>
/// Get or Set the Alpha component value for sRGB.
@@ -269,9 +290,12 @@ namespace BetterLyrics.WinUI3.Helper
private static VBox VboxFromPixels(IList<byte[]> pixels, int[] histo)
{
int rmin = 1000000, rmax = 0;
int gmin = 1000000, gmax = 0;
int bmin = 1000000, bmax = 0;
int rmin = 1000000,
rmax = 0;
int gmin = 1000000,
gmax = 0;
int bmin = 1000000,
bmax = 0;
// find min/max
var numPixels = pixels.Count;
@@ -313,7 +337,13 @@ namespace BetterLyrics.WinUI3.Helper
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
}
private static VBox[] DoCut(char color, VBox vbox, IList<int> partialsum, IList<int> lookaheadsum, int total)
private static VBox[] DoCut(
char color,
VBox vbox,
IList<int> partialsum,
IList<int> lookaheadsum,
int total
)
{
int vboxDim1;
int vboxDim2;
@@ -344,9 +374,10 @@ namespace BetterLyrics.WinUI3.Helper
var left = i - vboxDim1;
var right = vboxDim2 - i;
var d2 = left <= right
? Math.Min(vboxDim2 - 1, Math.Abs(i + right / 2))
: Math.Max(vboxDim1, Math.Abs(Convert.ToInt32(i - 1 - left / 2.0)));
var d2 =
left <= right
? Math.Min(vboxDim2 - 1, Math.Abs(i + right / 2))
: Math.Max(vboxDim1, Math.Abs(Convert.ToInt32(i - 1 - left / 2.0)));
// avoid 0-count boxes
while (d2 < 0 || partialsum[d2] <= 0)
@@ -383,7 +414,7 @@ namespace BetterLyrics.WinUI3.Helper
throw new Exception("VBox can't be cut");
}
private static VBox[] MedianCutApply(IList<int> histo, VBox vbox)
private static VBox?[]? MedianCutApply(IList<int> histo, VBox vbox)
{
if (vbox.Count(false) == 0)
{
@@ -391,7 +422,7 @@ namespace BetterLyrics.WinUI3.Helper
}
if (vbox.Count(false) == 1)
{
return new[] { vbox.Clone(), null };
return [vbox.Clone(), null];
}
// only one pixel, no split
@@ -417,7 +448,11 @@ namespace BetterLyrics.WinUI3.Helper
lookaheadsum[l] = -1;
}
int i, j, k, sum, index;
int i,
j,
k,
sum,
index;
if (maxw == rw)
{
@@ -480,8 +515,9 @@ namespace BetterLyrics.WinUI3.Helper
}
// determine the cut planes
return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total) : maxw == gw
? DoCut('g', vbox, partialsum, lookaheadsum, total) : DoCut('b', vbox, partialsum, lookaheadsum, total);
return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total)
: maxw == gw ? DoCut('g', vbox, partialsum, lookaheadsum, total)
: DoCut('b', vbox, partialsum, lookaheadsum, total);
}
/// <summary>
@@ -492,7 +528,12 @@ namespace BetterLyrics.WinUI3.Helper
/// <param name="target">The target.</param>
/// <param name="histo">The histo.</param>
/// <exception cref="System.Exception">vbox1 not defined; shouldn't happen!</exception>
private static void Iter(List<VBox> lh, IComparer<VBox> comparator, int target, IList<int> histo)
private static void Iter(
List<VBox> lh,
IComparer<VBox> comparator,
int target,
IList<int> histo
)
{
var ncolors = 1;
var niters = 0;
@@ -511,13 +552,12 @@ namespace BetterLyrics.WinUI3.Helper
// do the cut
var vboxes = MedianCutApply(histo, vbox);
var vbox1 = vboxes[0];
var vbox2 = vboxes[1];
var vbox1 = vboxes?[0];
var vbox2 = vboxes?[1];
if (vbox1 == null)
{
throw new Exception(
"vbox1 not defined; shouldn't happen!");
throw new Exception("vbox1 not defined; shouldn't happen!");
}
lh.Add(vbox1);
@@ -579,11 +619,23 @@ namespace BetterLyrics.WinUI3.Helper
return cmap;
}
public static double CreateComparisonValue(double saturation, double targetSaturation, double luma, double targetLuma, int population, int highestPopulation)
public static double CreateComparisonValue(
double saturation,
double targetSaturation,
double luma,
double targetLuma,
int population,
int highestPopulation
)
{
return WeightedMean(InvertDiff(saturation, targetSaturation), WeightSaturation,
InvertDiff(luma, targetLuma), WeightLuma,
population / (double)highestPopulation, WeightPopulation);
return WeightedMean(
InvertDiff(saturation, targetSaturation),
WeightSaturation,
InvertDiff(luma, targetLuma),
WeightLuma,
population / (double)highestPopulation,
WeightPopulation
);
}
private static double WeightedMean(params double[] values)
@@ -611,20 +663,22 @@ namespace BetterLyrics.WinUI3.Helper
public class QuantizedColor
{
public QuantizedColor(Color color, int population)
public QuantizedColor(RGB color, int population)
{
Color = color;
Population = population;
IsDark = CalculateYiqLuma(color) < 128;
}
public Color Color { get; private set; }
public RGB Color { get; private set; }
public int Population { get; private set; }
public bool IsDark { get; private set; }
public int CalculateYiqLuma(Color color)
public int CalculateYiqLuma(RGB color)
{
return Convert.ToInt32(Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f));
return Convert.ToInt32(
Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f)
);
}
}
@@ -732,8 +786,9 @@ namespace BetterLyrics.WinUI3.Helper
{
avg = new[]
{
Math.Abs(rsum / ntot), Math.Abs(gsum / ntot),
Math.Abs(bsum / ntot)
Math.Abs(rsum / ntot),
Math.Abs(gsum / ntot),
Math.Abs(bsum / ntot),
};
}
else
@@ -742,7 +797,7 @@ namespace BetterLyrics.WinUI3.Helper
{
Math.Abs(Mmcq.Mult * (R1 + R2 + 1) / 2),
Math.Abs(Mmcq.Mult * (G1 + G2 + 1) / 2),
Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2)
Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2),
};
}
}
@@ -809,14 +864,16 @@ namespace BetterLyrics.WinUI3.Helper
private byte[][] ConvertPixels(byte[] pixels, int pixelCount, int quality, bool ignoreWhite)
{
var expectedDataLength = pixelCount * ColorDepth;
if (expectedDataLength != pixels.Length)
{
throw new ArgumentException("(expectedDataLength = "
+ expectedDataLength + ") != (pixels.length = "
+ pixels.Length + ")");
throw new ArgumentException(
"(expectedDataLength = "
+ expectedDataLength
+ ") != (pixels.length = "
+ pixels.Length
+ ")"
);
}
// Store the RGB values in an array format suitable for quantize
@@ -864,17 +921,24 @@ namespace BetterLyrics.WinUI3.Helper
/// </param>
/// <param name="ignoreWhite">if set to <c>true</c> [ignore white].</param>
/// <returns></returns>
public async Task<QuantizedColor> GetColor(BitmapDecoder sourceImage, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite)
public async Task<QuantizedColor> GetColor(
BitmapDecoder sourceImage,
int quality = DefaultQuality,
bool ignoreWhite = DefaultIgnoreWhite
)
{
var palette = await GetPalette(sourceImage, 3, quality, ignoreWhite);
var dominantColor = new QuantizedColor(new Color
{
A = Convert.ToByte(palette.Average(a => a.Color.A)),
R = Convert.ToByte(palette.Average(a => a.Color.R)),
G = Convert.ToByte(palette.Average(a => a.Color.G)),
B = Convert.ToByte(palette.Average(a => a.Color.B))
}, Convert.ToInt32(palette.Average(a => a.Population)));
var dominantColor = new QuantizedColor(
new RGB
{
A = Convert.ToByte(palette.Average(a => a.Color.A)),
R = Convert.ToByte(palette.Average(a => a.Color.R)),
G = Convert.ToByte(palette.Average(a => a.Color.G)),
B = Convert.ToByte(palette.Average(a => a.Color.B)),
},
Convert.ToInt32(palette.Average(a => a.Population))
);
return dominantColor;
}
@@ -893,7 +957,12 @@ namespace BetterLyrics.WinUI3.Helper
/// <param name="ignoreWhite">if set to <c>true</c> [ignore white].</param>
/// <returns></returns>
/// <code>true</code>
public async Task<List<QuantizedColor>> GetPalette(BitmapDecoder sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite)
public async Task<List<QuantizedColor>> GetPalette(
BitmapDecoder sourceImage,
int colorCount = DefaultColorCount,
int quality = DefaultQuality,
bool ignoreWhite = DefaultIgnoreWhite
)
{
var pixelArray = await GetPixelsFast(sourceImage, quality, ignoreWhite);
var cmap = GetColorMap(pixelArray, colorCount);
@@ -912,7 +981,11 @@ namespace BetterLyrics.WinUI3.Helper
return pixels;
}
private async Task<byte[][]> GetPixelsFast(BitmapDecoder sourceImage, int quality, bool ignoreWhite)
private async Task<byte[][]> GetPixelsFast(
BitmapDecoder sourceImage,
int quality,
bool ignoreWhite
)
{
if (quality < 1)
{

View File

@@ -1,21 +0,0 @@
using BetterLyrics.WinUI3.Models;
using SQLite;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper {
public class DatabaseHelper {
private static SQLiteConnection _database;
public static SQLiteConnection InitializeDatabase() {
string dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MusicMetadataIndex.db");
_database = new SQLiteConnection(dbPath);
_database.CreateTable<MetadataIndex>(); // Create table if it doesn't exist
return _database;
}
}
}

View File

@@ -4,9 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper {
public class EasingHelper {
namespace BetterLyrics.WinUI3.Helper
{
public class EasingHelper
{
/// <summary>
/// No easing
/// </summary>
@@ -25,23 +26,24 @@ namespace BetterLyrics.WinUI3.Helper {
/// <summary>
/// Acceleration until halfway then deceleration
/// </summary>
public static float EaseInOutQuad(float t) {
return t < 0.5f
? 2 * t * t
: -1 + (4 - 2 * t) * t;
public static float EaseInOutQuad(float t)
{
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/// <summary>
/// Smoother transition than linear
/// </summary>
public static float SmoothStep(float t) {
public static float SmoothStep(float t)
{
return t * t * (3 - 2 * t);
}
/// <summary>
/// Even smoother transition with continuous first and second derivatives
/// </summary>
public static float SmootherStep(float t) {
public static float SmootherStep(float t)
{
return t * t * t * (t * (6 * t - 15) + 10);
}
}

View File

@@ -1,8 +1,9 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
@@ -11,7 +12,9 @@ namespace BetterLyrics.WinUI3.Helper
{
public class ImageHelper
{
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
byte[] imageBytes
)
{
if (imageBytes == null || imageBytes.Length == 0)
return null;
@@ -22,5 +25,13 @@ namespace BetterLyrics.WinUI3.Helper
return stream;
}
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
using var memoryStream = new MemoryStream();
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
}

View File

@@ -4,13 +4,18 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper {
public class MathHelper {
public static List<int> GetAllFactors(int n) {
namespace BetterLyrics.WinUI3.Helper
{
public class MathHelper
{
public static List<int> GetAllFactors(int n)
{
var result = new SortedSet<int>();
for (int i = 1; i <= Math.Sqrt(n); i++) {
if (n % i == 0) {
for (int i = 1; i <= Math.Sqrt(n); i++)
{
if (n % i == 0)
{
result.Add(i);
result.Add(n / i);
}
@@ -18,6 +23,5 @@ namespace BetterLyrics.WinUI3.Helper {
return [.. result];
}
}
}
}

View File

@@ -6,8 +6,10 @@ namespace BetterLyrics.WinUI3.Helper
{
public class SystemBackdropHelper
{
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType) {
return backdropType switch {
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType)
{
return backdropType switch
{
BackdropType.None => null,
BackdropType.Mica => new MicaSystemBackdrop(MicaKind.Base),
BackdropType.MicaAlt => new MicaSystemBackdrop(MicaKind.BaseAlt),
@@ -18,6 +20,5 @@ namespace BetterLyrics.WinUI3.Helper
_ => null,
};
}
}
}

View File

@@ -1,32 +1,40 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
namespace BetterLyrics.WinUI3.Helper {
public class VisualHelper {
namespace BetterLyrics.WinUI3.Helper
{
public class VisualHelper
{
/// <summary>
/// Source: https://stackoverflow.com/a/61626933/11048731
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="depObj"></param>
/// <returns></returns>
public static List<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject {
List<T> list = new List<T>();
if (depObj != null) {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) {
public static List<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
List<T> list = [];
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T) {
list.Add((T)child);
if (child != null && child is T t)
{
list.Add(t);
}
List<T> childItems = FindVisualChildren<T>(child);
if (childItems != null && childItems.Count() > 0) {
foreach (var item in childItems) {
if (childItems != null && childItems.Count > 0)
{
foreach (var item in childItems)
{
list.Add(item);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -57,6 +57,21 @@
</interactivity:Interaction.Behaviors>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<ImageIcon
Height="24"
Margin="16,0"
Source="ms-appx:///Assets/Logo.png" />
<TextBlock
x:Name="AppTitleTextBlock"
Margin="0,-4,0,0"
VerticalAlignment="Center"
Text="{x:Bind Title}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<!-- Always On Top -->
<AppBarButton
x:Name="AOTButton"
@@ -89,15 +104,6 @@
</Grid>
</AppBarButton>
<TextBlock
x:Name="AppTitleTextBlock"
Margin="0,-4,0,0"
VerticalAlignment="Center"
Text="{x:Bind Title}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<!-- Window Mini -->
<AppBarButton
x:Name="MiniButton"

View File

@@ -1,58 +1,66 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Services.Settings;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Behaviors;
using DevWinUI;
using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
using WinRT;
using WinRT.Interop;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3 {
namespace BetterLyrics.WinUI3
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window {
public sealed partial class MainWindow : Window
{
private readonly OverlappedPresenter _presenter;
private SettingsService _settingsService;
private readonly SettingsService _settingsService;
public static StackedNotificationsBehavior? StackedNotificationsBehavior { get; private set; }
public static StackedNotificationsBehavior? StackedNotificationsBehavior
{
get;
private set;
}
public MainWindow() {
public MainWindow()
{
this.InitializeComponent();
_settingsService = Ioc.Default.GetService<SettingsService>();
_settingsService = Ioc.Default.GetService<SettingsService>()!;
RootGrid.RequestedTheme = (ElementTheme)_settingsService.Theme;
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop((BackdropType)_settingsService.BackdropType);
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
(BackdropType)_settingsService.BackdropType
);
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(this, (r, m) => {
RootGrid.RequestedTheme = m.Value;
});
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(
this,
(r, m) =>
{
RootGrid.RequestedTheme = m.Value;
}
);
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(this, (r, m) => {
SystemBackdrop = null;
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
});
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(
this,
(r, m) =>
{
SystemBackdrop = null;
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
}
);
// AppWindow.SetIcon("white_round.ico");
StackedNotificationsBehavior = NotificationQueue;
@@ -64,55 +72,67 @@ namespace BetterLyrics.WinUI3 {
SetTitleBar(TopCommandGrid);
}
public void Navigate(Type type) {
public void Navigate(Type type)
{
RootFrame.Navigate(type);
}
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) {
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
private void BackButton_Click(object sender, RoutedEventArgs e) {
if (RootFrame.CanGoBack) {
RootFrame.GoBack();
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e) {
if (RootFrame.CurrentSourcePageType == typeof(MainPage)) {
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
if (RootFrame.CurrentSourcePageType == typeof(MainPage))
{
(
(RootFrame.Content as MainPage)!.FindChild("LyricsCanvas")
as CanvasAnimatedControl
)!.Paused = true;
App.Current.Exit();
} else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage)) {
}
else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage))
{
App.Current.SettingsWindow!.AppWindow.Hide();
}
}
private void MaximiseButton_Click(object sender, RoutedEventArgs e) {
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
{
_presenter.Maximize();
//MaximiseButton.Visibility = Visibility.Collapsed;
//RestoreButton.Visibility = Visibility.Visible;
}
private void MinimiseButton_Click(object sender, RoutedEventArgs e) {
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
{
_presenter.Minimize();
}
private void RestoreButton_Click(object sender, RoutedEventArgs e) {
private void RestoreButton_Click(object sender, RoutedEventArgs e)
{
_presenter.Restore();
//MaximiseButton.Visibility = Visibility.Visible;
//RestoreButton.Visibility = Visibility.Collapsed;
}
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args) {
if (_presenter.State == OverlappedPresenterState.Maximized) {
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
{
if (_presenter.State == OverlappedPresenterState.Maximized)
{
MaximiseButton.Visibility = Visibility.Collapsed;
RestoreButton.Visibility = Visibility.Visible;
} else if (_presenter.State == OverlappedPresenterState.Restored) {
}
else if (_presenter.State == OverlappedPresenterState.Restored)
{
MaximiseButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed;
}
}
private void MiniButton_Click(object sender, RoutedEventArgs e) {
private void MiniButton_Click(object sender, RoutedEventArgs e)
{
AppWindow.Resize(new Windows.Graphics.SizeInt32(144, 48));
MiniButton.Visibility = Visibility.Collapsed;
UnminiButton.Visibility = Visibility.Visible;
@@ -122,7 +142,8 @@ namespace BetterLyrics.WinUI3 {
CloseButton.Visibility = Visibility.Collapsed;
}
private void UnminiButton_Click(object sender, RoutedEventArgs e) {
private void UnminiButton_Click(object sender, RoutedEventArgs e)
{
AppWindow.Resize(new Windows.Graphics.SizeInt32(800, 600));
MiniButton.Visibility = Visibility.Visible;
UnminiButton.Visibility = Visibility.Collapsed;
@@ -132,21 +153,26 @@ namespace BetterLyrics.WinUI3 {
CloseButton.Visibility = Visibility.Visible;
}
private void RootFrame_Navigated(object sender, NavigationEventArgs e) {
AppWindow.Title = Title = App.ResourceLoader.GetString($"{e.SourcePageType.Name}Title");
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
AppWindow.Title = Title = App.ResourceLoader!.GetString(
$"{e.SourcePageType.Name}Title"
);
}
private void AOTButton_Click(object sender, RoutedEventArgs e) {
private void AOTButton_Click(object sender, RoutedEventArgs e)
{
_presenter.IsAlwaysOnTop = !_presenter.IsAlwaysOnTop;
string prefix;
if (_presenter.IsAlwaysOnTop) {
if (_presenter.IsAlwaysOnTop)
{
prefix = "Show";
} else {
}
else
{
prefix = "Hide";
}
(PinnedFontIcon.Resources[$"{prefix}PinnedFontIconStoryboard"] as Storyboard)!.Begin();
}
}
}

View File

@@ -1,14 +1,13 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using DevWinUI;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging.Messages;
using DevWinUI;
namespace BetterLyrics.WinUI3.Messages {
public class SystemBackdropChangedMessage : ValueChangedMessage<BackdropType> {
public SystemBackdropChangedMessage(BackdropType value) : base(value) {
}
}
namespace BetterLyrics.WinUI3.Messages
{
public class SystemBackdropChangedMessage(BackdropType value)
: ValueChangedMessage<BackdropType>(value) { }
}

View File

@@ -1,14 +1,13 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
namespace BetterLyrics.WinUI3.Messages {
public class ThemeChangedMessage : ValueChangedMessage<ElementTheme> {
public ThemeChangedMessage(ElementTheme value) : base(value) {
}
}
namespace BetterLyrics.WinUI3.Messages
{
public class ThemeChangedMessage(ElementTheme value)
: ValueChangedMessage<ElementTheme>(value) { }
}

View File

@@ -4,8 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum Language {
namespace BetterLyrics.WinUI3.Models
{
public enum Language
{
FollowSystem,
English,
SimplifiedChinese,

View File

@@ -4,8 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum LyricsAlignmentType {
namespace BetterLyrics.WinUI3.Models
{
public enum LyricsAlignmentType
{
Left,
Center,
Right,

View File

@@ -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 enum LyricsFontColorType
{
Default,
Dominant,
}
}

View File

@@ -1,14 +1,15 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Graphics.Canvas.Text;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Graphics.Canvas.Text;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models {
public class LyricsLine {
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
public List<string> Texts { get; set; } = [];
public int LanguageIndex { get; set; } = 0;
@@ -37,6 +38,5 @@ namespace BetterLyrics.WinUI3.Models {
public float Opacity { get; set; }
public CanvasTextLayout TextLayout { get; set; }
}
}

View File

@@ -4,19 +4,23 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum LyricsPlayingState {
namespace BetterLyrics.WinUI3.Models
{
public enum LyricsPlayingState
{
/// <summary>
/// Not played yet, will be playing in the future
/// </summary>
NotPlayed,
/// <summary>
/// Playing
/// </summary>
Playing,
/// <summary>
/// Has already played
/// </summary>
Played
Played,
}
}

View File

@@ -1,15 +1,12 @@
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public class MetadataIndex {
namespace BetterLyrics.WinUI3.Models
{
public class MetadataIndex
{
[PrimaryKey]
public string Path { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
public string? Path { get; set; }
public string? Title { get; set; }
public string? Artist { get; set; }
}
}

View File

@@ -1,20 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public partial class MusicFolder : ObservableObject {
[ObservableProperty]
private string _path;
public bool IsValid => Directory.Exists(Path);
public MusicFolder(string path) {
Path = path;
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
namespace BetterLyrics.WinUI3
{
public class Program
{
[STAThread]
static int Main(string[] args)
{
WinRT.ComWrappersSupport.InitializeComWrappers();
bool isRedirect = DecideRedirection();
if (!isRedirect)
{
Application.Start(
(p) =>
{
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread()
);
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
}
);
}
return 0;
}
private static bool DecideRedirection()
{
bool isRedirect = false;
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind;
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(Helper.AppInfo.AppName);
if (keyInstance.IsCurrent)
{
keyInstance.Activated += OnActivated;
}
else
{
isRedirect = true;
RedirectActivationTo(args, keyInstance);
}
return isRedirect;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateEvent(
IntPtr lpEventAttributes,
bool bManualReset,
bool bInitialState,
string lpName
);
[DllImport("kernel32.dll")]
private static extern bool SetEvent(IntPtr hEvent);
[DllImport("ole32.dll")]
private static extern uint CoWaitForMultipleObjects(
uint dwFlags,
uint dwMilliseconds,
ulong nHandles,
IntPtr[] pHandles,
out uint dwIndex
);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
private static IntPtr redirectEventHandle = IntPtr.Zero;
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
public static void RedirectActivationTo(
AppActivationArguments args,
AppInstance keyInstance
)
{
redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
uint CWMO_DEFAULT = 0;
uint INFINITE = 0xFFFFFFFF;
_ = CoWaitForMultipleObjects(
CWMO_DEFAULT,
INFINITE,
1,
[redirectEventHandle],
out uint handleIndex
);
// Bring the window to the foreground
Process process = Process.GetProcessById((int)keyInstance.ProcessId);
SetForegroundWindow(process.MainWindowHandle);
}
private static void OnActivated(object sender, AppActivationArguments args)
{
ExtendedActivationKind kind = args.Kind;
}
}
}

View File

@@ -1,76 +1,127 @@
using ATL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using ATL;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using SQLite;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Ude;
using Windows.Media.Control;
namespace BetterLyrics.WinUI3.Services.Database {
public class DatabaseService {
namespace BetterLyrics.WinUI3.Services.Database
{
public class DatabaseService
{
private readonly SQLiteConnection _connection;
private readonly CharsetDetector _charsetDetector = new();
public DatabaseService() {
string dbPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MusicMetadataIndex.db"
);
_connection = new SQLiteConnection(dbPath);
_connection.CreateTable<MetadataIndex>();
public DatabaseService()
{
_connection = new SQLiteConnection(Helper.AppInfo.DatabasePath);
if (_connection.GetTableInfo("MetadataIndex").Count == 0)
{
_connection.CreateTable<MetadataIndex>();
}
}
public async Task RebuildMusicMetadataIndexDatabaseAsync(IList<MusicFolder> musicFolders) {
await Task.Run(() => {
public async Task RebuildMusicMetadataIndexDatabaseAsync(IList<string> paths)
{
await Task.Run(() =>
{
_connection.DeleteAll<MetadataIndex>();
foreach (var localMusicFolder in musicFolders) {
if (localMusicFolder.IsValid) {
foreach (var file in Directory.GetFiles(localMusicFolder.Path)) {
foreach (var path in paths)
{
if (Directory.Exists(path))
{
foreach (var file in Directory.GetFiles(path))
{
var fileExtension = Path.GetExtension(file);
var track = new Track(file);
_connection.Insert(new MetadataIndex {
Path = file,
Title = track.Title,
Artist = track.Artist,
});
_connection.Insert(
new MetadataIndex
{
Path = file,
Title = track.Title,
Artist = track.Artist,
}
);
}
}
}
});
}
public Track? GetMusicMetadata(string? title, string? artist) {
var founds = _connection.Table<MetadataIndex>()
.Where(m => m.Title == title && m.Artist == artist).ToList();
if (founds == null || founds.Count == 0) {
public Track? GetMusicMetadata(
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
)
{
if (mediaProps == null || mediaProps.Title == null || mediaProps.Artist == null)
return null;
} else {
var founds = _connection
.Table<MetadataIndex>()
// 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(mediaProps.Title)
&& m.Artist.Contains(mediaProps.Artist)
)
|| (
m.Path != null
&& m.Path.Contains(mediaProps.Title)
&& m.Path.Contains(mediaProps.Artist)
)
)
.ToList();
if (founds == null || founds.Count == 0)
{
return null;
}
else
{
var first = new Track(founds[0].Path);
if (founds.Count == 1) {
if (founds.Count == 1)
{
return first;
} else {
if (first.Lyrics.Exists()) {
}
else
{
if (first.Lyrics.Exists())
{
return first;
} else {
foreach (var found in founds) {
if (found.Path.EndsWith(".lrc")) {
using (FileStream fs = File.OpenRead(found.Path)) {
}
else
{
foreach (var found in founds)
{
if (found.Path.EndsWith(".lrc"))
{
using (FileStream fs = File.OpenRead(found.Path))
{
_charsetDetector.Feed(fs);
_charsetDetector.DataEnd();
}
string content;
if (_charsetDetector.Charset != null) {
Encoding encoding = Encoding.GetEncoding(_charsetDetector.Charset);
if (_charsetDetector.Charset != null)
{
Encoding encoding = Encoding.GetEncoding(
_charsetDetector.Charset
);
content = File.ReadAllText(found.Path, encoding);
} else {
}
else
{
content = File.ReadAllText(found.Path, Encoding.UTF8);
}
first.Lyrics.ParseLRC(content);
@@ -83,7 +134,5 @@ namespace BetterLyrics.WinUI3.Services.Database {
}
}
}
}
}

View File

@@ -1,26 +1,35 @@
using BetterLyrics.WinUI3.Models;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services.Settings {
public static class SettingsDefaultValues {
namespace BetterLyrics.WinUI3.Services.Settings
{
public static class SettingsDefaultValues
{
public const bool IsFirstRun = true;
// Theme
public const int ThemeType = 0; // Follow system
// Language
public const int Language = 0; // Default
// Music
public const string MusicLibraries = "[]";
// Backdrop
public const int BackdropType = 5; // Acrylic Base
public const bool IsCoverOverlayEnabled = true;
public const bool IsDynamicCoverOverlay = true;
public const int CoverOverlayOpacity = 100; // 1.0
public const int CoverOverlayBlurAmount = 200;
// Album art
public const int CoverImageRadius = 24;
// Lyrics
public const int LyricsAlignmentType = 1; // Center
public const int LyricsBlurAmount = 0;
@@ -29,5 +38,7 @@ namespace BetterLyrics.WinUI3.Services.Settings {
public const int LyricsFontSize = 28;
public const bool IsLyricsGlowEffectEnabled = false;
public const bool IsLyricsDynamicGlowEffectEnabled = false;
public const int LyricsFontColorType = 0; // Default
public const int LyricsFontSelectedAccentColorIndex = 0;
}
}

View File

@@ -4,22 +4,31 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.Settings {
public static class SettingsKeys {
namespace BetterLyrics.WinUI3.Services.Settings
{
public static class SettingsKeys
{
public const string IsFirstRun = "IsFirstRun";
// Theme
public const string ThemeType = "ThemeType";
// Language
public const string Language = "Language";
// Music
public const string MusicLibraries = "MusicLibraries";
// Backdrop
public const string BackdropType = "BackdropType";
public const string IsCoverOverlayEnabled = "IsCoverOverlayEnabled";
public const string IsDynamicCoverOverlay = "IsDynamicCoverOverlay";
public const string CoverOverlayOpacity = "CoverOverlayOpacity";
public const string CoverOverlayBlurAmount = "CoverOverlayBlurAmount";
// Album art
public const string CoverImageRadius = "CoverImageRadius";
// Lyrics
public const string LyricsAlignmentType = "LyricsAlignmentType";
public const string LyricsBlurAmount = "LyricsBlurAmount";
@@ -28,6 +37,8 @@ namespace BetterLyrics.WinUI3.Services.Settings {
public const string LyricsFontSize = "LyricsFontSize";
public const string IsLyricsGlowEffectEnabled = "IsLyricsGlowEffectEnabled";
public const string IsLyricsDynamicGlowEffectEnabled = "IsLyricsDynamicGlowEffectEnabled";
public const string LyricsFontColorType = "LyricsFontColorType";
public const string LyricsFontSelectedAccentColorIndex =
"LyricsFontSelectedAccentColorIndex";
}
}

View File

@@ -1,68 +1,90 @@
using ATL;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using DevWinUI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Media;
using Newtonsoft.Json;
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Messages;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using DevWinUI;
using Microsoft.UI.Xaml;
using Newtonsoft.Json;
using Windows.Globalization;
using Windows.Storage;
using Windows.System;
namespace BetterLyrics.WinUI3.Services.Settings {
public partial class SettingsService : ObservableObject {
namespace BetterLyrics.WinUI3.Services.Settings
{
public partial class SettingsService : ObservableObject
{
private readonly ApplicationDataContainer _localSettings;
public bool IsFirstRun {
public SettingsService()
{
_localSettings = ApplicationData.Current.LocalSettings;
_musicLibraries =
[
.. JsonConvert.DeserializeObject<List<string>>(
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)!
)!,
];
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
}
private void WatchMultipleDirectories(IEnumerable<string> directories)
{
foreach (var dir in directories)
{
if (!Directory.Exists(dir))
continue;
var watcher = new FileSystemWatcher
{
Path = dir,
Filter = "*.*",
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
EnableRaisingEvents = true,
};
}
}
private void OnFileCreated(object sender, FileSystemEventArgs e)
{
App.DispatcherQueue.TryEnqueue(() => { });
}
public bool IsFirstRun
{
get => Get(SettingsKeys.IsFirstRun, SettingsDefaultValues.IsFirstRun);
set => Set(SettingsKeys.IsFirstRun, value);
}
[ObservableProperty]
private bool _isRebuildingLyricsIndexDatabase;
private bool _isRebuildingLyricsIndexDatabase = false;
// Theme
public int Theme {
public int Theme
{
get => Get(SettingsKeys.ThemeType, SettingsDefaultValues.ThemeType);
set {
set
{
Set(SettingsKeys.ThemeType, value);
WeakReferenceMessenger.Default.Send(new ThemeChangedMessage((ElementTheme)value));
}
}
// Music
private ObservableCollection<MusicFolder> _musicLibraries;
private ObservableCollection<string> _musicLibraries;
public ObservableCollection<MusicFolder> MusicLibraries {
get {
if (_musicLibraries == null) {
var list = JsonConvert.DeserializeObject<List<MusicFolder>>(
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)
);
_musicLibraries = new ObservableCollection<MusicFolder>(list);
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
}
return _musicLibraries;
}
set {
if (_musicLibraries != null) {
public ObservableCollection<string> MusicLibraries
{
get { return _musicLibraries; }
set
{
if (_musicLibraries != null)
{
_musicLibraries.CollectionChanged -= (_, _) => SaveMusicLibraries();
}
@@ -73,17 +95,20 @@ namespace BetterLyrics.WinUI3.Services.Settings {
}
}
private void SaveMusicLibraries() {
private void SaveMusicLibraries()
{
Set(SettingsKeys.MusicLibraries, JsonConvert.SerializeObject(MusicLibraries.ToList()));
}
// Language
public int Language {
public int Language
{
get => Get(SettingsKeys.Language, SettingsDefaultValues.Language);
set {
set
{
Set(SettingsKeys.Language, value);
switch ((Models.Language)Language) {
switch ((Models.Language)Language)
{
case Models.Language.FollowSystem:
ApplicationLanguages.PrimaryLanguageOverride = "";
break;
@@ -103,81 +128,142 @@ namespace BetterLyrics.WinUI3.Services.Settings {
}
// Backdrop
public int BackdropType {
public int BackdropType
{
get => Get(SettingsKeys.BackdropType, SettingsDefaultValues.BackdropType);
set {
set
{
Set(SettingsKeys.BackdropType, value);
WeakReferenceMessenger.Default.Send(new SystemBackdropChangedMessage((BackdropType)value));
WeakReferenceMessenger.Default.Send(
new SystemBackdropChangedMessage((BackdropType)value)
);
}
}
public bool IsCoverOverlayEnabled {
get => Get(SettingsKeys.IsCoverOverlayEnabled, SettingsDefaultValues.IsCoverOverlayEnabled);
public bool IsCoverOverlayEnabled
{
get =>
Get(
SettingsKeys.IsCoverOverlayEnabled,
SettingsDefaultValues.IsCoverOverlayEnabled
);
set => Set(SettingsKeys.IsCoverOverlayEnabled, value);
}
public bool IsDynamicCoverOverlay {
get => Get(SettingsKeys.IsDynamicCoverOverlay, SettingsDefaultValues.IsDynamicCoverOverlay);
public bool IsDynamicCoverOverlay
{
get =>
Get(
SettingsKeys.IsDynamicCoverOverlay,
SettingsDefaultValues.IsDynamicCoverOverlay
);
set => Set(SettingsKeys.IsDynamicCoverOverlay, value);
}
public int CoverOverlayOpacity {
public int CoverOverlayOpacity
{
get => Get(SettingsKeys.CoverOverlayOpacity, SettingsDefaultValues.CoverOverlayOpacity);
set => Set(SettingsKeys.CoverOverlayOpacity, value);
}
public int CoverOverlayBlurAmount {
get => Get(SettingsKeys.CoverOverlayBlurAmount, SettingsDefaultValues.CoverOverlayBlurAmount);
public int CoverOverlayBlurAmount
{
get =>
Get(
SettingsKeys.CoverOverlayBlurAmount,
SettingsDefaultValues.CoverOverlayBlurAmount
);
set => Set(SettingsKeys.CoverOverlayBlurAmount, value);
}
// Album art
public int CoverImageRadius
{
get => Get(SettingsKeys.CoverImageRadius, SettingsDefaultValues.CoverImageRadius);
set => Set(SettingsKeys.CoverImageRadius, value);
}
// Lyrics
public int LyricsAlignmentType {
public int LyricsAlignmentType
{
get => Get(SettingsKeys.LyricsAlignmentType, SettingsDefaultValues.LyricsAlignmentType);
set => Set(SettingsKeys.LyricsAlignmentType, value);
}
public int LyricsBlurAmount {
public int LyricsBlurAmount
{
get => Get(SettingsKeys.LyricsBlurAmount, SettingsDefaultValues.LyricsBlurAmount);
set => Set(SettingsKeys.LyricsBlurAmount, value);
}
public int LyricsVerticalEdgeOpacity {
get => Get(SettingsKeys.LyricsVerticalEdgeOpacity, SettingsDefaultValues.LyricsVerticalEdgeOpacity);
public int LyricsVerticalEdgeOpacity
{
get =>
Get(
SettingsKeys.LyricsVerticalEdgeOpacity,
SettingsDefaultValues.LyricsVerticalEdgeOpacity
);
set => Set(SettingsKeys.LyricsVerticalEdgeOpacity, value);
}
public float LyricsLineSpacingFactor {
get => Get(SettingsKeys.LyricsLineSpacingFactor, SettingsDefaultValues.LyricsLineSpacingFactor);
public float LyricsLineSpacingFactor
{
get =>
Get(
SettingsKeys.LyricsLineSpacingFactor,
SettingsDefaultValues.LyricsLineSpacingFactor
);
set => Set(SettingsKeys.LyricsLineSpacingFactor, value);
}
public int LyricsFontSize {
public int LyricsFontSize
{
get => Get(SettingsKeys.LyricsFontSize, SettingsDefaultValues.LyricsFontSize);
set => Set(SettingsKeys.LyricsFontSize, value);
}
public bool IsLyricsGlowEffectEnabled {
get => Get(SettingsKeys.IsLyricsGlowEffectEnabled, SettingsDefaultValues.IsLyricsGlowEffectEnabled);
public bool IsLyricsGlowEffectEnabled
{
get =>
Get(
SettingsKeys.IsLyricsGlowEffectEnabled,
SettingsDefaultValues.IsLyricsGlowEffectEnabled
);
set => Set(SettingsKeys.IsLyricsGlowEffectEnabled, value);
}
public bool IsLyricsDynamicGlowEffectEnabled {
get => Get(SettingsKeys.IsLyricsDynamicGlowEffectEnabled, SettingsDefaultValues.IsLyricsDynamicGlowEffectEnabled);
public bool IsLyricsDynamicGlowEffectEnabled
{
get =>
Get(
SettingsKeys.IsLyricsDynamicGlowEffectEnabled,
SettingsDefaultValues.IsLyricsDynamicGlowEffectEnabled
);
set => Set(SettingsKeys.IsLyricsDynamicGlowEffectEnabled, value);
}
private readonly ApplicationDataContainer _localSettings;
private readonly DatabaseService _databaseService;
public SettingsService(DatabaseService databaseService) {
_localSettings = ApplicationData.Current.LocalSettings;
_databaseService = databaseService;
public int LyricsFontColorType
{
get => Get(SettingsKeys.LyricsFontColorType, SettingsDefaultValues.LyricsFontColorType);
set => Set(SettingsKeys.LyricsFontColorType, value);
}
public int LyricsFontSelectedAccentColorIndex
{
get =>
Get(
SettingsKeys.LyricsFontSelectedAccentColorIndex,
SettingsDefaultValues.LyricsFontSelectedAccentColorIndex
);
set
{
if (value >= 0)
Set(SettingsKeys.LyricsFontSelectedAccentColorIndex, value);
}
}
private T Get<T>(string key, T defaultValue = default) {
if (_localSettings.Values.TryGetValue(key, out object value)) {
private T? Get<T>(string key, T? defaultValue = default)
{
if (_localSettings.Values.TryGetValue(key, out object? value))
{
return (T)Convert.ChangeType(value, typeof(T));
}
return defaultValue;
}
private void Set<T>(string key, T value, [CallerMemberName] string propertyName = null) {
private void Set<T>(string key, T value, [CallerMemberName] string? propertyName = null)
{
_localSettings.Values[key] = value;
OnPropertyChanged(propertyName);
}
}
}

View File

@@ -126,6 +126,9 @@
<data name="SettingsPageOpenPath.Content" xml:space="preserve">
<value>Open in file explorer</value>
</data>
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
<value>Open in file explorer</value>
</data>
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
<value>Remove from app</value>
</data>
@@ -276,7 +279,7 @@
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>Lyrics effect</value>
</data>
<data name="SettingsPageAlbum.Text" xml:space="preserve">
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>Album background</value>
</data>
<data name="SettingsPageAbout.Text" xml:space="preserve">
@@ -322,7 +325,7 @@
<value>Setup lyrics database now</value>
</data>
<data name="MainPageInitDatabaseTeachingTip.Subtitle" xml:space="preserve">
<value>Hover on the bottom left corner and then click to open settings page</value>
<value>Hover on the bottom left corner and then click to open settings page</value>
</data>
<data name="MainPageBottomCommandTeachingTip.Subtitle" xml:space="preserve">
<value>Hover on the bottom area to show it</value>
@@ -339,4 +342,31 @@
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>No music playing now</value>
</data>
<data name="SettingsPageDev.Text" xml:space="preserve">
<value>Developer options</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>Play test music</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play using system player</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>Log</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>Font color</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>Default</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>Album art accent color</value>
</data>
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>Album art style</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>Corner radius</value>
</data>
</root>

View File

@@ -126,6 +126,9 @@
<data name="SettingsPageOpenPath.Content" xml:space="preserve">
<value>在文件资源管理器中打开</value>
</data>
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
<value>在文件资源管理器中打开</value>
</data>
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
<value>从应用中移除</value>
</data>
@@ -276,7 +279,7 @@
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌词效果</value>
</data>
<data name="SettingsPageAlbum.Text" xml:space="preserve">
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>专辑背景</value>
</data>
<data name="SettingsPageAbout.Text" xml:space="preserve">
@@ -339,4 +342,31 @@
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>当前没有正在播放的音乐</value>
</data>
<data name="SettingsPageDev.Text" xml:space="preserve">
<value>开发者选项</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>播放测试音乐</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系统播放器播放</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>日志</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字体颜色</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>默认</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>专辑强调色</value>
</data>
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>专辑封面样式</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>圆角半径</value>
</data>
</root>

View File

@@ -126,6 +126,9 @@
<data name="SettingsPageOpenPath.Content" xml:space="preserve">
<value>在檔案總管中開啟</value>
</data>
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
<value>在檔案總管中開啟</value>
</data>
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
<value>從應用程式中移除</value>
</data>
@@ -276,7 +279,7 @@
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞效果</value>
</data>
<data name="SettingsPageAlbum.Text" xml:space="preserve">
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>專輯背景</value>
</data>
<data name="SettingsPageAbout.Text" xml:space="preserve">
@@ -339,4 +342,31 @@
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>目前沒有正在播放的音樂</value>
</data>
<data name="SettingsPageDev.Text" xml:space="preserve">
<value>開發者選項</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>播放測試音樂</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系統播放器播放</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>紀錄</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字體顏色</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>預設</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>專輯強調色</value>
</data>
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>專輯封面樣式</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>圓角半徑</value>
</data>
</root>

View File

@@ -1,24 +1,35 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ATL;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using DevWinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Ude;
using Windows.Graphics.Imaging;
using Windows.Media.Control;
using Windows.Storage.Streams;
using Windows.UI;
using static System.Net.Mime.MediaTypeNames;
using static ATL.LyricsInfo;
using static CommunityToolkit.WinUI.Animations.Expressions.ExpressionValues;
namespace BetterLyrics.WinUI3.ViewModels {
public partial class MainViewModel : ObservableObject {
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private bool _isAnyMusicSessionExisted = false;
@@ -28,8 +39,8 @@ namespace BetterLyrics.WinUI3.ViewModels {
[ObservableProperty]
private string? _artist;
public List<Color> CoverImageDominantColors { get; set; } =
[Colors.Transparent, Colors.Transparent, Colors.Transparent];
[ObservableProperty]
private ObservableCollection<Color> _coverImageDominantColors;
[ObservableProperty]
private BitmapImage? _coverImage;
@@ -46,20 +57,33 @@ namespace BetterLyrics.WinUI3.ViewModels {
[ObservableProperty]
private bool _lyricsExisted = false;
private Helper.ColorThief _colorThief = new();
private readonly ColorThief _colorThief = new();
private readonly SettingsService _settingsService;
private readonly DatabaseService _databaseService;
public MainViewModel(DatabaseService databaseService) {
private readonly int _accentColorCount = 3;
public MainViewModel(SettingsService settingsService, DatabaseService databaseService)
{
_settingsService = settingsService;
_databaseService = databaseService;
CoverImageDominantColors =
[
.. Enumerable.Repeat(Colors.Transparent, _accentColorCount),
];
}
public List<LyricsLine> GetLyrics(Track? track) {
public List<LyricsLine> GetLyrics(Track? track)
{
List<LyricsLine> result = [];
var lyricsPhrases = track?.Lyrics.SynchronizedLyrics;
if (lyricsPhrases?.Count > 0) {
if (lyricsPhrases[0].TimestampMs > 0) {
var lyricsPhrases = track?.Lyrics?.SynchronizedLyrics;
if (lyricsPhrases?.Count > 0)
{
if (lyricsPhrases[0].TimestampMs > 0)
{
var placeholder = new LyricsPhrase(0, " ");
lyricsPhrases.Insert(0, placeholder);
lyricsPhrases.Insert(0, placeholder);
@@ -68,40 +92,50 @@ namespace BetterLyrics.WinUI3.ViewModels {
LyricsLine? lyricsLine = null;
for (int i = 0; i < lyricsPhrases?.Count; i++) {
for (int i = 0; i < lyricsPhrases?.Count; i++)
{
var lyricsPhrase = lyricsPhrases[i];
int startTimestampMs = lyricsPhrase.TimestampMs;
int endTimestampMs;
if (i + 1 < lyricsPhrases.Count) {
if (i + 1 < lyricsPhrases.Count)
{
endTimestampMs = lyricsPhrases[i + 1].TimestampMs;
} else {
}
else
{
endTimestampMs = (int)track.DurationMs;
}
lyricsLine ??= new LyricsLine {
StartPlayingTimestampMs = startTimestampMs,
};
lyricsLine ??= new LyricsLine { StartPlayingTimestampMs = startTimestampMs };
lyricsLine.Texts.Add(lyricsPhrase.Text);
if (endTimestampMs == startTimestampMs) {
if (endTimestampMs == startTimestampMs)
{
continue;
} else {
}
else
{
lyricsLine.EndPlayingTimestampMs = endTimestampMs;
result.Add(lyricsLine);
lyricsLine = null;
}
}
LyricsExisted = result.Count != 0;
if (!LyricsExisted)
{
ShowLyricsOnly = false;
}
return result;
}
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps, ICanvasAnimatedControl control) {
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
)
{
SoftwareBitmap? coverSoftwareBitmap = null;
uint coverImagePixelWidth = 0;
uint coverImagePixelHeight = 0;
@@ -111,26 +145,37 @@ namespace BetterLyrics.WinUI3.ViewModels {
IRandomAccessStream? stream = null;
var track = _databaseService.GetMusicMetadata(Title, Artist);
var track = _databaseService.GetMusicMetadata(mediaProps);
if (mediaProps?.Thumbnail is IRandomAccessStreamReference reference) {
if (mediaProps?.Thumbnail is IRandomAccessStreamReference reference)
{
stream = await reference.OpenReadAsync();
} else {
if (track?.EmbeddedPictures.Count > 0) {
}
else
{
if (track?.EmbeddedPictures.Count > 0)
{
var bytes = track.EmbeddedPictures[0].PictureData;
if (bytes != null) {
if (bytes != null)
{
stream = await Helper.ImageHelper.GetStreamFromBytesAsync(bytes);
}
}
}
// Set cover image and dominant colors
if (stream == null) {
if (stream == null)
{
CoverImage = null;
for (int i = 0; i < 3; i++) {
CoverImageDominantColors[i] = Colors.Transparent;
}
} else {
CoverImageDominantColors =
[
.. Enumerable.Repeat(Colors.Transparent, _accentColorCount),
];
_settingsService.LyricsFontSelectedAccentColorIndex =
_settingsService.LyricsFontSelectedAccentColorIndex;
}
else
{
CoverImage = new BitmapImage();
await CoverImage.SetSourceAsync(stream);
stream.Seek(0);
@@ -144,19 +189,24 @@ namespace BetterLyrics.WinUI3.ViewModels {
BitmapAlphaMode.Premultiplied
);
var quantizedColors = await _colorThief.GetPalette(decoder, 3);
for (int i = 0; i < 3; i++) {
Helper.QuantizedColor quantizedColor = quantizedColors[i];
CoverImageDominantColors[i] = Color.FromArgb(
quantizedColor.Color.A, quantizedColor.Color.R, quantizedColor.Color.G, quantizedColor.Color.B);
}
CoverImageDominantColors =
[
.. (await _colorThief.GetPalette(decoder, _accentColorCount)).Select(color =>
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
),
];
_settingsService.LyricsFontSelectedAccentColorIndex =
_settingsService.LyricsFontSelectedAccentColorIndex;
stream.Dispose();
}
return (GetLyrics(track), coverSoftwareBitmap, coverImagePixelWidth, coverImagePixelHeight);
return (
GetLyrics(track),
coverSoftwareBitmap,
coverImagePixelWidth,
coverImagePixelHeight
);
}
}
}

View File

@@ -1,54 +1,56 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using System;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Windows.ApplicationModel.Core;
using Windows.Media;
using Windows.Media.Playback;
using Windows.Storage.Pickers;
using Windows.System;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.ViewModels {
public partial class SettingsViewModel : ObservableObject {
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class SettingsViewModel(
DatabaseService databaseService,
SettingsService settingsService,
MainViewModel mainViewModel
) : ObservableObject
{
private readonly MediaPlayer _mediaPlayer = new();
private readonly DatabaseService _databaseService;
private readonly DatabaseService _databaseService = databaseService;
[ObservableProperty]
private SettingsService _settingsService;
public SettingsService SettingsService => settingsService;
[ObservableProperty]
private string _version;
public MainViewModel MainViewModel => mainViewModel;
public SettingsViewModel(DatabaseService databaseService, SettingsService settingsService) {
_databaseService = databaseService;
_settingsService = settingsService;
var version = Package.Current.Id.Version;
Version = $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
public string Version => Helper.AppInfo.AppVersion;
[RelayCommand]
private async Task RebuildLyricsIndexDatabaseAsync() {
private async Task RebuildLyricsIndexDatabaseAsync()
{
SettingsService.IsRebuildingLyricsIndexDatabase = true;
await _databaseService.RebuildMusicMetadataIndexDatabaseAsync(SettingsService.MusicLibraries);
await _databaseService.RebuildMusicMetadataIndexDatabaseAsync(
SettingsService.MusicLibraries
);
SettingsService.IsRebuildingLyricsIndexDatabase = false;
}
[RelayCommand]
private async Task RemoveFolderAsync(MusicFolder musicFolder) {
SettingsService.MusicLibraries.Remove(musicFolder);
public async Task RemoveFolderAsync(string path)
{
SettingsService.MusicLibraries.Remove(path);
await RebuildLyricsIndexDatabaseAsync();
}
[RelayCommand]
private async Task AddFolderAsync() {
private async Task SelectAndAddFolderAsync()
{
var picker = new FolderPicker();
picker.FileTypeFilter.Add("*");
@@ -58,41 +60,64 @@ namespace BetterLyrics.WinUI3.ViewModels {
var folder = await picker.PickSingleFolderAsync();
if (folder != null) {
string path = folder.Path;
bool existed = SettingsService.MusicLibraries.Count((x) => x.Path == path) > 0;
if (existed) {
MainWindow.StackedNotificationsBehavior?.Show(App.ResourceLoader.GetString("SettingsPagePathExistedInfo"), 3900);
} else {
SettingsService.MusicLibraries.Add(new MusicFolder(path));
await RebuildLyricsIndexDatabaseAsync();
}
App.Current.SettingsWindow!.AppWindow.MoveInZOrderAtTop();
if (folder != null)
{
await AddFolderAsync(folder.Path);
}
}
private async Task AddFolderAsync(string path)
{
bool existed = SettingsService.MusicLibraries.Any((x) => x == path);
if (existed)
{
MainWindow.StackedNotificationsBehavior?.Show(
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"),
Helper.AnimationHelper.StackedNotificationsShowingDuration
);
}
else
{
SettingsService.MusicLibraries.Add(path);
await RebuildLyricsIndexDatabaseAsync();
}
}
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync() {
await Launcher.LaunchUriAsync(new Uri("https://github.com/jayfunc/BetterLyrics"));
private static async Task LaunchProjectGitHubPageAsync()
{
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
}
private static void OpenFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = path,
UseShellExecute = true,
}
);
}
public static void OpenMusicFolder(string path)
{
OpenFolderInFileExplorer(path);
}
[RelayCommand]
private void OpenFolderInFileExplorer(MusicFolder musicFolder) {
Process.Start(new ProcessStartInfo {
FileName = "explorer.exe",
Arguments = musicFolder.Path,
UseShellExecute = true
});
}
[RelayCommand]
private void RestartApp() {
private static void RestartApp()
{
// The restart will be executed immediately.
AppRestartFailureReason failureReason =
Microsoft.Windows.AppLifecycle.AppInstance.Restart("");
// If the restart fails, handle it here.
switch (failureReason) {
switch (failureReason)
{
case AppRestartFailureReason.RestartPending:
break;
case AppRestartFailureReason.NotInForeground:
@@ -104,5 +129,18 @@ namespace BetterLyrics.WinUI3.ViewModels {
}
}
[RelayCommand]
private async Task PlayTestingMusicTask()
{
await AddFolderAsync(Helper.AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(Helper.AppInfo.TestMusicPath));
_mediaPlayer.Play();
}
[RelayCommand]
private static void OpenLogFolder()
{
OpenFolderInFileExplorer(Helper.AppInfo.LogDirectory);
}
}
}

View File

@@ -32,6 +32,7 @@
<canvas:CanvasAnimatedControl
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Loaded="LyricsCanvas_Loaded"
Update="LyricsCanvas_Update">
@@ -111,32 +112,40 @@
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel
x:Name="SongInfoStackPanel"
<Grid
x:Name="SongInfoInnerGrid"
Grid.Column="0"
Grid.ColumnSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Center">
Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition Height="4*" />
<!-- Cover area -->
<RowDefinition Height="20*" />
<!-- Spacer -->
<RowDefinition Height="2*" />
<!-- Title and artist area -->
<RowDefinition Height="5*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<StackPanel.Resources>
<Grid.Resources>
<!-- Animation for song info -->
<Storyboard x:Name="SongInfoStackPanelFadeInStoryboard">
<DoubleAnimation
Storyboard.TargetName="SongInfoStackPanel"
Storyboard.TargetName="SongInfoInnerGrid"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2" />
</Storyboard>
<Storyboard x:Name="SongInfoStackPanelFadeOutStoryboard" BeginTime="0:0:0.2">
<DoubleAnimation
Storyboard.TargetName="SongInfoStackPanel"
Storyboard.TargetName="SongInfoInnerGrid"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:0.2" />
</Storyboard>
</StackPanel.Resources>
</Grid.Resources>
<interactivity:Interaction.Behaviors>
@@ -157,179 +166,142 @@
<!-- Cover area -->
<Grid
x:Name="CoverGrid"
MaxWidth="300"
MaxHeight="300"
CornerRadius="24">
<Grid.Resources>
x:Name="CoverArea"
Grid.Row="1"
SizeChanged="CoverArea_SizeChanged">
<!-- Animation for cover grid -->
<Storyboard x:Key="CoverGridShowStoryboard">
<DoubleAnimation
EnableDependentAnimation="True"
Storyboard.TargetName="CoverGrid"
Storyboard.TargetProperty="Height"
To="300"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation
BeginTime="0:0:0.2"
Storyboard.TargetName="CoverGrid"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
<Storyboard x:Key="CoverGridHideStoryboard">
<DoubleAnimation
Storyboard.TargetName="CoverGrid"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation
BeginTime="0:0:0.2"
EnableDependentAnimation="True"
Storyboard.TargetName="CoverGrid"
Storyboard.TargetProperty="Height"
To="0"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
<Grid x:Name="CoverImageGrid" CornerRadius="24">
<Image
x:Name="CoverImage"
Source="{x:Bind ViewModel.CoverImage, Mode=OneWay}"
Stretch="Uniform">
<Image.Resources>
<Storyboard x:Key="CoverIamgeFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CoverIamgeFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Image.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
</Grid>
</Grid.Resources>
</Grid>
<Image x:Name="CoverImage" Source="{x:Bind ViewModel.CoverImage, Mode=OneWay}">
<Image.Resources>
<Storyboard x:Key="CoverIamgeFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<!-- Title and artist -->
<StackPanel Grid.Row="3" Orientation="Vertical">
<!-- Song title -->
<controls:OpacityMaskView x:Name="TitleOpacityMaskView" HorizontalAlignment="Center">
<controls:OpacityMaskView.OpacityMask>
<Rectangle Fill="{StaticResource BaseHighEdgeHorizontalFadeBrush}" />
</controls:OpacityMaskView.OpacityMask>
<controls:OpacityMaskView.Resources>
<Storyboard x:Key="TitleOpacityMaskViewFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TitleOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CoverIamgeFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<Storyboard x:Key="TitleOpacityMaskViewFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TitleOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Image.Resources>
</controls:OpacityMaskView.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeOutStoryboard}" />
<interactivity:ControlStoryboardAction Storyboard="{StaticResource TitleOpacityMaskViewFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeInStoryboard}" />
<interactivity:ControlStoryboardAction Storyboard="{StaticResource TitleOpacityMaskViewFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
</Grid>
<labs:MarqueeText
x:Name="TitleTextBlock"
Behavior="Bouncing"
FontSize="{StaticResource TitleTextBlockFontSize}"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Text="{x:Bind ViewModel.Title, Mode=OneWay}" />
</controls:OpacityMaskView>
<!-- Song title -->
<controls:OpacityMaskView x:Name="TitleOpacityMaskView" HorizontalAlignment="Center">
<controls:OpacityMaskView.OpacityMask>
<Rectangle Fill="{StaticResource BaseHighEdgeHorizontalFadeBrush}" />
</controls:OpacityMaskView.OpacityMask>
<!-- Song artist -->
<controls:OpacityMaskView x:Name="ArtistOpacityMaskView" HorizontalAlignment="Center">
<controls:OpacityMaskView.OpacityMask>
<Rectangle Fill="{StaticResource BaseHighEdgeHorizontalFadeBrush}" />
</controls:OpacityMaskView.OpacityMask>
<controls:OpacityMaskView.Resources>
<Storyboard x:Key="ArtistOpacityMaskViewFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ArtistOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="ArtistOpacityMaskViewFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ArtistOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</controls:OpacityMaskView.Resources>
<controls:OpacityMaskView.Resources>
<Storyboard x:Key="TitleOpacityMaskViewFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TitleOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="TitleOpacityMaskViewFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TitleOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</controls:OpacityMaskView.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource ArtistOpacityMaskViewFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource ArtistOpacityMaskViewFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource TitleOpacityMaskViewFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource TitleOpacityMaskViewFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<labs:MarqueeText
Behavior="Bouncing"
FontSize="{StaticResource SubtitleTextBlockFontSize}"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Opacity="0.5"
Text="{x:Bind ViewModel.Artist, Mode=OneWay}" />
</controls:OpacityMaskView>
<TextBlock
x:Name="TitleTextBlock"
Margin="0,12,0,0"
FontSize="{StaticResource TitleTextBlockFontSize}"
FontWeight="SemiBold"
Text="{x:Bind ViewModel.Title, Mode=OneWay}" />
</controls:OpacityMaskView>
</StackPanel>
<!-- Song artist -->
<controls:OpacityMaskView x:Name="ArtistOpacityMaskView" HorizontalAlignment="Center">
<controls:OpacityMaskView.OpacityMask>
<Rectangle Fill="{StaticResource BaseHighEdgeHorizontalFadeBrush}" />
</controls:OpacityMaskView.OpacityMask>
<controls:OpacityMaskView.Resources>
<Storyboard x:Key="ArtistOpacityMaskViewFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ArtistOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="ArtistOpacityMaskViewFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ArtistOpacityMaskView" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</controls:OpacityMaskView.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource ArtistOpacityMaskViewFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource ArtistOpacityMaskViewFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<TextBlock
FontSize="{StaticResource SubtitleTextBlockFontSize}"
FontWeight="SemiBold"
Opacity="0.5"
Text="{x:Bind ViewModel.Artist, Mode=OneWay}" />
</controls:OpacityMaskView>
</StackPanel>
</Grid>
<TextBlock
x:Name="MainPageNoMusicPlayingTextBlock"

View File

@@ -10,7 +10,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
x:Name="RootPage"
NavigationCacheMode="Required"
mc:Ignorable="d">
@@ -29,54 +28,21 @@
<controls:SettingsExpander
x:Uid="SettingsPageMusicLib"
HeaderIcon="{ui:FontIcon Glyph=&#xE8B7;}"
IsEnabled="{x:Bind ViewModel.SettingsService.IsRebuildingLyricsIndexDatabase, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
IsExpanded="True"
ItemsSource="{x:Bind ViewModel.SettingsService.MusicLibraries, Mode=OneWay}">
<controls:SettingsExpander.ItemTemplate>
<DataTemplate>
<controls:SettingsCard Header="{Binding Path, Mode=OneWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{Binding IsValid, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding IsValid, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<controls:SettingsCard.Description>
<TextBlock x:Uid="SettingsPagePathNotFound" Foreground="{ThemeResource SystemFillColorCriticalBrush}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{Binding IsValid, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding IsValid, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
</controls:SettingsCard.Description>
<controls:SettingsCard Header="{Binding}">
<StackPanel Orientation="Horizontal">
<HyperlinkButton
x:Uid="SettingsPageOpenPath"
Command="{Binding DataContext.OpenFolderInFileExplorerCommand, ElementName=RootPage}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsValid, Mode=OneWay}" />
Click="SettingsPageOpenPathButton_Click"
Tag="{Binding}" />
<HyperlinkButton
x:Uid="SettingsPageRemovePath"
Command="{Binding DataContext.RemoveFolderCommand, ElementName=RootPage}"
CommandParameter="{Binding}"
IsEnabled="{Binding DataContext.SettingsService.IsRebuildingLyricsIndexDatabase, Converter={StaticResource BoolNegationConverter}, Mode=OneWay, ElementName=RootPage}" />
Click="SettingsPageRemovePathButton_Click"
Tag="{Binding}" />
</StackPanel>
</controls:SettingsCard>
</DataTemplate>
@@ -110,11 +76,8 @@
</InfoBar>
</controls:SettingsExpander.ItemsHeader>
<controls:SettingsExpander.ItemsFooter>
<controls:SettingsCard
x:Uid="SettingsPageAddFolder"
IsEnabled="{x:Bind ViewModel.SettingsService.IsRebuildingLyricsIndexDatabase, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<Button x:Uid="SettingsPageAddFolderButton" Command="{x:Bind ViewModel.AddFolderCommand}" />
<controls:SettingsCard x:Uid="SettingsPageAddFolder" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<Button x:Uid="SettingsPageAddFolderButton" Command="{x:Bind ViewModel.SelectAndAddFolderCommand}" />
</controls:SettingsCard>
</controls:SettingsExpander.ItemsFooter>
</controls:SettingsExpander>
@@ -175,7 +138,7 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<TextBlock x:Uid="SettingsPageAlbum" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<TextBlock x:Uid="SettingsPageAlbumOverlay" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageCoverOverlay"
@@ -233,6 +196,27 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<TextBlock x:Uid="SettingsPageAlbumStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon Glyph=&#xE71A;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.SettingsService.CoverImageRadius, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="2"
TickFrequency="2"
TickPlacement="Outside"
Value="{x:Bind ViewModel.SettingsService.CoverImageRadius, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageLyricsStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon Glyph=&#xE8E3;}">
@@ -243,6 +227,69 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=&#xE8D3;}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="False" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="True" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ComboBox SelectedIndex="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=TwoWay}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Vertical">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<GridView ItemsSource="{x:Bind ViewModel.MainViewModel.CoverImageDominantColors, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.SettingsService.LyricsFontSelectedAccentColorIndex, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate>
<GridViewItem>
<StackPanel>
<Border
Width="64"
Height="64"
CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock
Margin="4,0,4,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</GridViewItem>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon Glyph=&#xE8E9;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
@@ -341,7 +388,7 @@
<TextBlock x:Uid="SettingsPageAbout" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Icon.png}">
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Logo.png}">
<controls:SettingsCard.Description>
<RichTextBlock>
<Paragraph>
@@ -359,6 +406,16 @@
HeaderIcon="{ui:FontIcon Glyph=&#xE943;}"
IsClickEnabled="True" />
<TextBlock x:Uid="SettingsPageDev" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
<Button x:Uid="SettingsPagePlayingMockMusicButton" Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLog">
<Button x:Uid="SettingsPageOpenLogFolderButton" Command="{x:Bind ViewModel.OpenLogFolderCommand}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -1,43 +1,40 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using ABI.System;
using Windows.System;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using System.Diagnostics;
using WinRT.Interop;
using Windows.Storage.Pickers;
using BetterLyrics.WinUI3.Models;
using Microsoft.Windows.ApplicationModel.Resources;
using BetterLyrics.WinUI3.Services.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Views {
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsPage : Page {
public sealed partial class SettingsPage : Page
{
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
public SettingsPage() {
public SettingsPage()
{
this.InitializeComponent();
DataContext = Ioc.Default.GetService<SettingsViewModel>();
}
private void SettingsPageOpenPathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
SettingsViewModel.OpenMusicFolder((string)(sender as HyperlinkButton)!.Tag);
}
private async void SettingsPageRemovePathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
await ViewModel.RemoveFolderAsync((string)(sender as HyperlinkButton)!.Tag);
}
}
}

View File

@@ -0,0 +1,35 @@
# How to install ".msixbundle" package
## Pre-steps
Be sure that you have already enable developer mode. To do that, you can follow the steps below:
1. Go to "Settings", select "System", go to "Developer Options".
![alt text](image.png)
2. Turn on "Developer Mode" and enable local PowerShell script allowance.
![alt text](image-1.png)
Now you are good to go.
## Step 1
Unzip downloaded .zip file, right-click on "install.ps1", select "Run using PowerShell".
![alt text](image-2.png)
## Step 2
Press "Enter" to continue, and agree on the popup window.
![alt text](image-3.png)
## Step 3
Enter "Y" to install cert.
![alt text](image-5.png)
## Step 4
You are good to go now.
![alt text](image-6.png)
> If you fail to install it with the previous version installed, please try to uninstall the old one and install it again.

BIN
How2Install/image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
How2Install/image-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Some files were not shown because too many files have changed in this diff Show More