Compare commits

..

74 Commits

Author SHA1 Message Date
Zhe Fang
d0b626c508 fix: issue related with docked mode 2025-10-22 10:52:57 -04:00
Zhe Fang
67a45e90fa chore: update version code 2025-10-22 10:31:14 -04:00
Zhe Fang
b4c7655043 chores: move window color sampling settings item to general tab, remove useless debug writeline 2025-10-22 10:23:50 -04:00
Zhe Fang
2adc2aced2 fix: window color sampling incorrect behavior 2025-10-22 10:18:49 -04:00
Zhe Fang
e638739638 fix: improve docked mode window sizeing behaviors 2025-10-22 09:24:16 -04:00
Zhe Fang
c24213358e Merge pull request #118 from Storyteller-Studios/dev
Improve identification of NeteaseFamily and fix Lrc Parser unexpected behavior when parsing [Min:Sec:MillSec]
2025-10-22 07:58:28 -04:00
Raspberry-Monster
6e78f849c4 chores: Improve LrcParser 2025-10-22 14:10:23 +08:00
Raspberry-Monster
80444b69e0 chores: Improve identification of NeteaseFamily and fix Lrc Parser unexpected behavior when parsing [Min:Sec:MillSec] 2025-10-22 13:06:33 +08:00
Zhe Fang
78775e9bb3 Update README.md 2025-10-21 22:51:53 -04:00
Zhe Fang
78c81347c8 feat: add window bounds settings in ui, change ExtendedSlider layout 2025-10-21 17:15:15 -04:00
Zhe Fang
65941323eb chore: update version code 2025-10-21 15:34:13 -04:00
Zhe Fang
aee79b9971 Update README.md
chrore: update Google Drive link
2025-10-21 15:33:09 -04:00
Zhe Fang
97df1c1891 fix: MediaSourceProviderInfo ctor with 2 args didn't listen on ItemPropertyChanged and CollectionChanged events of LyricsSearchProvidersInfo and AlbumArtSearchProvidersInfo 2025-10-21 15:22:31 -04:00
Zhe Fang
8a831b17cc add ani for fluid bg 2025-10-21 09:33:07 -04:00
Zhe Fang
78076efe40 Update README.md 2025-10-21 09:09:09 -04:00
Zhe Fang
d0346bf422 fix white edge issue in docked mode 2025-10-21 07:09:36 -04:00
Zhe Fang
7cb4172e84 Update README.md 2025-10-20 22:07:36 -04:00
Zhe Fang
1f2426f51b Update README.md 2025-10-20 21:51:52 -04:00
Zhe Fang
e122fdabe8 add spectrum 2025-10-20 21:38:22 -04:00
Zhe Fang
97078d966d enhance lyrics window status manage, add fluid layer background, fix #111 #96 #91 2025-10-20 14:38:09 -04:00
Zhe Fang
c43b69b4cb rollback window ctrl btn to previous version 2025-10-12 09:40:10 -04:00
Zhe Fang
94b22552e5 update doc 2025-10-10 11:40:44 -04:00
Zhe Fang
6deb16f6cb update version code 2025-10-10 11:34:39 -04:00
Zhe Fang
e467ab9c73 替换 SourceAppUserModelId 为 PlayerId 并新增 SongId
将 SongInfo 中的 SourceAppUserModelId 属性替换为 PlayerId,新增可选属性 SongId 以支持更精确的歌曲标识。
更新了相关服务和方法的参数签名,增加对 SongId 的支持,包括 SearchSmartlyAsync 和 SearchSingleAsync 方法。
调整了 MediaSessionsService 的逻辑,支持不同播放器的 SongId 处理。
优化了代码的灵活性和可扩展性,便于处理多播放器和歌曲标识。
2025-10-10 11:29:50 -04:00
Zhe Fang
ea038c9c56 更新 README.md 2025-10-05 07:59:44 -04:00
Zhe Fang
560250ad30 更新 README.md 2025-10-05 07:57:35 -04:00
Zhe Fang
536acc69a5 更新 README.md 2025-10-05 07:56:00 -04:00
Zhe Fang
0bbb379912 update LICENSE.txt 2025-09-21 21:15:15 -04:00
Zhe Fang
1f4d29e6f2 更新 README.md 2025-09-13 06:49:20 -04:00
Zhe Fang
5d1d7476c9 update screenshots 2025-09-11 21:10:24 -04:00
Zhe Fang
e016baefe1 doc 2025-09-11 20:44:17 -04:00
Zhe Fang
70b6194788 Improve formatting and clarity in README.md 2025-09-11 20:32:26 -04:00
Zhe Fang
9f103b0ea3 Update README with QQ 音乐 details and grammar fixes 2025-09-10 09:30:39 -04:00
Zhe Fang
2924140f95 Refine README.md for grammar and clarity
Corrected grammar and punctuation throughout the README file for improved clarity and consistency.
2025-09-08 18:33:06 -04:00
Zhe Fang
3d3f168926 更新 README.md 2025-09-05 07:23:52 -04:00
Zhe Fang
63b2285a36 Update download link for app in README 2025-09-05 07:03:26 -04:00
Zhe Fang
780689fa05 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-09-04 15:19:36 -04:00
Zhe Fang
01384717c4 更新应用版本和图像处理逻辑
在 `Package.appxmanifest` 中,将版本号更新为 `1.0.73.0`,发布者显示名称更改为 `Zhe Fang`。
在 `ImageHelper.cs` 中新增 `DataUrlToByteArray` 和 `GetImageBytesFromUrlAsync` 方法,以支持数据 URL 和网络图片的处理。
在 `MediaSessionsService.cs` 中,更新了音乐专辑封面图像的下载逻辑,改用新的图像获取方法并记录 URL 信息。
2025-09-04 15:19:33 -04:00
Zhe Fang
e18d78170a Add Apple Music configuration instructions
Added instructions for configuring Apple Music in the README.
2025-09-04 09:38:31 -04:00
Zhe Fang
023bf77afc Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-09-04 09:19:18 -04:00
Zhe Fang
2877ac2101 更新版本并增强媒体处理功能
- 更新 `Package.appxmanifest` 版本号至 `1.0.72.0`。
- 修改 `LXMusic.cs` 中的 `QuerySuffix`,新增 `picUrl` 过滤器。
- 清理 `MediaSourceProviderToLogoUriConverter.cs` 中的 `using` 语句。
- 在 `ImageHelper.cs` 中添加异步方法 `DownloadImageAsByteArrayAsync`,用于下载图像。
- 在 `MediaSessionsService.cs` 中添加 `_lxMusicAlbumArtBytes` 字节数组以存储专辑封面。
- 更新媒体属性处理逻辑,以支持从 `picUrl` 下载专辑封面。
- 修改 SSE 消息接收逻辑,支持异步操作以处理图像下载和更新。
2025-09-04 09:19:16 -04:00
Zhe Fang
16d82109bb 更新 README.md 2025-09-03 12:19:09 -04:00
Zhe Fang
c703f04119 Revise README for Microsoft Store and download options 2025-09-02 18:30:31 -04:00
Zhe Fang
998853f9d2 更新 README.md 2025-09-02 13:40:45 -04:00
Zhe Fang
f560735da0 更新 README.md 2025-09-02 13:35:09 -04:00
Zhe Fang
ab9da73b49 更新 README.md 2025-09-02 13:00:38 -04:00
Zhe Fang
dc364edf75 更新 README.md 2025-09-02 12:59:27 -04:00
Zhe Fang
7fbc8fbfe7 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-09-02 07:38:47 -04:00
Zhe Fang
4a00bb2ddf 更新版本并添加对 Apple Music 的支持
- 将 `Package.appxmanifest` 的版本号更新为 `1.0.71.0`。
- 移除 `BetterLyrics.WinUI3.csproj` 中对 `TinyPinyin.Net` 的引用。
- 在 `PlaybackSettingsControl.xaml` 中更新目标语言选择,移除中文选项并添加多个新语言。
- 新增 Apple Music 令牌输入框和按钮,允许用户保存令牌。
- 在 `LyricsSearchProviderToDisplayNameConverter.cs` 和 `TranslationSearchProviderToDisplayNameConverter.cs` 中添加对 Apple Music 的支持。
- 在 `LyricsSearchProvider.cs` 中新增 `AppleMusic` 作为歌词搜索提供者,并添加相关缓存目录和格式。
- 更新 `LanguageHelper.cs` 中的目标语言列表。
- 将 `TranslationSettings.cs` 中的 `SelectedTargetLanguageIndex` 属性更改为 `SelectedTargetLanguageCode`。
- 在 `LyricsSearchService.cs` 中添加 Apple Music 的歌词搜索功能。
- 更新 `MediaSessionsService.cs` 中的翻译和歌词更新逻辑。
- 移除 `SettingsPageViewModel.cs` 中的库信息支持,添加对媒体会话服务的引用。
- 新增 `AppleMusic.cs` 文件,包含与 Apple Music API 交互的逻辑。
2025-09-02 07:38:45 -04:00
Zhe Fang
7472aa048f 更新 README.md 2025-08-27 22:07:14 -04:00
Zhe Fang
49b0f7a692 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-27 08:28:44 -04:00
Zhe Fang
4d0602ebef 更新版本号并优化歌词窗口边界计算
在 `Package.appxmanifest` 文件中,将应用程序版本号从 `1.0.66.0` 更新为 `1.0.67.0`。
在 `LiveStates.cs` 文件中,修改了 `DemoLyricsWindowBounds` 的计算方式,以更准确地反映歌词窗口在监视器上的位置。
2025-08-27 08:28:42 -04:00
Zhe Fang
626395d93a Update instructions from 'Clone' to 'Fork' 2025-08-26 22:34:18 -04:00
Zhe Fang
0ab5602569 Revise translation contribution instructions
Updated translation instructions for contributors.
2025-08-26 16:50:34 -04:00
Zhe Fang
37a7528762 更新 FAQ.md 2025-08-25 08:56:13 -04:00
Zhe Fang
3ca391a509 更新 FAQ.md 2025-08-25 08:54:43 -04:00
Zhe Fang
f5e542d2f3 更新 How2Install.md 2025-08-25 08:53:10 -04:00
Zhe Fang
107bdf8bee 更新 README.md 2025-08-25 08:51:19 -04:00
Zhe Fang
3e9e56f5cc ignore ttml x-roman span 2025-08-25 07:12:24 -04:00
Zhe Fang
b0fd43ead5 fix input issue in search panel; fix display issue in lyrics window manager 2025-08-24 16:29:28 -04:00
Zhe Fang
81c59495c0 improvement 2025-08-23 21:12:57 -04:00
Zhe Fang
7c5f1a804e Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-23 18:09:14 -04:00
Zhe Fang
13ee4227f7 fix #101 fix #100 2025-08-23 18:09:12 -04:00
Zhe Fang
c1360ac972 更新 README.md 2025-08-22 17:48:53 -04:00
Zhe Fang
c72aa8a58f 更新 FAQ.md 2025-08-22 15:33:46 -04:00
Zhe Fang
9545ed610b update readme 2025-08-22 15:26:51 -04:00
Zhe Fang
1c4515acb9 update readme 2025-08-22 15:18:40 -04:00
Zhe Fang
92a6fe46de update docs 2025-08-22 15:00:57 -04:00
Zhe Fang
2f9fa02214 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-21 17:56:24 -04:00
Zhe Fang
c174363c07 fix #99 ;add thumb for timeline slider; add margin for top command area 2025-08-21 17:56:13 -04:00
Zhe Fang
8506062c9a Update FUNDING.yml 2025-08-21 13:19:24 -04:00
Zhe Fang
52711cba1f showing ordered system font family list; fix issue that timeline change can not be invoked sometimes; add jump to specific lyrics line when clikcing in searching flyout 2025-08-21 11:01:39 -04:00
Zhe Fang
7eda076920 fix #85 and some other bugs fixed 2025-08-20 20:12:18 -04:00
Zhe Fang
282c2f5ac0 update readme for demo video url changed 2025-08-18 15:06:17 -04:00
187 changed files with 6695 additions and 4216 deletions

2
.github/FUNDING.yml vendored
View File

@@ -12,4 +12,4 @@ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cl
polar: # Replace with a single Polar username
buy_me_a_coffee: founchoo
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://paypal.me/zhefangpay']

View File

@@ -12,13 +12,13 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.56.0" />
Version="1.0.83.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>BetterLyrics</DisplayName>
<PublisherDisplayName>founchoo</PublisherDisplayName>
<PublisherDisplayName>Zhe Fang</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>

View File

@@ -51,10 +51,14 @@
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
<converter:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
<converter:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
<converter:RectToMarginConverter x:Key="RectToMarginConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />

View File

@@ -84,7 +84,7 @@ namespace BetterLyrics.WinUI3
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHelper.OpenWindow<LyricsWindow>();
WindowHelper.OpenOrShowWindow<LyricsWindow>();
}
private static void ConfigureServices()
@@ -113,11 +113,11 @@ namespace BetterLyrics.WinUI3
.AddSingleton<ILastFMService, LastFMService>()
// ViewModels
.AddSingleton<AppSettingsControlViewModel>()
.AddSingleton<LyricsBackgroundSettingsControlViewModel>()
.AddSingleton<AlbumArtLayoutSettingsControlViewModel>()
.AddSingleton<PlaybackSettingsControlViewModel>()
.AddSingleton<MediaSettingsControlViewModel>()
.AddSingleton<AllLyricsSettingsControlViewModel>()
.AddSingleton<LyricsSearchControlViewModel>()
.AddSingleton<LyricsWindowSettingsControlViewModel>()
.AddSingleton<LyricsWindowSwitchControlViewModel>()
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

View File

@@ -20,17 +20,23 @@
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\effect.bin" />
<None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Assets\Wiki82.profile.xml" />
<None Remove="Controls\AlbumArtLayoutSettingsControl.xaml" />
<None Remove="Controls\AllLyricsSettingsControl.xaml" />
<None Remove="Controls\AppSettingsControl.xaml" />
<None Remove="Controls\DemoWindowGrid.xaml" />
<None Remove="Controls\ExtendedSlider.xaml" />
<None Remove="Controls\LyricsSearchControl.xaml" />
<None Remove="Controls\LyricsSettingsControl.xaml" />
<None Remove="Controls\LyricsWindowSettingsControl.xaml" />
<None Remove="Controls\LyricsWindowSwitchControl.xaml" />
<None Remove="Controls\MediaSettingsControl.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\LyricsSearchWindow.xaml" />
<None Remove="Views\LyricsWindowSwitchWindow.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
@@ -58,6 +64,7 @@
<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="csharp-pinyin" Version="1.0.1" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
@@ -67,9 +74,11 @@
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="RomajiConverter.Core" Version="1.0.9" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
@@ -77,7 +86,6 @@
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.1.6" />
@@ -111,10 +119,13 @@
<Content Update="Assets\Chrome.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Discord.png">
<Content Update="Assets\Edge.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Edge.png">
<Content Update="Assets\effect.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Empty.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyBox.png">
@@ -138,6 +149,9 @@
<Content Update="Assets\Leaf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Listen1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@@ -150,16 +164,19 @@
<Content Update="Assets\MediaPlayerWindows11.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MoeKoeMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MusicBee.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<Content Update="Assets\Page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQ.png">
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQMusic.png">
@@ -168,19 +185,49 @@
<Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\SaltPlayerForWindows.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Spotify.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Telegram.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Wiki82.profile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Views\LyricsWindowSwitchWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsWindowSwitchControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\DemoWindowGrid.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsWindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\LyricsSearchWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsSearchControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ShortcutTextBox.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -191,11 +238,6 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AllLyricsSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -246,6 +288,11 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<PRIResource Update="Strings\en-US\Resources.resw">
<Generator></Generator>
</PRIResource>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class AppleMusic
{
public const string MediaUserTokenKey = "AppleMusicMediaUserToken";
}
}

View File

@@ -8,6 +8,6 @@ namespace BetterLyrics.WinUI3.Constants
{
public static class LXMusic
{
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration";
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration,picUrl";
}
}

View File

@@ -8,7 +8,8 @@ namespace BetterLyrics.WinUI3.Constants
{
public static class Link
{
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string GitHubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string FAQUrl = $"{GitHubUrl}/blob/dev/FAQ/FAQ.md";
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";

View File

@@ -17,6 +17,7 @@ namespace BetterLyrics.WinUI3.Constants
public const string PotPlayer = "PotPlayerMini64.exe";
public const string Spotify = "Spotify.exe";
public const string AppleMusic = "AppleInc.AppleMusicWin_nzyj5cx40ttqa!App";
public const string AppleMusicAlternative = "AppleMusic.exe";
public const string NetEaseCloudMusic = "cloudmusic.exe";
public const string KugouMusic = "kugou";
public const string QQMusic = "QQMusic.exe";
@@ -25,5 +26,9 @@ namespace BetterLyrics.WinUI3.Constants
public const string Edge = "MSEdge";
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
public const string SaltPlayerForWindows = "Sakawish.SaltPlayerforWindows_q65q631pyh094!SaltPlayerforWindows";
public const string MoeKoeMusic = "cn.MoeKoe.Music";
public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music";
public const string Listen1 = "com.listen1.listen1";
}
}

View File

@@ -16,6 +16,7 @@ namespace BetterLyrics.WinUI3.Constants
public const string PotPlayer = "PotPlayer";
public const string Spotify = "Spotify";
public const string AppleMusic = "Apple Music";
public const string AppleMusicAlternative = "Apple Music";
public const string NetEaseCloudMusic = "网易云音乐";
public const string KugouMusic = "酷狗音乐";
public const string QQMusic = "QQ 音乐";
@@ -24,5 +25,8 @@ namespace BetterLyrics.WinUI3.Constants
public const string Edge = "Microsoft Edge";
public const string BetterLyrics = "BetterLyrics";
public const string BetterLyricsDebug = "BetterLyrics (Debug)";
public const string SaltPlayerForWindows = "Salt Player for Windows";
public const string MoeKoeMusic = "MoeKoe Music";
public const string Listen1 = "Listen 1";
}
}

View File

@@ -17,6 +17,27 @@
<TextBlock x:Uid="SettingsPageAlbumArt" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageAlbumArtSize"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE744;}"
IsExpanded="True">
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageAutoSize">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="800"
Minimum="10"
ResetButtonVisibility="Collapsed"
Unit="px"
Value="{x:Bind AlbumArtLayoutSettings.AlbumArtSize, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}">
<local:ExtendedSlider
Default="12"
@@ -24,7 +45,7 @@
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.AlbumArtLayoutSettings.CoverImageRadius, Mode=TwoWay}" />
Value="{x:Bind AlbumArtLayoutSettings.CoverImageRadius, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF5EF;}">
@@ -33,22 +54,43 @@
Frequency="1"
Maximum="64"
Minimum="0"
Value="{x:Bind ViewModel.AppSettings.AlbumArtLayoutSettings.CoverImageShadowAmount, Mode=TwoWay}" />
Value="{x:Bind AlbumArtLayoutSettings.CoverImageShadowAmount, Mode=TwoWay}" />
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageSongInfo" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageSongInfoAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBox SelectedIndex="{x:Bind AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageSongInfoLeft" />
<ComboBoxItem x:Uid="SettingsPageSongInfoCenter" />
<ComboBoxItem x:Uid="SettingsPageSongInfoRight" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E9;}">
<local:ExtendedSlider
Default="18"
Frequency="2"
Maximum="72"
Minimum="8"
Value="{x:Bind AlbumArtLayoutSettings.SongInfoFontSize, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageShowTitle"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEF3D;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.ShowTitle, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageShowArtists">
<ToggleSwitch IsEnabled="{x:Bind AlbumArtLayoutSettings.ShowTitle, Mode=OneWay}" IsOn="{x:Bind AlbumArtLayoutSettings.ShowArtists, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -1,3 +1,4 @@
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -22,11 +23,18 @@ namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class AlbumArtLayoutSettingsControl : UserControl
{
public AlbumArtLayoutSettingsControlViewModel ViewModel => (AlbumArtLayoutSettingsControlViewModel)DataContext;
public static readonly DependencyProperty AlbumArtLayoutSettingsProperty =
DependencyProperty.Register(nameof(AlbumArtLayoutSettings), typeof(AlbumArtLayoutSettings), typeof(AlbumArtLayoutSettingsControl), new PropertyMetadata(default));
public AlbumArtLayoutSettings AlbumArtLayoutSettings
{
get => (AlbumArtLayoutSettings)GetValue(AlbumArtLayoutSettingsProperty);
set => SetValue(AlbumArtLayoutSettingsProperty, value);
}
public AlbumArtLayoutSettingsControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<AlbumArtLayoutSettingsControlViewModel>();
}
}
}

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AllLyricsSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
mc:Ignorable="d">
<Grid>
<controls:SwitchPresenter Margin="0,72,0,0" Value="{Binding SelectedItem.Tag, ElementName=LyricsSettingsSegmentedControl}">
<controls:Case Value="Standard">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.StandardLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.StandardLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<controls:Case Value="Desktop">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.DesktopLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.DesktopLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<controls:Case Value="Dock">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.DockLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.DockLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<controls:Case Value="PictureInPicture">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.PictureInPictureLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.PictureInPictureLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
</controls:SwitchPresenter>
<controls:Segmented
x:Name="LyricsSettingsSegmentedControl"
Margin="36,36,36,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SelectedIndex="{x:Bind ViewModel.SelectedTabIndex, Mode=OneWay}">
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlStandard" Tag="Standard" />
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlDock" Tag="Dock" />
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlDesktop" Tag="Desktop" />
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlPictureInPicture" Tag="PictureInPicture" />
</controls:Segmented>
</Grid>
</UserControl>

View File

@@ -50,126 +50,28 @@
Unloaded="AutoStartupToggleSwitch_Unloaded" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE736;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.GeneralSettings.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartPIPLyrics" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE967;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreFullscreenWindow, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.HideWindowWhenNotPlaying, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageGlobalDrag" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C2;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsDragEverywhereEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageExitOnLyricsWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7E8;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ExitOnLyricsWindowClosed, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Standard mode -->
<TextBlock x:Uid="SettingsPageAppStandard" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.StandardModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<controls:SettingsCard x:Uid="SettingsPageListenNewSession" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF270;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Desktop mode -->
<TextBlock x:Uid="SettingsPageAppDesktop" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageToggleHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.DesktopModeSettings.ToggleShortcut, Mode=TwoWay}" />
<controls:SettingsCard x:Uid="SettingsPageShowHideHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ShowOrHideLyricsWindowShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DesktopModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<controls:SettingsCard x:Uid="SettingsPageBorderlessHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.BorderlessShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoLock" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE755;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.DesktopModeSettings.AutoLockOnDesktopMode, Mode=TwoWay}" />
<controls:SettingsCard x:Uid="SettingsPageClickThroughHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ClickThroughShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLockHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.DesktopModeSettings.LockShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Dock mode -->
<TextBlock x:Uid="SettingsPageAppDock" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageToggleHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.DockModeSettings.ToggleShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DockModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDockMonitor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7F4;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.AppSettings.DockModeSettings.DockMonitorDeviceName, Mode=TwoWay}" />
<Button
Command="{x:Bind ViewModel.RefreshMonitorDeviceNamesCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDockWindowHeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED5E;}">
<local:ExtendedSlider
Default="64"
Frequency="1"
Maximum="200"
Minimum="64"
Unit="px"
Value="{x:Bind ViewModel.AppSettings.DockModeSettings.DockWindowHeight, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDockPlacement" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DockModeSettings.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</controls:SettingsCard>
<!-- Picture in picture mode -->
<TextBlock x:Uid="SettingsPageAppPictureInPicture" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageToggleHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.PictureInPictureModeSettings.ToggleShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDisplayTypeSwitcher" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF246;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.PictureInPictureModeSettings.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<controls:SettingsCard x:Uid="SettingsPageLyricsWindowSwitchHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.LyricsWindowSwitchShortcut, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Playback shortcut -->

View File

@@ -1,5 +1,8 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.DemoWindowGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Width="{x:Bind LyricsWindowStatus.DemoMonitorBounds.Width, Mode=OneWay}"
Height="{x:Bind LyricsWindowStatus.DemoMonitorBounds.Height, Mode=OneWay}"
Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}"
CornerRadius="4">
<Grid
Width="{x:Bind LyricsWindowStatus.DemoWindowBounds.Width, Mode=OneWay}"
Height="{x:Bind LyricsWindowStatus.DemoWindowBounds.Height, Mode=OneWay}"
Margin="{x:Bind LyricsWindowStatus.DemoWindowBounds, Converter={StaticResource RectToMarginConverter}, Mode=OneWay}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="{ThemeResource AccentAcrylicBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource AccentFillColorDefaultBrush}"
CornerRadius="4">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsWindowStatus.IsBorderless, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="BorderThickness" Value="0" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsWindowStatus.IsBorderless, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="BorderThickness" Value="1" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<!-- Is default -->
<Grid
Margin="4"
Padding="6,3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}"
CornerRadius="4"
Opacity="0.7"
Visibility="{x:Bind LyricsWindowStatus.IsDefault, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<TextBlock
x:Uid="DemoWindowControlDefault"
FontSize="12"
TextWrapping="Wrap" />
</Grid>
<!-- Is always on top -->
<Grid
Margin="4"
Padding="6,3"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}"
CornerRadius="4"
Opacity="0.7"
Visibility="{x:Bind LyricsWindowStatus.IsAlwaysOnTop, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="12"
Glyph="&#xE718;" />
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="12"
Glyph="&#xE841;"
Visibility="{x:Bind LyricsWindowStatus.IsAlwaysOnTopPolling, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</Grid>
<!-- Moniter name -->
<Grid
Margin="4"
Padding="6,3"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}"
CornerRadius="4"
Opacity="0.7">
<TextBlock
FontSize="12"
Text="{x:Bind LyricsWindowStatus.MonitorDeviceName, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
<!-- Config name -->
<Grid
Padding="6,3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="4"
Opacity="0.7">
<TextBlock
FontWeight="ExtraBlack"
Text="{x:Bind LyricsWindowStatus.Name, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,37 @@
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 BetterLyrics.WinUI3.Models;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class DemoWindowGrid : UserControl
{
public DemoWindowGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(DemoWindowGrid), new PropertyMetadata(default));
public LyricsWindowStatus LyricsWindowStatus
{
get => (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty);
set => SetValue(LyricsWindowStatusProperty, value);
}
}

View File

@@ -4,54 +4,58 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="6,0,2,0"
VerticalAlignment="Center"
Text="{x:Bind Value, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind Unit, Mode=OneWay}" />
<StackPanel>
<Slider
Maximum="{x:Bind Maximum, Mode=OneWay}"
Minimum="{x:Bind Minimum, Mode=OneWay}"
SnapsTo="Ticks"
StepFrequency="{x:Bind Frequency, Mode=OneWay}"
TickFrequency="{x:Bind Frequency, Mode=OneWay}"
TickPlacement="Outside"
TickPlacement="None"
Value="{x:Bind Value, Mode=TwoWay}" />
<Button
Margin="3,0,0,0"
VerticalAlignment="Center"
Click="ResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ResetButtonVisibility, Mode=OneWay}" />
<Button
x:Name="SubtractButton"
Margin="3,0,0,0"
VerticalAlignment="Center"
Click="SubtractButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE738;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
x:Name="AddButton"
Margin="3,0,0,0"
VerticalAlignment="Center"
Click="AddButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE710;}"
Style="{StaticResource GhostButtonStyle}" />
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
VerticalAlignment="Center"
Click="ResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ResetButtonVisibility, Mode=OneWay}" />
<Button
x:Name="SubtractButton"
VerticalAlignment="Center"
Click="SubtractButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE738;}"
Style="{StaticResource GhostButtonStyle}" />
<StackPanel
Margin="3,-2,3,0"
Orientation="Horizontal"
Spacing="2">
<TextBlock VerticalAlignment="Center" Text="{x:Bind Value, Mode=OneWay}" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind Unit, Mode=OneWay}" />
</StackPanel>
<Button
x:Name="AddButton"
VerticalAlignment="Center"
Click="AddButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE710;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -18,60 +18,112 @@
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE790;}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind LyricsBackgroundSettings.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageFollowSystem" />
<ComboBoxItem x:Uid="SettingsPageLight" />
<ComboBoxItem x:Uid="SettingsPageDark" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsPureColorBgOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB42;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.PureColorOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPagePureLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEB42;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsPureColorOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsPureColorOverlayEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB42;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsPureColorOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.PureColorOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundSpeed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC49;}">
<uc:ExtendedSlider
Default="50"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageAlbumArtLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE93C;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="50"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageBlurAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="0"
Frequency="1"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsExpander
x:Uid="SettingsPageFluidLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEDA8;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsFluidOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsFluidOverlayEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsFluidOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.FluidOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsExpander
x:Uid="SettingsPageSpectrumLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF61F;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsSpectrumOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsSpectrumOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE80A;}">
<uc:ExtendedSlider
Default="0"
Frequency="1"
Maximum="10"
Minimum="0"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -1,3 +1,4 @@
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -22,12 +23,18 @@ namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsBackgroundSettingsControl : UserControl
{
public LyricsBackgroundSettingsControlViewModel ViewModel => (LyricsBackgroundSettingsControlViewModel)DataContext;
public static readonly DependencyProperty LyricsBackgroundSettingsProperty =
DependencyProperty.Register(nameof(LyricsBackgroundSettings), typeof(LyricsBackgroundSettings), typeof(LyricsBackgroundSettingsControl), new PropertyMetadata(default));
public LyricsBackgroundSettings LyricsBackgroundSettings
{
get => (LyricsBackgroundSettings)GetValue(LyricsBackgroundSettingsProperty);
set => SetValue(LyricsBackgroundSettingsProperty, value);
}
public LyricsBackgroundSettingsControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<LyricsBackgroundSettingsControlViewModel>();
}
}
}

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.LyricsSearchControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid Padding="16" RowSpacing="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsExpander x:Uid="LyricsSearchControlSongInfoMapping" IsExpanded="True">
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="LyricsSearchControlTitle" Description="{x:Bind ViewModel.MappedSongSearchQuery.OriginalTitle, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedTitle, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedTitleCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsSearchControlArtist" Description="{x:Bind ViewModel.MappedSongSearchQuery.OriginalArtist, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard>
<CheckBox x:Uid="LyricsSearchControlMarkAsPureMusic" IsChecked="{x:Bind ViewModel.MappedSongSearchQuery.IsMarkedAsPureMusic, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="LyricsSearchControlTargetSearchProvider">
<Button
x:Uid="LyricsSearchControlSearch"
Command="{x:Bind ViewModel.SearchCommand}"
Style="{StaticResource AccentButtonStyle}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
<Grid Grid.Column="1">
<ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ListViewItem IsEnabled="{Binding IsFound}">
<Grid Opacity="{Binding IsFound, Converter={StaticResource BoolToOpacityConverter}}">
<RelativePanel Padding="0,6">
<TextBlock
x:Name="SearchedTitle"
RelativePanel.AlignLeftWithPanel="True"
Text="{Binding Title}"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedArtists"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="SearchedTitle"
Text="{Binding Artist}"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedProvider"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Text="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
</RelativePanel>
<TextBlock
x:Uid="LyricsSearchControlNotFound"
VerticalAlignment="Center"
Text="Not found"
Visibility="{Binding IsFound, Converter={StaticResource BoolNegationToVisibilityConverter}}" />
</Grid>
</ListViewItem>
</DataTemplate>
</ListView.ItemTemplate>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ListView>
<StackPanel
Padding="0,36"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<Image MaxWidth="100" Source="/Assets/Empty.png" />
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
<ProgressBar
VerticalAlignment="Top"
IsIndeterminate="True"
ShowError="False"
ShowPaused="False"
Visibility="{x:Bind ViewModel.IsSearching, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</Grid>
<Grid Grid.Column="2">
<ListView ItemsSource="{x:Bind ViewModel.LyricsData.LyricsLines, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsLine, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
ComparisonCondition="Equal"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{Binding StartMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock
Margin="1,0"
Foreground="{ThemeResource SystemFillColorNeutralBrush}"
Text="-" />
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{Binding EndMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock Margin="6,0" Text="{Binding OriginalText}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel
Padding="0,36"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<Image MaxWidth="100" Source="/Assets/Page.png" />
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
ComparisonCondition="Equal"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="1">
<RelativePanel>
<TextBlock
x:Uid="LyricsSearchControlHelp"
Margin="0,0,24,0"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.AlignVerticalCenterWithPanel="True"
RelativePanel.LeftOf="Reset"
TextWrapping="Wrap" />
<Button
x:Name="Reset"
x:Uid="LyricsSearchControlReset"
Margin="0,0,6,0"
Command="{x:Bind ViewModel.ResetCommand}"
RelativePanel.AlignVerticalCenterWithPanel="True"
RelativePanel.LeftOf="SaveChanges" />
<Button
x:Name="SaveChanges"
x:Uid="LyricsSearchControlSaveChanges"
Command="{x:Bind ViewModel.SaveCommand}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource AccentButtonStyle}" />
</RelativePanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -1,3 +1,4 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -12,6 +13,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Ude.Core;
using Windows.Foundation;
using Windows.Foundation.Collections;
@@ -20,13 +22,14 @@ using Windows.Foundation.Collections;
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class AllLyricsSettingsControl : UserControl
public sealed partial class LyricsSearchControl : UserControl
{
public AllLyricsSettingsControlViewModel ViewModel => (AllLyricsSettingsControlViewModel)DataContext;
public AllLyricsSettingsControl()
public LyricsSearchControlViewModel ViewModel => (LyricsSearchControlViewModel)DataContext;
public LyricsSearchControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<AllLyricsSettingsControlViewModel>();
DataContext = Ioc.Default.GetRequiredService<LyricsSearchControlViewModel>();
}
}
}

View File

@@ -175,14 +175,25 @@
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E9;}">
<local:ExtendedSlider
Frequency="2"
Maximum="96"
Minimum="12"
ResetButtonVisibility="Collapsed"
Value="{x:Bind LyricsStyleSettings.LyricsFontSize, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageLyricsFontSize"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8E9;}"
IsExpanded="True">
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageDynamicLyricsFontSize">
<ToggleSwitch IsOn="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard IsEnabled="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="96"
Minimum="12"
ResetButtonVisibility="Collapsed"
Value="{x:Bind LyricsStyleSettings.LyricsFontSize, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}">
<local:ExtendedSlider

View File

@@ -0,0 +1,388 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.LyricsWindowSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock
x:Uid="SettingsPageRecordedWindowStatus"
RelativePanel.AlignLeftWithPanel="True"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Button x:Uid="SettingsPageCreateFromCurrent" Command="{x:Bind ViewModel.CopyLyricsWindowStatusCommand}" />
<Button
x:Uid="SettingsPageCreateFromTemplates"
VerticalAlignment="Center"
RelativePanel.AlignRightWithPanel="True">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="SettingsPageStandardMode" Command="{x:Bind ViewModel.CreateStandardLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageDesktopMode" Command="{x:Bind ViewModel.CreateTransparentLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageDockedMode" Command="{x:Bind ViewModel.CreateDockedLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageFullscreenMode" Command="{x:Bind ViewModel.CreateFullLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageNarrowMode" Command="{x:Bind ViewModel.CreateNarrowLyricsWindowStatusCommand}" />
</MenuFlyout>
</Button.Flyout>
</Button>
<StackPanel
Padding="24,0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="4"
Spacing="16"
Visibility="{Binding ElementName=LyricsWindowManagerExpander, Path=IsExpanded, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<ListView
x:Name="WindowStatusListView"
Padding="0,12"
CornerRadius="4"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel
Padding="0,10"
RightTapped="StackPanel_RightTapped"
Spacing="6">
<StackPanel.ContextFlyout>
<MenuBarItemFlyout>
<MenuFlyoutItem
x:Uid="LyricsWindowSettingsControlSetDefault"
Click="SetDefaultMenuFlyoutItem_Click"
IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
<MenuFlyoutItem
x:Uid="SettingsPageDelete"
Click="DeleteMenuFlyoutItem_Click"
IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
</MenuBarItemFlyout>
</StackPanel.ContextFlyout>
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
<Grid Grid.Column="1">
<controls:SwitchPresenter Margin="0,100,0,0" Value="{x:Bind ViewModel.ListViewSelectedItemTag, Mode=OneWay}">
<controls:SwitchPresenter.ContentTransitions>
<TransitionCollection>
<PopupThemeTransition />
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<!-- General -->
<controls:Case Value="General">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageConfigName" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8AC;}">
<StackPanel
Margin="0,6,0,0"
Orientation="Horizontal"
Spacing="6">
<TextBox Text="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.Name, Mode=TwoWay}" TextWrapping="Wrap" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageDisplayTypeSwitcher"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLayoutOrientation">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationHorizontal" />
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationVertical" />
</ComboBox>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsExpander
x:Uid="SettingsPageAdaptEnvColor"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE88F;}"
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageEnvColorSample" Header="Environment color sample mode">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
</ComboBox>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageDockMonitor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7F4;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorDeviceName, Mode=TwoWay}" />
<Button
Command="{x:Bind ViewModel.RefreshMonitorDeviceNamesCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageWindowBounds"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF16B;}"
IsExpanded="True">
<controls:SettingsExpander.Items>
<controls:SettingsCard Header="X">
<uc:ExtendedSlider
Frequency="1"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Right, Mode=OneWay}"
Minimum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Left, Mode=OneWay}"
ResetButtonVisibility="Collapsed"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowX, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard Header="Y">
<uc:ExtendedSlider
Frequency="1"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom, Mode=OneWay}"
Minimum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Top, Mode=OneWay}"
ResetButtonVisibility="Collapsed"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowY, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageWidth">
<uc:ExtendedSlider
Frequency="1"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Width, Mode=OneWay}"
Minimum="64"
ResetButtonVisibility="Collapsed"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowWidth, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageHeight">
<uc:ExtendedSlider
Frequency="1"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
ResetButtonVisibility="Collapsed"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowHeight, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsExpander
x:Uid="SettingsPageAOT"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE718;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTop, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageForceAlwaysOnTop" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTop, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTopPolling, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsExpander
x:Uid="SettingsPageWorkArea"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE78B;}"
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageWorkAreaHeight" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<uc:ExtendedSlider
Default="64"
Frequency="1"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.DockHeight, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDockPlacement" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</controls:Case>
<!-- Album art area style -->
<controls:Case Value="AlbumArtStyle">
<uc:AlbumArtLayoutSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics style and effect -->
<controls:Case Value="Lyrics">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics background -->
<controls:Case Value="LyricsBackground">
<uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" />
</controls:Case>
<!-- Advanced -->
<controls:Case Value="Advanced">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageShowInSwitchers" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C4;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsShownInSwitchers, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageClickThrough" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C9;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsClickThrough, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageBorderless" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B2;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsBorderless, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB41;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaTop" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaWhole" />
</ComboBox>
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</controls:Case>
</controls:SwitchPresenter>
<Grid Padding="36,0" Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<ListView
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Items>
<ListViewItem Tag="General">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xECAA;" />
<TextBlock x:Uid="AppSettingsControlGeneral" VerticalAlignment="Center" />
</StackPanel>
</ListViewItem>
<ListViewItem Tag="AlbumArtStyle">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE93C;" />
<TextBlock x:Uid="SettingsPageAlbumStyle" VerticalAlignment="Center" />
</StackPanel>
</ListViewItem>
<ListViewItem Tag="Lyrics">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xEDC6;" />
<TextBlock x:Uid="SettingsPageLyrics" VerticalAlignment="Center" />
</StackPanel>
</ListViewItem>
<ListViewItem Tag="LyricsBackground">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xF5EF;" />
<TextBlock x:Uid="SettingsPageBackgroundOverlay" VerticalAlignment="Center" />
</StackPanel>
</ListViewItem>
<ListViewItem Tag="Advanced">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xEC7A;" />
<TextBlock x:Uid="SettingsPageAdvanced" VerticalAlignment="Center" />
</StackPanel>
</ListViewItem>
</ListView.Items>
</ListView>
</StackPanel>
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,84 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
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 NTextCat.Commons;
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 static Vanara.PInvoke.ComCtl32;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsWindowSettingsControl : UserControl
{
public LyricsWindowSettingsControlViewModel ViewModel => (LyricsWindowSettingsControlViewModel)DataContext;
private ISettingsService _settingsService;
public LyricsWindowSettingsControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<LyricsWindowSettingsControlViewModel>();
_settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
}
private void DeleteMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuFlyoutItem menuFlyoutItem)
{
var data = menuFlyoutItem.DataContext as LyricsWindowStatus;
if (data != null)
{
ViewModel.AppSettings.WindowBoundsRecords.Remove(data);
}
}
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel?.ListViewSelectedItemTag = ((sender as ListView)!.SelectedItem as ListViewItem)!.Tag;
}
private void SetDefaultMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuFlyoutItem menuFlyoutItem)
{
var data = menuFlyoutItem.DataContext as LyricsWindowStatus;
if (data != null)
{
ViewModel.AppSettings.WindowBoundsRecords.ForEach(x => x.IsDefault = false);
data.IsDefault = true;
}
}
}
private void StackPanel_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
if (sender is StackPanel stackPanel)
{
if (stackPanel.DataContext is MenuBarItemFlyout menuBarItemFlyout)
{
menuBarItemFlyout.ShowAt(stackPanel);
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.LyricsWindowSwitchControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
CornerRadius="12">
<Button
Margin="12"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="Button_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE653;}"
Style="{StaticResource GhostButtonStyle}" />
<ListView
Margin="48"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid
Margin="0,10"
Padding="5"
AllowFocusOnInteraction="True"
CornerRadius="4"
Tapped="Grid_Tapped">
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>

View File

@@ -0,0 +1,45 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsWindowSwitchControl : UserControl
{
public LyricsWindowSwitchControlViewModel ViewModel => (LyricsWindowSwitchControlViewModel)DataContext;
public LyricsWindowSwitchControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<LyricsWindowSwitchControlViewModel>();
}
private void Grid_Tapped(object sender, TappedRoutedEventArgs e)
{
WindowHelper.HideWindow<LyricsWindowSwitchWindow>();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
WindowHelper.HideWindow<LyricsWindowSwitchWindow>();
}
}
}

View File

@@ -31,16 +31,20 @@
</controls:SettingsCard>
<!-- 时间轴相关配置 -->
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsTimelineThresholdReset"
Frequency="100"
Maximum="1000"
Minimum="0"
ResetButtonVisibility="Collapsed"
Unit="ms"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="SettingsPageLyricsTimeline" IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsTimelineSyncEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold" IsEnabled="{x:Bind ViewModel.SelectedMediaSourceProvider.IsTimelineSyncEnabled, Mode=OneWay}">
<local:ExtendedSlider
Frequency="100"
Maximum="1000"
Minimum="0"
ResetButtonVisibility="Collapsed"
Unit="ms"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsExpander x:Uid="MainPagePositionOffsetSlider" IsExpanded="True">
<local:ExtendedSlider
x:Uid="SettingsPagePositionOffsetReset"
@@ -118,8 +122,15 @@
</StackPanel>
</Grid>
</ScrollViewer>
<!-- 播放源列表 -->
<ListView
x:Name="MediaSourceProvidersListView"
VerticalAlignment="Top"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="MediaSourceProvidersListView_DragItemsCompleted"
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled"
@@ -134,6 +145,10 @@
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="Segoe UI Symbol"
FontSize="12"
Glyph="&#x283F;" />
<ImageIcon Height="16" Source="{Binding Provider, Converter={StaticResource MediaSourceProviderToLogoUriConverter}, Mode=OneWay}" />
<TextBlock
MaxWidth="200"
@@ -158,6 +173,7 @@
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<StackPanel
Grid.Column="0"
HorizontalAlignment="Center"
@@ -187,6 +203,132 @@
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<!-- Provider info -->
<TextBlock x:Uid="SettingsPageRealtimeStatus" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="LyricsPageLyricsProviderPrefix">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.LyricsSearchProvider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsPageTranslationProviderPrefix">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
</controls:SettingsCard>
<!-- Lyrics translation -->
<TextBlock x:Uid="SettingsPageTranslation" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="LyricsPageTranslationEnabled"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE774;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.SelectedTargetLanguageIndex, Mode=TwoWay}">
<ComboBoxItem Content="العربية" Tag="ar" />
<ComboBoxItem Content="Azərbaycan dili" Tag="az" />
<ComboBoxItem Content="Български" Tag="bg" />
<ComboBoxItem Content="বাংলা" Tag="bn" />
<ComboBoxItem Content="Català" Tag="ca" />
<ComboBoxItem Content="Čeština" Tag="cs" />
<ComboBoxItem Content="Dansk" Tag="da" />
<ComboBoxItem Content="Deutsch" Tag="de" />
<ComboBoxItem Content="Ελληνικά" Tag="el" />
<ComboBoxItem Content="English" Tag="en" />
<ComboBoxItem Content="Esperanto" Tag="eo" />
<ComboBoxItem Content="Español" Tag="es" />
<ComboBoxItem Content="Eesti" Tag="et" />
<ComboBoxItem Content="Euskara" Tag="eu" />
<ComboBoxItem Content="فارسی" Tag="fa" />
<ComboBoxItem Content="Suomi" Tag="fi" />
<ComboBoxItem Content="Français" Tag="fr" />
<ComboBoxItem Content="Gaeilge" Tag="ga" />
<ComboBoxItem Content="Galego" Tag="gl" />
<ComboBoxItem Content="עברית" Tag="he" />
<ComboBoxItem Content="हिन्दी" Tag="hi" />
<ComboBoxItem Content="Magyar" Tag="hu" />
<ComboBoxItem Content="Bahasa Indonesia" Tag="id" />
<ComboBoxItem Content="Italiano" Tag="it" />
<ComboBoxItem Content="日本語" Tag="ja" />
<ComboBoxItem Content="한국어" Tag="ko" />
<ComboBoxItem Content="Кыргызча" Tag="ky" />
<ComboBoxItem Content="Lietuvių" Tag="lt" />
<ComboBoxItem Content="Latviešu" Tag="lv" />
<ComboBoxItem Content="Bahasa Melayu" Tag="ms" />
<ComboBoxItem Content="Norsk bokmål" Tag="nb" />
<ComboBoxItem Content="Nederlands" Tag="nl" />
<ComboBoxItem Content="Português (Brasil)" Tag="pt-BR" />
<ComboBoxItem Content="Polski" Tag="pl" />
<ComboBoxItem Content="Português" Tag="pt" />
<ComboBoxItem Content="Română" Tag="ro" />
<ComboBoxItem Content="Русский" Tag="ru" />
<ComboBoxItem Content="Slovenčina" Tag="sk" />
<ComboBoxItem Content="Slovenščina" Tag="sl" />
<ComboBoxItem Content="Shqip" Tag="sq" />
<ComboBoxItem Content="Српски" Tag="sr" />
<ComboBoxItem Content="Svenska" Tag="sv" />
<ComboBoxItem Content="ไทย" Tag="th" />
<ComboBoxItem Content="Filipino" Tag="tl" />
<ComboBoxItem Content="Türkçe" Tag="tr" />
<ComboBoxItem Content="Українська" Tag="uk" />
<ComboBoxItem Content="اردو" Tag="ur" />
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
<ComboBoxItem Content="中文" Tag="zh" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageTranslationConfig" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
<controls:SettingsCard.Description>
<HyperlinkButton Margin="0,6,0,0" NavigateUri="https://github.com/LibreTranslate/LibreTranslate">
<TextBlock
x:Uid="SettingsPageTranslationInfoLink"
FontSize="14"
TextWrapping="Wrap" />
</HyperlinkButton>
</controls:SettingsCard.Description>
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBox
x:Name="LibreTranslateServerTextBox"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
PlaceholderText="http://localhost:5000"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}" />
<Button
x:Uid="SettingsPageServerTestButton"
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsPageTranslationOnly" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.ShowTranslationOnly, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- Lyrics phonetic -->
<controls:SettingsExpander
x:Uid="SettingsPageChinese"
IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsChineseRomanizationEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard>
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.TranslationSettings.ChineseRomanization, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}">
<ComboBoxItem x:Uid="SettingsPagePinyin" />
<ComboBoxItem x:Uid="SettingsPageJyutping" />
</ComboBox>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageJapanese" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- 中文简体繁体偏好 -->
<controls:SettingsCard x:Uid="SettingsPageChinesePreference">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTraditionalChineseEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Last.fm -->
<TextBlock x:Uid="SettingsPageLastFM" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
@@ -218,85 +360,7 @@
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- Lyrics translation -->
<TextBlock x:Uid="SettingsPageTranslation" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="LyricsPageTranslationEnabled"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE774;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.TranslationSettings.SelectedTargetLanguageIndex, Mode=TwoWay}">
<ComboBoxItem Content="العربية" Tag="ar" />
<ComboBoxItem Content="Azərbaycan dili" Tag="az" />
<ComboBoxItem Content="简体中文" Tag="zh-Hans" />
<ComboBoxItem Content="繁體中文" Tag="zh-Hant" />
<ComboBoxItem Content="Čeština" Tag="cs" />
<ComboBoxItem Content="Dansk" Tag="da" />
<ComboBoxItem Content="Nederlands" Tag="nl" />
<ComboBoxItem Content="English" Tag="en" />
<ComboBoxItem Content="Esperanto" Tag="eo" />
<ComboBoxItem Content="Suomi" Tag="fi" />
<ComboBoxItem Content="Français" Tag="fr" />
<ComboBoxItem Content="Deutsch" Tag="de" />
<ComboBoxItem Content="Ελληνικά" Tag="el" />
<ComboBoxItem Content="עברית" Tag="he" />
<ComboBoxItem Content="हिन्दी" Tag="hi" />
<ComboBoxItem Content="Magyar" Tag="hu" />
<ComboBoxItem Content="Bahasa Indonesia" Tag="id" />
<ComboBoxItem Content="Gaeilge" Tag="ga" />
<ComboBoxItem Content="Italiano" Tag="it" />
<ComboBoxItem Content="日本語" Tag="ja" />
<ComboBoxItem Content="한국어" Tag="ko" />
<ComboBoxItem Content="فارسی" Tag="fa" />
<ComboBoxItem Content="Polski" Tag="pl" />
<ComboBoxItem Content="Português" Tag="pt" />
<ComboBoxItem Content="Русский" Tag="ru" />
<ComboBoxItem Content="Slovenčina" Tag="sk" />
<ComboBoxItem Content="Español" Tag="es" />
<ComboBoxItem Content="Svenska" Tag="sv" />
<ComboBoxItem Content="Türkçe" Tag="tr" />
<ComboBoxItem Content="Українська" Tag="uk" />
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageTranslationConfig">
<controls:SettingsCard.Description>
<HyperlinkButton Margin="0,6,0,0" NavigateUri="https://github.com/LibreTranslate/LibreTranslate">
<TextBlock
x:Uid="SettingsPageTranslationInfoLink"
FontSize="14"
TextWrapping="Wrap" />
</HyperlinkButton>
</controls:SettingsCard.Description>
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBox
x:Name="LibreTranslateServerTextBox"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
PlaceholderText="http://localhost:5000"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}" />
<Button
x:Uid="SettingsPageServerTestButton"
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsPageTranslationOnly" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.ShowTranslationOnly, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="LyricsPageLyricsProviderPrefix">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.LyricsSearchProvider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsPageTranslationProviderPrefix">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
</controls:SettingsCard>
<!-- LX music server -->
<TextBlock x:Uid="SettingsPageLXMusicServer" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard>
@@ -311,6 +375,28 @@
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
<!-- Apple Music token -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="media-user-token (for Apple Muisc)" />
<controls:SettingsCard
Background="{ThemeResource SystemFillColorCautionBackgroundBrush}"
Foreground="{ThemeResource SystemFillColorCautionBrush}"
Header="Use at your own risk">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBox
MaxWidth="250"
PlaceholderText="media-user-token"
Text="{x:Bind ViewModel.AppleMusicMediaUserToken, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Command="{x:Bind ViewModel.SaveAppleMusicMediaUserTokenCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE8FB;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -41,5 +41,11 @@ namespace BetterLyrics.WinUI3.Controls
// <20><> LyricsSearchProvidersInfo <20><><EFBFBD><EFBFBD> CollectionChanged <20>¼<EFBFBD>
ViewModel.SelectedMediaSourceProvider?.LyricsSearchProvidersInfo?.Refresh();
}
private void MediaSourceProvidersListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
// <20><> MediaSourceProvidersInfo <20><><EFBFBD><EFBFBD> CollectionChanged <20>¼<EFBFBD>
ViewModel.AppSettings.MediaSourceProvidersInfo?.Refresh();
}
}
}

View File

@@ -50,12 +50,6 @@
Command="{x:Bind ViewModel.ResetWindowPositionCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE923;}" />
<MenuFlyoutItem
x:Uid="SystemTrayUnlock"
Command="{x:Bind ViewModel.UnlockWindowCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE785;}"
Visibility="{x:Bind ViewModel.IsLyricsWindowLocked, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="SystemTrayRestart"

View File

@@ -0,0 +1,23 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class BoolNegationToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return boolValue ? Visibility.Collapsed : Visibility.Visible;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class BoolToOpacityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return boolValue ? 1.0 : 0.3;
}
return 1.0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -19,6 +19,7 @@ namespace BetterLyrics.WinUI3.Converter
LyricsSearchProvider.Netease => "网易云音乐",
LyricsSearchProvider.Kugou => "酷狗音乐",
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
LyricsSearchProvider.AppleMusic => "Apple Music",
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),

View File

@@ -33,6 +33,10 @@ namespace BetterLyrics.WinUI3.Converter
PlayerID.Edge => PlayerName.Edge,
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
PlayerID.SaltPlayerForWindows => PlayerName.SaltPlayerForWindows,
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
PlayerID.Listen1 => PlayerName.Listen1,
_ => provider,
};
}

View File

@@ -2,10 +2,6 @@
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
@@ -19,6 +15,7 @@ namespace BetterLyrics.WinUI3.Converter
{
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
PlayerID.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
PlayerID.iTunes => PathHelper.iTunesLogoPath,
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
@@ -33,6 +30,10 @@ namespace BetterLyrics.WinUI3.Converter
PlayerID.Edge => PathHelper.EdgeLogoPath,
PlayerID.BetterLyrics => PathHelper.LogoPath,
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
PlayerID.SaltPlayerForWindows => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
PlayerID.Listen1 => PathHelper.Listen1LogoPath,
_ => PathHelper.UnknownPlayerLogoPath,
};
}

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MillisecondsToFormattedTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int milliseconds)
{
return TimeSpan.FromMilliseconds(milliseconds).ToString(@"mm\:ss\.fff");
}
else if (value is double doubleMilliseconds)
{
return TimeSpan.FromMilliseconds(doubleMilliseconds).ToString(@"mm\:ss\.fff");
}
return value?.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class RectToMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is Windows.Foundation.Rect rect)
{
return new Microsoft.UI.Xaml.Thickness(rect.X, rect.Y, 0, 0);
}
return new Microsoft.UI.Xaml.Thickness(0);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -19,6 +19,7 @@ namespace BetterLyrics.WinUI3.Converter
TranslationSearchProvider.Netease => "网易云音乐",
TranslationSearchProvider.Kugou => "酷狗音乐",
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
TranslationSearchProvider.AppleMusic => "Apple Music",
TranslationSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
TranslationSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
TranslationSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),

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.Enums
{
public enum ChineseRomanization
{
Pinyin,
Jyutping,
}
}

View File

@@ -67,5 +67,16 @@ namespace BetterLyrics.WinUI3.Enums
_ => ".*",
};
}
public static LyricsSearchProvider? ToLyricsSearchProvider(this LyricsFormat format)
{
return format switch
{
LyricsFormat.Lrc => LyricsSearchProvider.LocalLrcFile,
LyricsFormat.Eslrc => LyricsSearchProvider.LocalEslrcFile,
LyricsFormat.Ttml => LyricsSearchProvider.LocalTtmlFile,
_ => null,
};
}
}
}

View File

@@ -15,6 +15,7 @@ namespace BetterLyrics.WinUI3.Enums
LocalLrcFile,
LocalEslrcFile,
LocalTtmlFile,
AppleMusic,
}
public static class LyricsSearchProviderExtensions
@@ -28,6 +29,7 @@ namespace BetterLyrics.WinUI3.Enums
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
};
}
@@ -41,6 +43,7 @@ namespace BetterLyrics.WinUI3.Enums
LyricsSearchProvider.Kugou => LyricsFormat.Krc,
LyricsSearchProvider.Netease => LyricsFormat.Lrc,
LyricsSearchProvider.AmllTtmlDb => LyricsFormat.Ttml,
LyricsSearchProvider.AppleMusic => LyricsFormat.Ttml,
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
LyricsSearchProvider.LocalEslrcFile => LyricsFormat.Eslrc,
LyricsSearchProvider.LocalTtmlFile => LyricsFormat.Ttml,
@@ -71,6 +74,7 @@ namespace BetterLyrics.WinUI3.Enums
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
LyricsSearchProvider.AppleMusic => TranslationSearchProvider.AppleMusic,
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,

View File

@@ -1,12 +0,0 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsWindowMode
{
StandardMode,
DockMode,
DesktopMode,
PictureInPictureMode,
}
}

View File

@@ -8,10 +8,10 @@ namespace BetterLyrics.WinUI3.Enums
{
public enum ShortcutID
{
DesktopLock,
DesktopToggle,
DockToggle,
PictureInPictureToggle,
LyricsWindowShowOrHide,
Borderless,
ClickThrough,
LyricsWindowSwitch,
PlayOrPauseSong,
NextSong,
PreviousSong,

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum TitleBarArea
{
None,
Top,
Whole,
}
}

View File

@@ -13,6 +13,7 @@ namespace BetterLyrics.WinUI3.Enums
Netease,
LrcLib,
AmllTtmlDb,
AppleMusic,
LocalMusicFile,
LocalLrcFile,
LocalEslrcFile,

View File

@@ -8,11 +8,11 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, List<Color> albumArtLightAccentColors, List<Color> albumArtDarkAccentColors) : EventArgs
{
public byte[]? Bytes { get; set; } = bytes;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
public Color? AlbumArtLightAccentColor { get; set; } = albumArtLightAccentColor;
public Color? AlbumArtDarkAccentColor { get; set; } = albumArtDarkAccentColor;
public List<Color> AlbumArtLightAccentColors { get; set; } = albumArtLightAccentColors;
public List<Color> AlbumArtDarkAccentColors { get; set; } = albumArtDarkAccentColors;
}
}

View File

@@ -0,0 +1,126 @@
using BetterLyrics.WinUI3.Constants;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class AppleMusic
{
private readonly HttpClient _client;
private string _accessToken = "";
private string _storefront = "";
private string _language = "";
public AppleMusic()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36");
_client.DefaultRequestHeaders.Add("Accept", "application/json");
_client.DefaultRequestHeaders.Add("Origin", "https://music.apple.com");
_client.DefaultRequestHeaders.Add("Referer", "https://music.apple.com/");
}
public async Task<bool> InitAsync()
{
await GetAccessTokenAsync();
await SetMediaUserTokenAsync();
return
!string.IsNullOrEmpty(_accessToken) &&
!string.IsNullOrEmpty(PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey));
}
private async Task GetAccessTokenAsync()
{
var resp = await _client.GetStringAsync("https://music.apple.com/us/browse");
var jsMatch = Regex.Match(resp, "(?<=index)(.*?)(?=\\.js\")");
if (!jsMatch.Success) throw new Exception("Failed to find index.js");
var jsUrl = $"https://music.apple.com/assets/index{jsMatch.Value}.js";
var jsResp = await _client.GetStringAsync(jsUrl);
var tokenMatch = Regex.Match(jsResp, "(?=eyJh)(.*?)(?=\")");
if (!tokenMatch.Success) throw new Exception("Failed to find access token");
_accessToken = tokenMatch.Value;
_client.DefaultRequestHeaders.Remove("Authorization");
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_accessToken}");
}
private async Task SetMediaUserTokenAsync()
{
_client.DefaultRequestHeaders.Remove("media-user-token");
_client.DefaultRequestHeaders.Add("media-user-token",
PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey));
var resp = await _client.GetStringAsync("https://amp-api.music.apple.com/v1/me/storefront");
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
_storefront = json.GetProperty("data")[0].GetProperty("id").ToString();
_language = json.GetProperty("data")[0].GetProperty("attributes").GetProperty("defaultLanguageTag").ToString();
_client.DefaultRequestHeaders.Remove("Accept-Language");
_client.DefaultRequestHeaders.Add("Accept-Language", $"{_language},en;q=0.9");
}
public async Task<string?> GetLyricsAsync(string title, string artist)
{
string id = await SearchSongInfoAsync(artist, title);
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/songs/{id}";
var url = apiUrl + $"?include[songs]=lyrics,syllable-lyrics&l={_language}";
var resp = await _client.GetStringAsync(url);
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
var data = json.GetProperty("data");
if (data.GetArrayLength() == 0) return string.Empty;
var song = data[0];
if (!song.TryGetProperty("relationships", out var relationships))
return string.Empty;
if (relationships.TryGetProperty("syllable-lyrics", out var syllableLyrics) &&
syllableLyrics.GetProperty("data").GetArrayLength() > 0)
{
var syllableLyric = syllableLyrics.GetProperty("data")[0];
if (syllableLyric.TryGetProperty("attributes", out var attributes) &&
attributes.TryGetProperty("ttml", out var ttml))
{
string? raw = ttml.GetString();
if (raw != null && raw.Contains("begin=") && raw.Contains("end="))
{
return raw;
}
}
}
//if (relationships.TryGetProperty("lyrics", out var lyrics) &&
// lyrics.GetProperty("data").GetArrayLength() > 0)
//{
// var lyric = lyrics.GetProperty("data")[0];
// if (lyric.TryGetProperty("attributes", out var attributes) &&
// attributes.TryGetProperty("ttml", out var ttml))
// {
// return ttml.GetString();
// }
//}
return null;
}
private async Task<string> SearchSongInfoAsync(string artist, string title)
{
var query = $"{artist} {title}";
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/search";
var url = apiUrl + $"?term={WebUtility.UrlEncode(query)}&types=songs&limit=1&l={_language}";
var resp = await _client.GetStringAsync(url);
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
var results = json.GetProperty("results");
if (results.TryGetProperty("songs", out var songs) && songs.GetProperty("data").GetArrayLength() > 0)
{
var song = songs.GetProperty("data")[0];
return song.GetProperty("id").ToString();
}
return string.Empty;
}
}
}

View File

@@ -74,9 +74,17 @@ namespace BetterLyrics.WinUI3.Helper
using var ds = list.CreateDrawingSession();
if (strokeWidth > 0)
{
if (lyricsLine.TextGeometry == null)
{
return list;
}
ds.DrawGeometry(lyricsLine.TextGeometry, lyricsLine.Position, strokeColor, strokeWidth); // 描边
}
ds.FillGeometry(lyricsLine.TextGeometry, lyricsLine.Position, fontColor); // 填充
if (lyricsLine.CanvasTextLayout == null)
{
return list;
}
ds.DrawTextLayout(lyricsLine.CanvasTextLayout, lyricsLine.Position, fontColor); // 绘制文本(填充)
return list;
}

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Numerics;
using Vanara.PInvoke;
using Windows.UI;
@@ -94,6 +95,11 @@ namespace BetterLyrics.WinUI3.Helper
return Color.FromArgb(alpha, color.R, color.G, color.B);
}
public static Color WithOpacity(this Color color, float opacity)
{
return Color.FromArgb((byte)(opacity * 255), color.R, color.G, color.B);
}
public static Color WithBrightness(this Color color, double brightness)
{
// 确保亮度因子在合理范围内
@@ -106,6 +112,11 @@ namespace BetterLyrics.WinUI3.Helper
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
}
public static Vector3 ToVector3RGB(this Color color)
{
return new Vector3((float)color.R / 0xff, (float)color.G / 0xff, (float)color.B / 0xff);
}
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
{
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
@@ -116,15 +127,11 @@ namespace BetterLyrics.WinUI3.Helper
{
case WindowPixelSampleMode.BelowWindow:
{
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Bottom + 2, screenWidth, 1);
}
case WindowPixelSampleMode.AboveWindow:
{
int sampleHeight = 1;
int sampleY = myRect.Top - 1;
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - 2, screenWidth, 1);
}
case WindowPixelSampleMode.WindowArea:
{
@@ -142,49 +149,21 @@ namespace BetterLyrics.WinUI3.Helper
if (width <= 0 || height <= 0)
return System.Drawing.Color.Transparent;
var edgeThickness = new Thickness(36, 0, 36, 0);
var edgeThickness = new Thickness(36, 36, 36, 36);
List<System.Drawing.Color> edgeColors = [];
// Top edge
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top,
width,
(int)edgeThickness.Top
)
);
if (edgeThickness.Top > 0)
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - (int)edgeThickness.Top, width, (int)edgeThickness.Top));
// Bottom edge
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Bottom - (int)edgeThickness.Bottom,
width,
(int)edgeThickness.Bottom
)
);
if (edgeThickness.Bottom > 0)
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Left, myRect.Bottom, width, (int)edgeThickness.Bottom));
// Left edge
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Left,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
if (edgeThickness.Left > 0)
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Left - (int)edgeThickness.Left, myRect.Top, (int)edgeThickness.Left, height));
// Right edge
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Right - (int)edgeThickness.Right,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Right,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
if (edgeThickness.Right > 0)
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Right, myRect.Top, (int)edgeThickness.Right, height));
// 合并四边平均色
if (edgeColors.Count == 0)
@@ -246,6 +225,5 @@ namespace BetterLyrics.WinUI3.Helper
if (count == 0) return System.Drawing.Color.Transparent;
return System.Drawing.Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
}
}
}

View File

@@ -1,109 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
using WinRT.Interop;
using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
{
public static class DesktopModeHelper
{
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
public static void Disable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20>ָ<EFBFBD>TopMost״̬
if (_originalTopmostStates.TryGetValue(hwnd, out var wasTopMost))
{
window.SetIsAlwaysOnTop(wasTopMost);
_originalTopmostStates.Remove(hwnd);
}
// <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
if (_originalWindowBounds.TryGetValue(hwnd, out var bounds))
{
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(
(int)bounds.X,
(int)bounds.Y,
(int)bounds.Width,
(int)bounds.Height
)
);
_originalWindowBounds.Remove(hwnd);
}
window.SetIsShownInSwitchers(true);
}
public static void Enable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
if (!_originalWindowBounds.ContainsKey(hwnd))
{
_originalWindowBounds[hwnd] = (
window.AppWindow.Position.X,
window.AppWindow.Position.Y,
window.AppWindow.Size.Width,
window.AppWindow.Size.Height
);
}
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
window.AppWindow.MoveAndResize(_settingsService.AppSettings.DesktopModeSettings.WindowBounds.ToRectInt32());
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
if (!_originalTopmostStates.ContainsKey(hwnd))
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
window.SetIsAlwaysOnTop(true);
window.SetIsShownInSwitchers(false);
}
public static void SetClickThrough(Window window, bool enable)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
if (enable)
{
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
if (!_originalWindowStyles.ContainsKey(hwnd))
_originalWindowStyles[hwnd] = window.GetWindowStyle();
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
}
else
{
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
{
window.SetWindowStyle(style);
_originalWindowStyles.Remove(hwnd);
}
}
}
}
}

View File

@@ -1,205 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Vanara.PInvoke;
using WinRT.Interop;
using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
{
public static class DockModeHelper
{
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly HashSet<IntPtr> _registered = [];
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
public static bool IsEnabled(IntPtr hwnd)
{
return _registered.Contains(hwnd);
}
public static void Disable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (!_registered.Contains(hwnd)) return;
window.SetIsShownInSwitchers(true);
window.ExtendsContentIntoTitleBar = true;
window.SetIsAlwaysOnTop(false);
UnregisterAppBar(hwnd);
window.SetWindowStyle(_originalWindowStyle[hwnd]);
_originalWindowStyle.Remove(hwnd);
window.ExtendsContentIntoTitleBar = true;
if (_originalPositions.TryGetValue(hwnd, out var rect))
{
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
rect.Left,
rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top,
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
_originalPositions.Remove(hwnd);
}
}
public static void Enable(Window window, string monitorDeviceName, int appBarHeight, DockPlacement dockPlacement)
{
window.SetIsShownInSwitchers(false);
window.SetIsAlwaysOnTop(true);
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (!_originalWindowStyle.ContainsKey(hwnd))
{
_originalWindowStyle[hwnd] = window.GetWindowStyle();
}
if (!_originalPositions.ContainsKey(hwnd))
{
if (User32.GetWindowRect(hwnd, out var rect))
{
_originalPositions[hwnd] = rect;
}
}
RegisterAppBar(hwnd, monitorDeviceName, appBarHeight, dockPlacement);
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.AppSettings.DockModeSettings.DockMonitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int screenHeight = monitorInfo.rcMonitor.Bottom - monitorInfo.rcMonitor.Top;
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - appBarHeight;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
monitorInfo.rcMonitor.Left,
y,
screenWidth,
appBarHeight,
User32.SetWindowPosFlags.SWP_HIDEWINDOW
);
window.ExtendsContentIntoTitleBar = false;
window.ToggleWindowStyle(true, WindowStyle.Popup);
window.Show();
}
private static void RegisterAppBar(IntPtr hwnd, string monitorDeviceName, int height, DockPlacement dockPlacement)
{
if (_registered.Contains(hwnd)) return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - height;
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + height : monitorInfo.rcMonitor.Bottom;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = uEdge,
rc = new RECT
{
Left = monitorInfo.rcMonitor.Left,
Top = top,
Right = monitorInfo.rcMonitor.Right,
Bottom = bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
_registered.Add(hwnd);
}
private static void UnregisterAppBar(IntPtr hwnd)
{
if (!_registered.Contains(hwnd))
return;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
_registered.Remove(hwnd);
}
public static void UpdateAppBarHeight(IntPtr hwnd, string monitorDeviceName, int newHeight, DockPlacement dockPlacement)
{
App.DispatcherQueueTimer?.Debounce(() =>
{
if (!_registered.Contains(hwnd))
return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + newHeight : monitorInfo.rcMonitor.Bottom;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = uEdge,
rc = new RECT
{
Left = monitorInfo.rcMonitor.Left,
Top = top,
Right = monitorInfo.rcMonitor.Right,
Bottom = bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
// 同步窗口实际高度和位置
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
int repeatCount = 2;
while (repeatCount > 0)
{
repeatCount--;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
monitorInfo.rcMonitor.Left,
y,
screenWidth,
newHeight,
newHeight == 0 ? User32.SetWindowPosFlags.SWP_HIDEWINDOW : User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
}
}, TimeSpan.FromMilliseconds(100));
}
}
}

View File

@@ -11,6 +11,6 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class FontHelper
{
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies().Order().ToArray();
}
}

View File

@@ -17,7 +17,6 @@ namespace BetterLyrics.WinUI3.Helper
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
private HWND _currentForeground = HWND.NULL;
private readonly IntPtr _selfHwnd;
private readonly ThrottleHelper _winEventProcThrottle = new(TimeSpan.FromSeconds(1));
public delegate void WindowChangedHandler(HWND hwnd);
private readonly WindowChangedHandler _onWindowChanged;
@@ -73,7 +72,7 @@ namespace BetterLyrics.WinUI3.Helper
_hooks.Clear();
_timer.Stop();
//_timer.Stop();
}
private void Timer_Tick(object? sender, object e)

View File

@@ -14,6 +14,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
@@ -219,5 +220,68 @@ namespace BetterLyrics.WinUI3.Helper
}
return pixelData;
}
public static async Task<byte[]> DownloadImageAsByteArrayAsync(string url)
{
using var httpClient = new HttpClient();
return await httpClient.GetByteArrayAsync(url);
}
public static byte[]? DataUrlToByteArray(string dataUrl)
{
const string base64Marker = ";base64,";
int base64Index = dataUrl.IndexOf(base64Marker, StringComparison.OrdinalIgnoreCase);
if (base64Index >= 0)
{
string base64Data = dataUrl.Substring(base64Index + base64Marker.Length);
return Convert.FromBase64String(base64Data);
}
else
{
// 非 base64直接取逗号后内容并解码
int commaIndex = dataUrl.IndexOf(',');
if (commaIndex >= 0)
{
string rawData = dataUrl.Substring(commaIndex + 1);
return System.Text.Encoding.UTF8.GetBytes(Uri.UnescapeDataString(rawData));
}
else
{
return null;
}
}
}
public static async Task<byte[]?> GetImageBytesFromUrlAsync(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return null;
}
try
{
if (url.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
// data URL直接解析
return DataUrlToByteArray(url);
}
else if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
{
// 普通网络图片,下载
return await DownloadImageAsByteArrayAsync(url);
}
else
{
// 其他类型暂不支持
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}

View File

@@ -1,14 +1,7 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using NTextCat;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using TinyPinyin;
using Windows.Globalization;
namespace BetterLyrics.WinUI3.Services
@@ -18,49 +11,83 @@ namespace BetterLyrics.WinUI3.Services
private static readonly RankedLanguageIdentifierFactory _factory = new();
private static readonly RankedLanguageIdentifier _identifier;
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
public static List<Models.LanguageInfo> SupportedTargetLanguages { get; set; } =
[
new Models.LanguageInfo("ar", "العربية"),
new Models.LanguageInfo("az", "Azərbaycan dili"),
new Models.LanguageInfo("zh-Hans", "简体中文"),
new Models.LanguageInfo("zh-Hant", "繁體中文"),
new Models.LanguageInfo("bg", "Български"),
new Models.LanguageInfo("bn", "বাংলা"),
new Models.LanguageInfo("ca", "Català"),
new Models.LanguageInfo("cs", "Čeština"),
new Models.LanguageInfo("da", "Dansk"),
new Models.LanguageInfo("nl", "Nederlands"),
new Models.LanguageInfo("de", "Deutsch"),
new Models.LanguageInfo("el", "Ελληνικά"),
new Models.LanguageInfo("en", "English"),
new Models.LanguageInfo("eo", "Esperanto"),
new Models.LanguageInfo("es", "Español"),
new Models.LanguageInfo("et", "Eesti"),
new Models.LanguageInfo("eu", "Euskara"),
new Models.LanguageInfo("fa", "فارسی"),
new Models.LanguageInfo("fi", "Suomi"),
new Models.LanguageInfo("fr", "Français"),
new Models.LanguageInfo("de", "Deutsch"),
new Models.LanguageInfo("el", "Ελληνικά"),
new Models.LanguageInfo("ga", "Gaeilge"),
new Models.LanguageInfo("gl", "Galego"),
new Models.LanguageInfo("he", "עברית"),
new Models.LanguageInfo("hi", "हिन्दी"),
new Models.LanguageInfo("hu", "Magyar"),
new Models.LanguageInfo("id", "Bahasa Indonesia"),
new Models.LanguageInfo("ga", "Gaeilge"),
new Models.LanguageInfo("it", "Italiano"),
new Models.LanguageInfo("ja", "日本語"),
new Models.LanguageInfo("ko", "한국어"),
new Models.LanguageInfo("fa", "فارسی"),
new Models.LanguageInfo("ky", "Кыргызча"),
new Models.LanguageInfo("lt", "Lietuvių"),
new Models.LanguageInfo("lv", "Latviešu"),
new Models.LanguageInfo("ms", "Bahasa Melayu"),
new Models.LanguageInfo("nb", "Norsk bokmål"),
new Models.LanguageInfo("nl", "Nederlands"),
new Models.LanguageInfo("pt-BR", "Português (Brasil)"),
new Models.LanguageInfo("pl", "Polski"),
new Models.LanguageInfo("pt", "Português"),
new Models.LanguageInfo("ro", "Română"),
new Models.LanguageInfo("ru", "Русский"),
new Models.LanguageInfo("sk", "Slovenčina"),
new Models.LanguageInfo("es", "Español"),
new Models.LanguageInfo("sl", "Slovenščina"),
new Models.LanguageInfo("sq", "Shqip"),
new Models.LanguageInfo("sr", "Српски"),
new Models.LanguageInfo("sv", "Svenska"),
new Models.LanguageInfo("th", "ไทย"),
new Models.LanguageInfo("tl", "Filipino"),
new Models.LanguageInfo("tr", "Türkçe"),
new Models.LanguageInfo("uk", "Українська"),
new Models.LanguageInfo("ur", "اردو"),
new Models.LanguageInfo("vi", "Tiếng Việt"),
new Models.LanguageInfo("zh", "中文"),
];
static LanguageHelper()
{
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
}
private static string SimplifiedChineseOrTraditionalChinese(string text)
{
return text == ChineseConverter.ConvertToSimplifiedChinese(text) ? "zh-Hans" : "zh-Hant";
RomajiConverter.Core.Helpers.RomajiHelper.Init();
}
public static string? DetectLanguageCode(string? text)
@@ -72,9 +99,8 @@ namespace BetterLyrics.WinUI3.Services
code = code switch
{
"simple" => "en",
"zh_classical" => SimplifiedChineseOrTraditionalChinese(text),
"zh_yue" => SimplifiedChineseOrTraditionalChinese(text),
"zh" => SimplifiedChineseOrTraditionalChinese(text),
"zh_classical" => "zh",
"zh_yue" => "zh",
_ => code
};
return code;
@@ -89,26 +115,17 @@ namespace BetterLyrics.WinUI3.Services
};
}
public static string ConvertToCountryCode(string? languageCode)
public static string GetDefaultTargetLanguageCode()
{
if (languageCode == null) return "us";
return languageCode switch
var found = SupportedTargetLanguages.Find(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
if (found == null)
{
"zh" => "cn",
"zh-Hans" => "cn",
"zh-Hant" => "tw",
"ja" => "jp",
"ko" => "kr",
_ => "us"
};
}
public static int GetDefaultTargetLanguageIndex()
{
int found = SupportedTargetLanguages.FindIndex(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
if (found == -1) found = 7; // 默认使用英语
return found;
return "en";
}
else
{
return found.Code;
}
}
public static string GetOrderChar(string text)
@@ -118,12 +135,17 @@ namespace BetterLyrics.WinUI3.Services
if (char.IsLetter(c) && c < 128)
return char.ToUpper(c).ToString();
if (PinyinHelper.IsChinese(c))
if (Pinyin.Pinyin.Instance.IsHanzi(c.ToString()))
{
return PinyinHelper.GetPinyinInitials($"{c}");
return Pinyin.Pinyin.Instance.HanziToPinyin(c.ToString(), Pinyin.ManTone.Style.NORMAL).ToStr().ToUpper().FirstOrDefault().ToString();
}
return "#";
}
public static string ToRomaji(string text)
{
return string.Join(" ", RomajiConverter.Core.Helpers.RomajiHelper.SentenceToRomaji(text).Select(x => x.Romaji));
}
}
}

View File

@@ -3,19 +3,18 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using Lyricify.Lyrics.Helpers.General;
using Lyricify.Lyrics.Models;
using Lyricify.Lyrics.Parsers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Windows.Globalization.Fonts;
using LyricsData = BetterLyrics.WinUI3.Models.LyricsData;
namespace BetterLyrics.WinUI3.Helper
{
public class LyricsParser
public partial class LyricsParser
{
private List<LyricsData> _lyricsDataArr = [];
@@ -36,10 +35,10 @@ namespace BetterLyrics.WinUI3.Helper
ParseLrc(raw);
break;
case LyricsFormat.Qrc:
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
ParseQQNeteaseKugou(QrcParser.Parse(raw).Lines);
break;
case LyricsFormat.Krc:
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
ParseQQNeteaseKugou(KrcParser.Parse(raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(raw);
@@ -48,50 +47,67 @@ namespace BetterLyrics.WinUI3.Helper
break;
}
}
FillChineseLyricsData();
FillRomanizationLyricsData();
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
return _lyricsDataArr;
}
private void FillChineseLyricsData()
private void FillRomanizationLyricsData()
{
var simplifiedChinese = _lyricsDataArr.Where(x => x.LanguageCode == "zh-Hans").FirstOrDefault();
var traditionalChinese = _lyricsDataArr.Where(x => x.LanguageCode == "zh-Hant").FirstOrDefault();
if (simplifiedChinese != null && traditionalChinese == null)
var chinese = _lyricsDataArr.Where(x => x.LanguageCode == "zh").FirstOrDefault();
if (chinese != null)
{
// 如果没有繁体中文歌词,则将简体中文歌词转换为繁体中文
_lyricsDataArr.Add(new LyricsData
{
LyricsLines = simplifiedChinese.LyricsLines.Select(line => new LyricsLine
LanguageCode = "pinyin",
LyricsLines = chinese.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = ChineseConverter.ConvertToTraditionalChinese(line.OriginalText),
OriginalText = Pinyin.Pinyin.Instance.HanziToPinyin(line.OriginalText).ToStr(),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = ChineseConverter.ConvertToTraditionalChinese(c.Text),
Text = Pinyin.Pinyin.Instance.HanziToPinyin(c.Text).ToStr(),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
_lyricsDataArr.Add(new LyricsData
{
LanguageCode = "jyutping",
LyricsLines = chinese.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = Pinyin.Jyutping.Instance.HanziToPinyin(line.OriginalText).ToStr(),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = Pinyin.Jyutping.Instance.HanziToPinyin(c.Text).ToStr(),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
else if (traditionalChinese != null && simplifiedChinese == null)
var japanese = _lyricsDataArr.Where(x => x.LanguageCode == "ja").FirstOrDefault();
if (japanese != null)
{
// 如果没有简体中文歌词,则将繁体中文歌词转换为简体中文
_lyricsDataArr.Add(new LyricsData
{
LyricsLines = traditionalChinese.LyricsLines.Select(line => new LyricsLine
LanguageCode = "romaji",
LyricsLines = japanese.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = ChineseConverter.ConvertToSimplifiedChinese(line.OriginalText),
OriginalText = LanguageHelper.ToRomaji(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = ChineseConverter.ConvertToSimplifiedChinese(c.Text),
Text = LanguageHelper.ToRomaji(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
@@ -106,9 +122,7 @@ namespace BetterLyrics.WinUI3.Helper
new List<(int time, string text, List<(int time, string text)> syllables)>();
// 支持 [mm:ss.xx]字、<mm:ss.xx>字,毫秒两位或三位
var syllableRegex = new Regex(
@"(\[|\<)(\d{2}):(\d{2})\.(\d{2,3})(\]|\>)([^\[\]\<\>]*)"
);
var syllableRegex = SyllableRegex();
foreach (var line in lines)
{
@@ -125,7 +139,7 @@ namespace BetterLyrics.WinUI3.Helper
syllables.Add((totalMs, text));
}
if (syllables.Count > 0)
if (syllables.Count > 1)
{
lrcLines.Add(
(
@@ -138,18 +152,19 @@ namespace BetterLyrics.WinUI3.Helper
else
{
// 普通LRC行
var bracketRegex = new Regex(@"\[(\d{2}):(\d{2})\.(\d{2,3})\]");
Regex? bracketRegex = LrcRegex();
var bracketMatches = bracketRegex.Matches(line);
string content = line;
int? lineStartTime = null;
if (bracketMatches.Count > 0)
{
var m = bracketMatches[0];
var m = bracketMatches![0];
int min = int.Parse(m.Groups[1].Value);
int sec = int.Parse(m.Groups[2].Value);
int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0'));
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
lineStartTime = min * 60_000 + sec * 1000 + ms;
content = bracketRegex.Replace(line, "");
content = bracketRegex!.Replace(line, "");
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
}
}
@@ -230,10 +245,11 @@ namespace BetterLyrics.WinUI3.Helper
int pStartMs = ParseTtmlTime(pBegin);
int pEndMs = ParseTtmlTime(pEnd);
// 只获取一级span且排除ttm:role="x-bg"span
// 只获取一级span且排除 ttm:role="x-bg"span 和 ttm:role="x-roman"
var spans = p.Elements()
.Where(s => s.Name.LocalName == "span" &&
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg")
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg" &&
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-roman")
.ToList();
// 原文和翻译分离
@@ -434,5 +450,10 @@ namespace BetterLyrics.WinUI3.Helper
_lyricsDataArr.Add(new LyricsData(lyricsLines));
}
[GeneratedRegex(@"\[(\d*):(\d*)(\.|\:)(\d*)\]")]
private static partial Regex LrcRegex();
[GeneratedRegex(@"(\[|\<)(\d*):(\d*)\.(\d*)(\]|\>)([^\[\]\<\>]*)")]
private static partial Regex SyllableRegex();
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
@@ -6,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.Helper
{
@@ -60,5 +62,14 @@ namespace BetterLyrics.WinUI3.Helper
var primaryMonitorInfo = GetPrimaryMonitorInfoEx();
return primaryMonitorInfo.szDevice;
}
public static User32.MONITORINFOEX GetMonitorInfoExFromWindow(Window window)
{
var hwnd = WindowNative.GetWindowHandle(window);
var hMonitor = User32.MonitorFromWindow(hwnd, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST);
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
User32.GetMonitorInfo(hMonitor, ref monitorInfoEx);
return monitorInfoEx;
}
}
}

View File

@@ -34,6 +34,10 @@ namespace BetterLyrics.WinUI3.Helper
public static string PotPlayerLogoPath => Path.Combine(AssetsFolder, "PotPlayer.png");
public static string ChromeLogoPath => Path.Combine(AssetsFolder, "Chrome.png");
public static string EdgeLogoPath => Path.Combine(AssetsFolder, "Edge.png");
public static string SaltPlayerForWindowsLogoPath => Path.Combine(AssetsFolder, "SaltPlayerForWindows.png");
public static string MoeKoeMusicLogoPath => Path.Combine(AssetsFolder, "MoeKoeMusic.png");
public static string Listen1LogoPath => Path.Combine(AssetsFolder, "Listen1.png");
public static string UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
@@ -46,6 +50,7 @@ namespace BetterLyrics.WinUI3.Helper
public static string QQLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "qq");
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
public static string AppleMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "apple-music");
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.json");
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
@@ -53,6 +58,7 @@ namespace BetterLyrics.WinUI3.Helper
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
public static string KugouTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "kugou");
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
@@ -69,9 +75,11 @@ namespace BetterLyrics.WinUI3.Helper
Directory.CreateDirectory(KugouLyricsCacheDirectory);
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(AppleMusicCacheDirectory);
Directory.CreateDirectory(QQTranslationCacheDirectory);
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
Directory.CreateDirectory(KugouTranslationCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace BetterLyrics.WinUI3.Helper
{
public static class PlayerIdMatcher
{
private static readonly List<string> _neteaseFamilyRegex =
[
"cloudmusic.exe", //NetEaseCloudMusic
"^17588BrandonWong\\.LyricEase_", //LyricEase
"^48848aaaaaaccd\\.HyPlayer_" //HyPlayer
];
public static bool IsNeteaseFamily(string player)
{
foreach (var regex in _neteaseFamilyRegex)
{
var isMatch = Regex.IsMatch(player, regex);
if (isMatch) return true;
}
return false;
}
}
}

View File

@@ -18,5 +18,45 @@ namespace BetterLyrics.WinUI3.Helper
(int)rect.Height
);
}
public static Windows.Foundation.Rect WithHeight(this Windows.Foundation.Rect rect, double height)
{
return new Windows.Foundation.Rect(
rect.X,
rect.Y,
rect.Width,
height
);
}
public static Windows.Foundation.Rect WithWidth(this Windows.Foundation.Rect rect, double width)
{
return new Windows.Foundation.Rect(
rect.X,
rect.Y,
width,
rect.Height
);
}
public static Windows.Foundation.Rect WithX(this Windows.Foundation.Rect rect, double x)
{
return new Windows.Foundation.Rect(
x,
rect.Y,
rect.Width,
rect.Height
);
}
public static Windows.Foundation.Rect WithY(this Windows.Foundation.Rect rect, double y)
{
return new Windows.Foundation.Rect(
rect.X,
y,
rect.Width,
rect.Height
);
}
}
}

View File

@@ -0,0 +1,181 @@
using BetterLyrics.WinUI3.Models.Settings;
using NAudio.Dsp;
using NAudio.Wave;
using System;
using System.Diagnostics;
namespace BetterLyrics.WinUI3.Helper
{
public class SpectrumAnalyzer : IDisposable
{
private WasapiLoopbackCapture _capture;
private int _sampleRate = 48000;
private readonly int _fftLength = 2048;
private readonly float[] _fftLeftBuffer;
private readonly float[] _fftRightBuffer;
private readonly Complex[] _fftLeftData;
private readonly Complex[] _fftRightData;
private float[] _spectrumLeftData;
private float[] _spectrumRightData;
private float[] _spectrumData;
private bool _disposed = false;
private double[] _hammingWindow;
private float[] _currentSpectrum;
public float[] SmoothSpectrum { get; private set; }
public int BarCount { get; set; } = 32;
public int Sensitivity { get; set; } = 10;
public float SmoothingFactor { get; set; } = 0.95f;
public bool IsCapturing { get; private set; } = false;
public SpectrumAnalyzer()
{
_fftLeftBuffer = new float[_fftLength];
_fftLeftData = new Complex[_fftLength];
_fftRightBuffer = new float[_fftLength];
_fftRightData = new Complex[_fftLength];
_hammingWindow = new double[_fftLength];
//汉明窗
for (int i = 0; i < _fftLength; i++)
{
_hammingWindow[i] = 0.54 - 0.46 * Math.Cos((2 * Math.PI * i) / (_fftLength - 1));
}
}
public void StartCapture()
{
_currentSpectrum = new float[BarCount];
SmoothSpectrum = new float[BarCount];
try
{
_capture = new WasapiLoopbackCapture();
_sampleRate = _capture.WaveFormat.SampleRate;
_spectrumLeftData = new float[(int)(24000.0f / _sampleRate * _fftLength) / 2];
_spectrumRightData = new float[(int)(24000.0f / _sampleRate * _fftLength) / 2];
_spectrumData = new float[(int)(24000.0f / _sampleRate * _fftLength)];
_capture.DataAvailable += OnDataAvailable;
_capture.RecordingStopped += OnRecordingStopped;
_capture.StartRecording();
IsCapturing = true;
}
catch (Exception)
{
}
}
public void StopCapture()
{
_capture?.DataAvailable -= OnDataAvailable;
_capture?.RecordingStopped -= OnRecordingStopped;
_capture?.StopRecording();
IsCapturing = false;
}
private void OnDataAvailable(object? sender, WaveInEventArgs e)
{
if (_disposed || e.BytesRecorded == 0) return;
// 将字节转换为浮点数
int samples = e.BytesRecorded / 8;
if (samples < _fftLength) return;
for (int i = 0; i < _fftLength; i++)
{
_fftLeftBuffer[i] = BitConverter.ToSingle(e.Buffer, i * 8);
_fftRightBuffer[i] = BitConverter.ToSingle(e.Buffer, i * 8 + 4);
}
for (int i = 0; i < _fftLength; i++)
{
_fftLeftData[i].X = _fftLeftBuffer[i] * (float)_hammingWindow[i]; // Real part
_fftLeftData[i].Y = 0; // Imaginary part
_fftRightData[i].X = _fftRightBuffer[i] * (float)_hammingWindow[i];
_fftRightData[i].Y = 0;
}
// FFT
FastFourierTransform.FFT(true, (int)Math.Log(_fftLength, 2), _fftLeftData);
FastFourierTransform.FFT(true, (int)Math.Log(_fftLength, 2), _fftRightData);
for (int i = 0; i < _spectrumLeftData.Length; i++)
{
float real = (float)_fftLeftData[i].X;
float imaginary = (float)_fftLeftData[i].Y;
float magnitude = (float)Math.Sqrt(real * real + imaginary * imaginary);
float frequency = i * _sampleRate / _fftLength;
float compensationFactor = GetCompensationFactor(frequency);
_spectrumLeftData[i] = magnitude * compensationFactor;
_spectrumRightData[i] = (float)Math.Sqrt((float)_fftRightData[i].X * (float)_fftRightData[i].X + (float)_fftRightData[i].Y * (float)_fftRightData[i].Y) * compensationFactor;
for (int j = 0; j < _spectrumLeftData.Length; j++)
{
_spectrumData[j] = _spectrumLeftData[_spectrumLeftData.Length - 1 - j];
}
Array.Copy(_spectrumRightData, 0, _spectrumData, _spectrumLeftData.Length, _spectrumRightData.Length);
}
for (int i = 0; i < BarCount; i++)
{
int index = (int)((float)i / BarCount * _spectrumData.Length);
if (index < _spectrumData.Length)
{
_currentSpectrum[i] = _spectrumData[index] * 250f * Sensitivity;
}
}
}
public void UpdateSmoothSpectrum()
{
for (int i = 0; i < BarCount; i++)
{
SmoothSpectrum[i] = SmoothSpectrum[i] * SmoothingFactor +
_currentSpectrum[i] * (1 - SmoothingFactor);
}
}
private float GetCompensationFactor(float freq)
{
// 补偿曲线
float[] frequencies = { 20, 50, 100, 200, 500, 1000, 2000, 4000, 8000, 16000, 20000 };
float[] gains = { 0.5f, 0.3f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.1f, 0.9f, 0.8f };
if (freq <= frequencies[0])
{
return gains[0];
}
if (freq >= frequencies[frequencies.Length - 1])
{
return gains[gains.Length - 1];
}
int i = 0;
while (freq > frequencies[i + 1])
{
i++;
}
// 线性插值
float x1 = frequencies[i];
float y1 = gains[i];
float x2 = frequencies[i + 1];
float y2 = gains[i + 1];
return y1 + (freq - x1) * ((y2 - y1) / (x2 - x1));
}
private void OnRecordingStopped(object? sender, StoppedEventArgs e)
{
}
public void Dispose()
{
if (!_disposed)
{
_capture?.Dispose();
_disposed = true;
}
}
}
}

View File

@@ -1,12 +1,19 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using WinRT.Interop;
using WinUIEx;
@@ -15,6 +22,22 @@ namespace BetterLyrics.WinUI3.Helper
public static class WindowHelper
{
private static List<object> _activeWindows = [];
private static List<object> _workAreas = [];
private static readonly Dictionary<HWND, WindowStyle> _defaultWindowStyle = [];
private static readonly Dictionary<HWND, ExtendedWindowStyle> _defaultExtendedWindowStyle = [];
private static readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private static readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
public static void HideWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
if (window is Window w)
{
w.Hide();
}
}
public static void CloseWindow<T>()
{
@@ -41,7 +64,8 @@ namespace BetterLyrics.WinUI3.Helper
}
return default;
}
public static void OpenWindow<T>()
public static void OpenOrShowWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
if (window == null)
@@ -59,6 +83,14 @@ namespace BetterLyrics.WinUI3.Helper
{
window = new MusicGalleryWindow();
}
else if (typeof(T) == typeof(LyricsSearchWindow))
{
window = new LyricsSearchWindow();
}
else if (typeof(T) == typeof(LyricsWindowSwitchWindow))
{
window = new LyricsWindowSwitchWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
@@ -70,9 +102,14 @@ namespace BetterLyrics.WinUI3.Helper
if (typeof(T) == typeof(LyricsWindow))
{
var hwnd = WindowNative.GetWindowHandle(castedWindow);
_defaultWindowStyle.Add(hwnd, castedWindow.GetWindowStyle());
_defaultExtendedWindowStyle.Add(hwnd, castedWindow.GetExtendedWindowStyle());
var lyricsWindow = (LyricsWindow)window;
lyricsWindow.ViewModel.InitShortcuts();
lyricsWindow.AutoSelectLyricsMode();
lyricsWindow.ViewModel.InitFgWindowWatcher();
lyricsWindow.ViewModel.RefreshLyricsWindowStatus();
}
}
else
@@ -119,11 +156,7 @@ namespace BetterLyrics.WinUI3.Helper
private static void EnsureDockModeReleased()
{
LyricsWindow? lyricsWindow = GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
DockModeHelper.Disable(lyricsWindow);
}
SetIsWorkArea<LyricsWindow>(false);
}
private static void TrackWindow(object window)
@@ -141,7 +174,224 @@ namespace BetterLyrics.WinUI3.Helper
if (_activeWindows.Contains(sender))
{
_activeWindows.Remove(sender);
var hwnd = WindowNative.GetWindowHandle(sender);
_defaultWindowStyle.Remove(hwnd);
_defaultExtendedWindowStyle.Remove(hwnd);
}
}
public static void SetIsClickThrough<T>(bool enable)
{
Window? window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (enable)
{
window.SetExtendedWindowStyle(_defaultExtendedWindowStyle[hwnd] | ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered);
}
else
{
window.SetExtendedWindowStyle(_defaultExtendedWindowStyle[hwnd]);
}
}
public static void SetIsWorkArea<T>(bool enable)
{
Window? window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
if (enable)
{
EnableWorkArea(window);
}
else
{
DisableWorkArea(window);
}
}
public static void SetIsBorderless<T>(bool enable)
{
var window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
if (enable)
{
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
}
else
{
window.SetWindowStyle(_defaultWindowStyle[hwnd]);
}
}
public static void SetIsShowInSwitchers<T>(bool enable)
{
var window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
window.AppWindow.IsShownInSwitchers = enable;
}
public static void SetIsAlwaysOnTop<T>(bool enable)
{
var window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
if (window.AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.IsAlwaysOnTop = enable;
}
}
public static void MoveAndResize<T>(Rect rect)
{
var window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
window.AppWindow.MoveAndResize(rect.ToRectInt32());
}
public static void SetTitleBarArea<T>(TitleBarArea titleBarArea)
{
if (typeof(T) == typeof(LyricsWindow))
{
LyricsWindow? lyricsWindow = GetWindowByWindowType<LyricsWindow>();
lyricsWindow?.SetTitleBarArea(titleBarArea);
}
else
{
throw new Exception($"Unsupported window type: {typeof(T).FullName}");
}
}
private static void DisableWorkArea(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (!_workAreas.Contains(hwnd)) return;
UnregisterWorkArea(hwnd);
}
private static void EnableWorkArea(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (_workAreas.Contains(hwnd)) return;
RegisterWorkArea(hwnd);
}
private static void RegisterWorkArea(IntPtr hwnd)
{
if (_workAreas.Contains(hwnd)) return;
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top : _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
double bottom = top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = uEdge,
rc = new RECT
{
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
Top = (int)top,
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
Bottom = (int)bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
_workAreas.Add(hwnd);
}
private static void UnregisterWorkArea(IntPtr hwnd)
{
if (!_workAreas.Contains(hwnd))
return;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
_workAreas.Remove(hwnd);
}
public static void UpdateWorkArea<T>()
{
var window = GetWindowByWindowType<T>() as Window;
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
if (!_workAreas.Contains(hwnd))
return;
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
double bottom = top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = uEdge,
rc = new RECT
{
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
Top = (int)top,
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
Bottom = (int)bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
}
public static void SetLyricsWindowVisibilityByPlayingStatus()
{
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(false);
}
HideWindow<LyricsWindow>();
}
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(true);
}
OpenOrShowWindow<LyricsWindow>();
}
}
}
}

View File

@@ -1,39 +1,26 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models
{
public partial class LiveStates : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsWindowMode CurrentLyricsWindowMode { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsDisplayType CurrentLyricsDisplayType { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings CurrentLyricsStyleSettings { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings CurrentLyricsEffectSettings { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsWindowStatus LyricsWindowStatus { get; set; }
public LiveStates(AppSettings appSettings)
public LiveStates()
{
CurrentLyricsWindowMode = LyricsWindowMode.StandardMode;
CurrentLyricsDisplayType = appSettings.StandardModeSettings.LyricsDisplayType;
CurrentLyricsStyleSettings = appSettings.StandardLyricsStyleSettings;
CurrentLyricsEffectSettings = appSettings.StandardLyricsEffectSettings;
}
public void ToggleLyricsWindowMode(LyricsWindowMode mode)
{
if (CurrentLyricsWindowMode == mode)
{
CurrentLyricsWindowMode = LyricsWindowMode.StandardMode;
}
else
{
CurrentLyricsWindowMode = mode;
}
LyricsWindowStatus = new LyricsWindowStatus();
}
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using Lyricify.Lyrics.Helpers.General;
using System;
@@ -13,7 +14,12 @@ namespace BetterLyrics.WinUI3.Models
public class LyricsData
{
public List<LyricsLine> LyricsLines { get; set; }
public string? LanguageCode => LanguageHelper.DetectLanguageCode(WrappedOriginalText);
private string? _languageCode;
public string? LanguageCode
{
get => _languageCode ?? LanguageHelper.DetectLanguageCode(WrappedOriginalText);
set => _languageCode = value;
}
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
public LyricsData()
@@ -120,5 +126,18 @@ namespace BetterLyrics.WinUI3.Models
},
]);
}
public LyricsLine? GetLyricsLine(double sec)
{
for (int i = 0; i < LyricsLines.Count; i++)
{
var line = LyricsLines[i];
if (line.StartMs > sec * 1000)
{
return LyricsLines.ElementAtOrDefault(i - 1);
}
}
return null;
}
}
}

View File

@@ -0,0 +1,20 @@
using BetterLyrics.WinUI3.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsSearchResult
{
public bool IsFound => !string.IsNullOrEmpty(Raw);
public LyricsSearchProvider? Provider { get; set; }
public string? Raw { get; set; }
public string? Title { get; set; }
public string? Artist { get; set; }
}
}

View File

@@ -0,0 +1,283 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Windowing;
using System;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models
{
public partial class LyricsWindowStatus : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Name { get; set; } = string.Empty;
[ObservableProperty] public partial bool IsDefault { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MonitorDeviceName { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsWorkArea { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsBorderless { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysOnTop { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysOnTopPolling { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsShownInSwitchers { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsClickThrough { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsLayoutOrientation LyricsLayoutOrientation { get; set; } = LyricsLayoutOrientation.Horizontal;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsDisplayType LyricsDisplayType { get; set; } = LyricsDisplayType.SplitView;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 800, 500);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double DockHeight { get; set; } = 64;
[ObservableProperty] public partial Rect DemoWindowBounds { get; set; }
[ObservableProperty] public partial Rect MonitorBounds { get; set; }
[ObservableProperty] public partial Rect DemoMonitorBounds { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockPlacement DockPlacement { get; set; } = DockPlacement.Top;
[ObservableProperty] public partial LyricsStyleSettings LyricsStyleSettings { get; set; } = new();
[ObservableProperty] public partial LyricsEffectSettings LyricsEffectSettings { get; set; } = new(500, 500, 500, EasingType.EaseInOutQuad);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsBackgroundSettings LyricsBackgroundSettings { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtLayoutSettings AlbumArtLayoutSettings { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAdaptToEnvironment { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial WindowPixelSampleMode EnvironmentSampleMode { get; set; } = WindowPixelSampleMode.WindowEdge;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoShowOrHideWindow { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TitleBarArea TitleBarArea { get; set; } = TitleBarArea.Top;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowX { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowY { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowWidth { get; set; } = 800;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowHeight { get; set; } = 500;
public LyricsWindowStatus()
{
UpdateMonitorNameAndBounds();
UpdateDemoWindowAndMonitorBounds();
}
partial void OnWindowXChanged(double value)
{
WindowBounds = WindowBounds.WithX(value);
}
partial void OnWindowYChanged(double value)
{
WindowBounds = WindowBounds.WithY(value);
}
partial void OnWindowWidthChanged(double value)
{
WindowBounds = WindowBounds.WithWidth(value);
}
partial void OnWindowHeightChanged(double value)
{
WindowBounds = WindowBounds.WithHeight(value);
}
partial void OnLyricsStyleSettingsChanged(LyricsStyleSettings oldValue, LyricsStyleSettings newValue)
{
oldValue.PropertyChanged -= OldLyricsStyleSettings_PropertyChanged;
newValue.PropertyChanged += OldLyricsStyleSettings_PropertyChanged;
}
partial void OnLyricsEffectSettingsChanged(LyricsEffectSettings oldValue, LyricsEffectSettings newValue)
{
oldValue.PropertyChanged -= OldLyricsEffectSettings_PropertyChanged;
newValue.PropertyChanged += OldLyricsEffectSettings_PropertyChanged;
}
partial void OnLyricsBackgroundSettingsChanged(LyricsBackgroundSettings oldValue, LyricsBackgroundSettings newValue)
{
oldValue.PropertyChanged -= OldLyricsBackgroundSettings_PropertyChanged;
newValue.PropertyChanged += OldLyricsBackgroundSettings_PropertyChanged;
}
partial void OnAlbumArtLayoutSettingsChanged(AlbumArtLayoutSettings oldValue, AlbumArtLayoutSettings newValue)
{
oldValue.PropertyChanged -= OldAlbumArtLayoutSettings_PropertyChanged;
newValue.PropertyChanged += OldAlbumArtLayoutSettings_PropertyChanged;
}
private void OldLyricsStyleSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(LyricsStyleSettings));
}
private void OldLyricsEffectSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(LyricsEffectSettings));
}
private void OldLyricsBackgroundSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(LyricsBackgroundSettings));
}
private void OldAlbumArtLayoutSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(AlbumArtLayoutSettings));
}
partial void OnWindowBoundsChanged(Rect value)
{
UpdateMonitorNameAndBounds();
UpdateDemoWindowAndMonitorBounds();
}
partial void OnAutoShowOrHideWindowChanged(bool value)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
}
public void UpdateMonitorNameAndBounds()
{
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow == null) return;
var mointor = MonitorHelper.GetMonitorInfoExFromWindow(lyricsWindow);
MonitorDeviceName = mointor.szDevice;
MonitorBounds = new Rect(
mointor.rcMonitor.Left,
mointor.rcMonitor.Top,
mointor.rcMonitor.Width,
mointor.rcMonitor.Height
);
}
public void UpdateDemoWindowAndMonitorBounds(double factor = 0.1)
{
DemoWindowBounds = new Rect(
(WindowBounds.X - MonitorBounds.Left) * factor,
(WindowBounds.Y - MonitorBounds.Top) * factor,
WindowBounds.Width * factor,
WindowBounds.Height * factor
);
DemoMonitorBounds = new Rect(
MonitorBounds.Left * factor,
MonitorBounds.Top * factor,
MonitorBounds.Width * factor,
MonitorBounds.Height * factor
);
}
public object Clone()
{
return new LyricsWindowStatus
{
Name = this.Name,
IsDefault = this.IsDefault,
MonitorDeviceName = this.MonitorDeviceName,
IsWorkArea = this.IsWorkArea,
IsBorderless = this.IsBorderless,
IsAlwaysOnTop = this.IsAlwaysOnTop,
IsAlwaysOnTopPolling = this.IsAlwaysOnTopPolling,
IsShownInSwitchers = this.IsShownInSwitchers,
IsClickThrough = this.IsClickThrough,
LyricsLayoutOrientation = this.LyricsLayoutOrientation,
LyricsDisplayType = this.LyricsDisplayType,
WindowBounds = this.WindowBounds,
DockHeight = this.DockHeight,
DemoWindowBounds = this.DemoWindowBounds,
MonitorBounds = this.MonitorBounds,
DemoMonitorBounds = this.DemoMonitorBounds,
LyricsStyleSettings = (LyricsStyleSettings)this.LyricsStyleSettings.Clone(),
LyricsEffectSettings = (LyricsEffectSettings)this.LyricsEffectSettings.Clone(),
LyricsBackgroundSettings = (LyricsBackgroundSettings)this.LyricsBackgroundSettings.Clone(),
AlbumArtLayoutSettings = (AlbumArtLayoutSettings)this.AlbumArtLayoutSettings.Clone(),
IsAdaptToEnvironment = this.IsAdaptToEnvironment,
EnvironmentSampleMode = this.EnvironmentSampleMode,
AutoShowOrHideWindow = this.AutoShowOrHideWindow,
TitleBarArea = this.TitleBarArea,
};
}
}
public static class LyricsWindowStatusExtensions
{
public static LyricsWindowStatus DesktopMode()
{
return new LyricsWindowStatus
{
Name = App.ResourceLoader!.GetString("DesktopMode"),
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
WindowBounds = new Rect(100, 100, 600, 250),
IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true,
IsBorderless = true,
IsClickThrough = true,
IsAdaptToEnvironment = true,
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
LyricsStyleSettings = new()
{
LyricsFontSize = 20,
LyricsAlignmentType = TextAlignmentType.Center,
},
LyricsBackgroundSettings = new LyricsBackgroundSettings
{
IsPureColorOverlayEnabled = false,
IsCoverOverlayEnabled = false,
}
};
}
public static LyricsWindowStatus DockedMode()
{
return new LyricsWindowStatus
{
Name = App.ResourceLoader!.GetString("DockedMode"),
IsWorkArea = true,
IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true,
IsBorderless = true,
IsAdaptToEnvironment = true,
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow,
TitleBarArea = TitleBarArea.None,
LyricsStyleSettings = new LyricsStyleSettings
{
LyricsAlignmentType = TextAlignmentType.Center,
LyricsFontSize = 18,
},
LyricsBackgroundSettings = new LyricsBackgroundSettings
{
IsCoverOverlayEnabled = false,
}
};
}
public static LyricsWindowStatus FullscreenMode(Rect monitorBounds)
{
return new LyricsWindowStatus
{
Name = App.ResourceLoader!.GetString("FullscreenMode"),
WindowBounds = monitorBounds,
IsAlwaysOnTop = true,
IsBorderless = true,
TitleBarArea = Enums.TitleBarArea.None,
LyricsLayoutOrientation = Enums.LyricsLayoutOrientation.Vertical,
LyricsStyleSettings = new LyricsStyleSettings
{
LyricsFontSize = 96,
LyricsAlignmentType = Enums.TextAlignmentType.Center,
},
AlbumArtLayoutSettings = new AlbumArtLayoutSettings
{
AutoAlbumArtSize = false,
AlbumArtSize = 148,
SongInfoFontSize = 48,
}
};
}
public static LyricsWindowStatus StandardMode()
{
return new LyricsWindowStatus
{
Name = App.ResourceLoader!.GetString("StandardMode"),
};
}
public static LyricsWindowStatus NarrowMode()
{
return new LyricsWindowStatus
{
Name = App.ResourceLoader!.GetString("NarrowMode"),
WindowBounds = new Rect(100, 100, 400, 800),
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
};
}
}
}

View File

@@ -0,0 +1,36 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public partial class MappedSongSearchQuery : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalTitle { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalArtist { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedTitle { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedArtist { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMarkedAsPureMusic { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider? LyricsSearchProvider { get; set; }
public MappedSongSearchQuery Clone()
{
return new MappedSongSearchQuery
{
OriginalTitle = this.OriginalTitle,
OriginalArtist = this.OriginalArtist,
MappedTitle = this.MappedTitle,
MappedArtist = this.MappedArtist,
IsMarkedAsPureMusic = this.IsMarkedAsPureMusic,
LyricsSearchProvider = this.LyricsSearchProvider
};
}
}
}

View File

@@ -12,26 +12,33 @@ namespace BetterLyrics.WinUI3.Models
{
public partial class MediaSourceProviderInfo : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLastFMTrackEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLastFMTrackEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTimelineSyncEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TimelineSyncThreshold { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PositionOffset { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ResetPositionOffsetOnSongChanged { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; } = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; } = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
public MediaSourceProviderInfo() { }
public MediaSourceProviderInfo(string provider) : base()
public MediaSourceProviderInfo()
{
Provider = string.Empty;
TimelineSyncThreshold = 0;
PositionOffset = 0;
}
public MediaSourceProviderInfo(string provider, bool isEnable = true)
{
IsEnabled = isEnable;
switch (provider)
{
case Constants.PlayerID.AppleMusic:
@@ -47,11 +54,12 @@ namespace BetterLyrics.WinUI3.Models
}
Provider = provider;
IsEnabled = true;
IsLastFMTrackEnabled = false;
ResetPositionOffsetOnSongChanged = false;
LyricsSearchProvidersInfo = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
AlbumArtSearchProvidersInfo = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
AlbumArtSearchProvidersInfo.ItemPropertyChanged += AlbumArtSearchProvidersInfo_ItemPropertyChanged;
AlbumArtSearchProvidersInfo.CollectionChanged += AlbumArtSearchProvidersInfo_CollectionChanged;
LyricsSearchProvidersInfo.ItemPropertyChanged += LyricsSearchProvidersInfo_ItemPropertyChanged;
LyricsSearchProvidersInfo.CollectionChanged += LyricsSearchProvidersInfo_CollectionChanged;
}
partial void OnAlbumArtSearchProvidersInfoChanged(FullyObservableCollection<AlbumArtSearchProviderInfo> oldValue, FullyObservableCollection<AlbumArtSearchProviderInfo> newValue)

View File

@@ -8,12 +8,32 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class AlbumArtLayoutSettings : ObservableRecipient
public partial class AlbumArtLayoutSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType SongInfoAlignmentType { get; set; } = TextAlignmentType.Left;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageRadius { get; set; } = 12; // 12 % of the cover image size
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageShadowAmount { get; set; } = 12;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SongInfoFontSize { get; set; } = 18;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowTitle { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowArtists { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int AlbumArtSize { get; set; } = 64;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoAlbumArtSize { get; set; } = true;
public AlbumArtLayoutSettings() { }
public object Clone()
{
return new AlbumArtLayoutSettings
{
SongInfoAlignmentType = this.SongInfoAlignmentType,
CoverImageRadius = this.CoverImageRadius,
CoverImageShadowAmount = this.CoverImageShadowAmount,
SongInfoFontSize = this.SongInfoFontSize,
ShowTitle = this.ShowTitle,
ShowArtists = this.ShowArtists,
AlbumArtSize = this.AlbumArtSize,
AutoAlbumArtSize = this.AutoAlbumArtSize,
};
}
}
}

View File

@@ -10,23 +10,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
public string Version { get; set; } = Helper.MetadataHelper.AppVersion;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings StandardLyricsStyleSettings { get; set; } = new LyricsStyleSettings(32, TextAlignmentType.Left, 0);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings DesktopLyricsStyleSettings { get; set; } = new LyricsStyleSettings(28, TextAlignmentType.Center, 2);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings DockLyricsStyleSettings { get; set; } = new LyricsStyleSettings(16, TextAlignmentType.Center, 0);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings PictureInPictureLyricsStyleSettings { get; set; } = new LyricsStyleSettings(28, TextAlignmentType.Left, 0);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings StandardLyricsEffectSettings { get; set; } = new LyricsEffectSettings(100, 500, 1000, EasingType.EaseInOutQuad);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings DesktopLyricsEffectSettings { get; set; } = new LyricsEffectSettings(500, 500, 500, EasingType.EaseInOutQuad);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings DockLyricsEffectSettings { get; set; } = new LyricsEffectSettings(500, 500, 500, EasingType.EaseInOutQuad);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings PictureInPictureLyricsEffectSettings { get; set; } = new LyricsEffectSettings(500, 500, 500, EasingType.EaseInOutQuad);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial StandardModeSettings StandardModeSettings { get; set; } = new StandardModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DesktopModeSettings DesktopModeSettings { get; set; } = new DesktopModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockModeSettings DockModeSettings { get; set; } = new DockModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PictureInPictureModeSettings PictureInPictureModeSettings { get; set; } = new PictureInPictureModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsBackgroundSettings LyricsBackgroundSettings { get; set; } = new LyricsBackgroundSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtLayoutSettings AlbumArtLayoutSettings { get; set; } = new AlbumArtLayoutSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TranslationSettings TranslationSettings { get; set; } = new TranslationSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial GeneralSettings GeneralSettings { get; set; } = new GeneralSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial MusicGallerySettings MusicGallerySettings { get; set; } = new MusicGallerySettings();
@@ -34,6 +17,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MappedSongSearchQuery> MappedSongSearchQueries { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsWindowStatus> WindowBoundsRecords { get; set; } = [];
public AppSettings() { }
}

View File

@@ -10,6 +10,5 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class BaseModeSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsDisplayType LyricsDisplayType { get; set; }
}
}

View File

@@ -1,24 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class DesktopModeSettings : BaseModeSettings
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 400, 200);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoLockOnDesktopMode { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> LockShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "U" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "D" };
public DesktopModeSettings()
{
LyricsDisplayType = Enums.LyricsDisplayType.LyricsOnly;
}
}
}

View File

@@ -1,24 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class DockModeSettings : BaseModeSettings
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockPlacement DockPlacement { get; set; } = DockPlacement.Top;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int DockWindowHeight { get; set; } = 64;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string DockMonitorDeviceName { get; set; } = MonitorHelper.GetPrimaryMonitorDeviceName();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string> { "Ctrl", "Shift", "D" };
public DockModeSettings()
{
LyricsDisplayType = LyricsDisplayType.LyricsOnly;
}
}
}

View File

@@ -10,14 +10,14 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class GeneralSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsWindowMode AutoStartWindowType { get; set; } = LyricsWindowMode.StandardMode;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Language Language { get; set; } = Language.FollowSystem;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IgnoreFullscreenWindow { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LXMusicServer { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool HideWindowWhenNotPlaying { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDragEverywhereEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsImmersiveMode { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ShowOrHideLyricsWindowShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "H" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ListenOnNewPlaybackSource { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> BorderlessShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "B" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ClickThroughShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "C" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> LyricsWindowSwitchShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "S" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> PlayOrPauseShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "P" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> NextSongShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "Right" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> PreviousSongShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "Left" };

View File

@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
@@ -8,15 +9,37 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsBackgroundSettings : ObservableRecipient
public partial class LyricsBackgroundSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ElementTheme LyricsBackgroundTheme { get; set; } = ElementTheme.Dark;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPureColorOverlayEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PureColorOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsCoverOverlayEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayBlurAmount { get; set; } = 100; // 100 % of the cover image size
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PureColorOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlaySpeed { get; set; } = 50; // 50 % of the base rotate speed
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverAcrylicEffectAmount { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFluidOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int FluidOverlayOpacity { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsSpectrumOverlayEnabled { get; set; } = false;
public LyricsBackgroundSettings() { }
public object Clone()
{
return new LyricsBackgroundSettings
{
LyricsBackgroundTheme = this.LyricsBackgroundTheme,
CoverOverlayBlurAmount = this.CoverOverlayBlurAmount,
CoverOverlayOpacity = this.CoverOverlayOpacity,
PureColorOverlayOpacity = this.PureColorOverlayOpacity,
CoverOverlaySpeed = this.CoverOverlaySpeed,
CoverAcrylicEffectAmount = this.CoverAcrylicEffectAmount,
};
}
}
}

View File

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsEffectSettings : ObservableRecipient
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBlurAmount { get; set; } = 5;
@@ -47,5 +47,29 @@ namespace BetterLyrics.WinUI3.Models.Settings
LyricsScrollBottomDuration = lyricsScrollBottomDuration;
LyricsScrollEasingType = lyricsScrollEasingType;
}
public object Clone()
{
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
{
LyricsBlurAmount = this.LyricsBlurAmount,
IsLyricsLineFadeEnabled = this.IsLyricsLineFadeEnabled,
IsLyricsGlowEffectEnabled = this.IsLyricsGlowEffectEnabled,
LyricsGlowEffectScope = this.LyricsGlowEffectScope,
LyricsGlowEffectAmount = this.LyricsGlowEffectAmount,
IsLyricsShadowEnabled = this.IsLyricsShadowEnabled,
LyricsShadowScope = this.LyricsShadowScope,
LyricsShadowAmount = this.LyricsShadowAmount,
LyricsHighlightScope = this.LyricsHighlightScope,
LyricsHighlightAmount = this.LyricsHighlightAmount,
LyricsTranslationHighlightAmount = this.LyricsTranslationHighlightAmount,
IsLyricsFloatAnimationEnabled = this.IsLyricsFloatAnimationEnabled,
LyricsFloatAmount = this.LyricsFloatAmount,
LyricsScrollTopDelay = this.LyricsScrollTopDelay,
LyricsScrollBottomDelay = this.LyricsScrollBottomDelay,
LyricsVerticalEdgeOpacity = this.LyricsVerticalEdgeOpacity,
IsFanLyricsEnabled = this.IsFanLyricsEnabled
};
}
}
}

View File

@@ -11,12 +11,13 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsStyleSettings : ObservableRecipient
public partial class LyricsStyleSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontSize { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType LyricsAlignmentType { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDynamicLyricsFontSize { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontSize { get; set; } = 24;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType LyricsAlignmentType { get; set; } = TextAlignmentType.Left;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBgFontOpacity { get; set; } = 30; // 30% opacity
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontStrokeWidth { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontStrokeWidth { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomBgFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomFgFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomStrokeFontColor { get; set; } = Colors.White;
@@ -36,5 +37,27 @@ namespace BetterLyrics.WinUI3.Models.Settings
LyricsAlignmentType = lyricsAlignmentType;
LyricsFontStrokeWidth = lyricsFontStrokeWidth;
}
public object Clone()
{
return new LyricsStyleSettings
{
IsDynamicLyricsFontSize = this.IsDynamicLyricsFontSize,
LyricsFontSize = this.LyricsFontSize,
LyricsAlignmentType = this.LyricsAlignmentType,
LyricsBgFontOpacity = this.LyricsBgFontOpacity,
LyricsFontStrokeWidth = this.LyricsFontStrokeWidth,
LyricsCustomBgFontColor = this.LyricsCustomBgFontColor,
LyricsCustomFgFontColor = this.LyricsCustomFgFontColor,
LyricsCustomStrokeFontColor = this.LyricsCustomStrokeFontColor,
LyricsBgFontColorType = this.LyricsBgFontColorType,
LyricsFgFontColorType = this.LyricsFgFontColorType,
LyricsStrokeFontColorType = this.LyricsStrokeFontColorType,
LyricsFontWeight = this.LyricsFontWeight,
LyricsLineSpacingFactor = this.LyricsLineSpacingFactor,
LyricsTranslationSeparator = this.LyricsTranslationSeparator,
LyricsFontFamily = this.LyricsFontFamily
};
}
}
}

View File

@@ -1,21 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class PictureInPictureModeSettings : BaseModeSettings
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Point WindowPosition { get; set; } = new Point(100, 100);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string>() { "Ctrl", "Shift", "P" };
public PictureInPictureModeSettings()
{
LyricsDisplayType = Enums.LyricsDisplayType.SplitView;
}
}
}

View File

@@ -1,23 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class StandardModeSettings : BaseModeSettings
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 1000, 600);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMaximized { get; set; } = false;
public StandardModeSettings()
{
LyricsDisplayType = LyricsDisplayType.SplitView;
}
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
@@ -14,7 +15,12 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LibreTranslateServer { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTranslationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowTranslationOnly { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SelectedTargetLanguageIndex { get; set; } = LanguageHelper.GetDefaultTargetLanguageIndex();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string SelectedTargetLanguageCode { get; set; } = LanguageHelper.GetDefaultTargetLanguageCode();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsJyutpingEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ChineseRomanization ChineseRomanization { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsChineseRomanizationEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsJapaneseRomanizationEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTraditionalChineseEnabled { get; set; } = false;
public TranslationSettings() { }
}

View File

@@ -21,11 +21,14 @@ namespace BetterLyrics.WinUI3.Models
public partial double? DurationMs { get; set; }
[ObservableProperty]
public partial string? SourceAppUserModelId { get; set; } = null;
public partial string? PlayerId { get; set; } = null;
[ObservableProperty]
public partial string Title { get; set; }
[ObservableProperty]
public partial string? SongId { get; set; } = null;
public SongInfo() { }
}
}

View File

@@ -14,6 +14,7 @@
<Grid>
<canvas:CanvasAnimatedControl
x:Name="LyricsCanvas"
CreateResources="LyricsCanvas_CreateResources"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
</Grid>

View File

@@ -2,7 +2,11 @@
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage;
using Windows.Storage.Streams;
namespace BetterLyrics.WinUI3.Renderer
{
@@ -31,5 +35,10 @@ namespace BetterLyrics.WinUI3.Renderer
LyricsCanvas.RemoveFromVisualTree();
LyricsCanvas = null;
}
private async void LyricsCanvas_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
await ViewModel.CreateResourcesAsync(sender);
}
}
}

View File

@@ -12,6 +12,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
@@ -30,12 +31,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
_iTunesHttpClinet = new();
}
public async Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null)
{
return await Task.Run(async () => await SearchAsyncCore(mediaSessionId, title, artist, album, bytesFromSMTC));
}
public async Task<byte[]?> SearchAsyncCore(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null)
public async Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token)
{
byte[]? result = null;
@@ -60,6 +56,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
{
result = await SearchiTunesAsync(artist, album, title, countryCode);
if (token.IsCancellationRequested) return result;
if (result != null) break;
}
break;

View File

@@ -2,12 +2,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public interface IAlbumArtSearchService
{
Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null);
Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token);
}
}

View File

@@ -10,5 +10,6 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
public interface ILiveStatesService
{
LiveStates LiveStates { get; set; }
void RefreshLyricsWindowStatus();
}
}

View File

@@ -1,110 +1,138 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using System;
using System.Collections.Generic;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.WinUI.Controls;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.LiveStatesService
{
public class LiveStatesService : BaseViewModel, ILiveStatesService,
IRecipient<PropertyChangedMessage<LyricsWindowMode>>,
IRecipient<PropertyChangedMessage<LyricsDisplayType>>
public partial class LiveStatesService : BaseViewModel, ILiveStatesService
{
private readonly ISettingsService _settingsService;
public LiveStates LiveStates { get; set; }
public LiveStates LiveStates { get; set; } = new();
public LiveStatesService(ISettingsService settingsService)
{
_settingsService = settingsService;
LiveStates = new LiveStates(_settingsService.AppSettings);
LiveStates.PropertyChanged += LiveStates_PropertyChanged;
LiveStates.PropertyChanging += LiveStates_PropertyChanging;
InitLyricsWindowStatus();
}
public void Receive(PropertyChangedMessage<LyricsWindowMode> message)
private void LiveStates_PropertyChanging(object? sender, System.ComponentModel.PropertyChangingEventArgs e)
{
if (message.Sender is LiveStates)
if (e.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
if (message.PropertyName == nameof(LiveStates.CurrentLyricsWindowMode))
{
switch (message.NewValue)
{
case LyricsWindowMode.StandardMode:
LiveStates.CurrentLyricsStyleSettings = _settingsService.AppSettings.StandardLyricsStyleSettings;
LiveStates.CurrentLyricsEffectSettings = _settingsService.AppSettings.StandardLyricsEffectSettings;
LiveStates.CurrentLyricsDisplayType = _settingsService.AppSettings.StandardModeSettings.LyricsDisplayType;
break;
case LyricsWindowMode.DockMode:
LiveStates.CurrentLyricsStyleSettings = _settingsService.AppSettings.DockLyricsStyleSettings;
LiveStates.CurrentLyricsEffectSettings = _settingsService.AppSettings.DockLyricsEffectSettings;
LiveStates.CurrentLyricsDisplayType = _settingsService.AppSettings.DockModeSettings.LyricsDisplayType;
break;
case LyricsWindowMode.DesktopMode:
LiveStates.CurrentLyricsStyleSettings = _settingsService.AppSettings.DesktopLyricsStyleSettings;
LiveStates.CurrentLyricsEffectSettings = _settingsService.AppSettings.DesktopLyricsEffectSettings;
LiveStates.CurrentLyricsDisplayType = _settingsService.AppSettings.DesktopModeSettings.LyricsDisplayType;
break;
case LyricsWindowMode.PictureInPictureMode:
LiveStates.CurrentLyricsStyleSettings = _settingsService.AppSettings.PictureInPictureLyricsStyleSettings;
LiveStates.CurrentLyricsEffectSettings = _settingsService.AppSettings.PictureInPictureLyricsEffectSettings;
LiveStates.CurrentLyricsDisplayType = _settingsService.AppSettings.PictureInPictureModeSettings.LyricsDisplayType;
break;
default:
break;
}
}
LiveStates.LyricsWindowStatus.PropertyChanged -= LyricsWindowStatus_PropertyChanged;
}
}
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
private void LyricsWindowStatus_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (message.Sender is StandardModeSettings)
switch (e.PropertyName)
{
if (message.PropertyName == nameof(StandardModeSettings.LyricsDisplayType))
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.StandardMode)
case nameof(LyricsWindowStatus.IsWorkArea):
WindowHelper.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
LiveStates.CurrentLyricsDisplayType = message.NewValue;
UpdateWindowBoundsWhenWorkArea();
}
}
break;
case nameof(LyricsWindowStatus.DockHeight):
case nameof(LyricsWindowStatus.DockPlacement):
case nameof(LyricsWindowStatus.MonitorDeviceName):
WindowHelper.UpdateWorkArea<LyricsWindow>();
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
UpdateWindowBoundsWhenWorkArea();
}
break;
case nameof(LyricsWindowStatus.IsShownInSwitchers):
WindowHelper.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
break;
case nameof(LyricsWindowStatus.IsAlwaysOnTop):
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
break;
case nameof(LyricsWindowStatus.IsClickThrough):
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
break;
case nameof(LyricsWindowStatus.IsBorderless):
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
break;
case nameof(LyricsWindowStatus.WindowBounds):
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
break;
case nameof(LyricsWindowStatus.TitleBarArea):
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
break;
default:
break;
}
else if (message.Sender is DockModeSettings)
}
private void LiveStates_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
if (message.PropertyName == nameof(DockModeSettings.LyricsDisplayType))
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode)
{
LiveStates.CurrentLyricsDisplayType = message.NewValue;
}
}
LiveStates.LyricsWindowStatus.PropertyChanged += LyricsWindowStatus_PropertyChanged;
RefreshLyricsWindowStatus();
}
else if (message.Sender is DesktopModeSettings)
}
private void InitLyricsWindowStatus()
{
var defaultLyricsWindowStatus = _settingsService.AppSettings.WindowBoundsRecords.FirstOrDefault(x => x.IsDefault);
if (defaultLyricsWindowStatus == null)
{
if (message.PropertyName == nameof(DesktopModeSettings.LyricsDisplayType))
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DesktopMode)
{
LiveStates.CurrentLyricsDisplayType = message.NewValue;
}
}
defaultLyricsWindowStatus = LyricsWindowStatusExtensions.StandardMode();
defaultLyricsWindowStatus.IsDefault = true;
_settingsService.AppSettings.WindowBoundsRecords.Add(defaultLyricsWindowStatus);
}
else if (message.Sender is PictureInPictureModeSettings)
LiveStates.LyricsWindowStatus = defaultLyricsWindowStatus;
}
public void RefreshLyricsWindowStatus()
{
WindowHelper.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
if (message.PropertyName == nameof(PictureInPictureModeSettings.LyricsDisplayType))
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.PictureInPictureMode)
{
LiveStates.CurrentLyricsDisplayType = message.NewValue;
}
}
UpdateWindowBoundsWhenWorkArea();
}
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
LiveStates.LyricsWindowStatus.UpdateMonitorNameAndBounds();
LiveStates.LyricsWindowStatus.UpdateDemoWindowAndMonitorBounds();
WindowHelper.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
}
private void UpdateWindowBoundsWhenWorkArea()
{
LiveStates.LyricsWindowStatus.WindowBounds = new Windows.Foundation.Rect(
LiveStates.LyricsWindowStatus.MonitorBounds.X,
LiveStates.LyricsWindowStatus.DockPlacement switch
{
Enums.DockPlacement.Top => LiveStates.LyricsWindowStatus.MonitorBounds.Top,
Enums.DockPlacement.Bottom => LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - LiveStates.LyricsWindowStatus.DockHeight - 1,
_ => LiveStates.LyricsWindowStatus.MonitorBounds.Top,
},
LiveStates.LyricsWindowStatus.MonitorBounds.Width,
LiveStates.LyricsWindowStatus.DockPlacement switch
{
Enums.DockPlacement.Top => LiveStates.LyricsWindowStatus.DockHeight,
Enums.DockPlacement.Bottom => LiveStates.LyricsWindowStatus.DockHeight + 1,
_ => LiveStates.LyricsWindowStatus.DockHeight,
}
);
}
}
}

View File

@@ -1,14 +1,18 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
public interface ILyricsSearchService
{
Task<(string?, LyricsSearchProvider?)> SearchAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token);
Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, string? songId, CancellationToken token);
Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token);
}
}

View File

@@ -4,6 +4,7 @@ using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Providers.Web.Kugou;
@@ -11,6 +12,7 @@ using Lyricify.Lyrics.Searchers;
using Microsoft.Extensions.Logging;
using NTextCat.Commons;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -26,6 +28,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
private readonly HttpClient _amllTtmlDbHttpClient;
private readonly HttpClient _lrcLibHttpClient;
private readonly AppleMusic _appleMusic;
private readonly ISettingsService _settingsService;
private readonly ILogger _logger;
@@ -38,9 +41,10 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GithubUrl})"
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHubUrl})"
);
_amllTtmlDbHttpClient = new();
_appleMusic = new AppleMusic();
}
private static bool IsAmllTtmlDbIndexInvalid()
@@ -87,94 +91,161 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
public async Task<(string?, LyricsSearchProvider?)> SearchAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token)
public async Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, string? songId, CancellationToken token)
{
return await Task.Run(async () => await SearchAsyncCore(mediaSessionId, title, artist, album, durationMs, token), token);
var lyricsSearchResult = new LyricsSearchResult();
string overridenTitle = title;
string overridenArtist = artist;
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
var found = _settingsService.AppSettings.MappedSongSearchQueries
.Where(x => x.OriginalTitle == title && x.OriginalArtist == artist)
.FirstOrDefault();
if (found != null)
{
overridenTitle = found.MappedTitle;
overridenArtist = found.MappedArtist;
_logger.LogInformation("Found mapped song search query: {MappedSongSearchQuery}", found);
var pureMusic = found.IsMarkedAsPureMusic;
if (pureMusic)
{
lyricsSearchResult.Title = overridenTitle;
lyricsSearchResult.Artist = overridenArtist;
lyricsSearchResult.Raw = "[99:00.000]🎶🎶🎶";
return lyricsSearchResult;
}
var targetProvider = found.LyricsSearchProvider;
if (targetProvider != null)
{
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, album, durationMs, songId, token);
}
}
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.LyricsSearchProvidersInfo ?? [])
{
if (!provider.IsEnabled)
{
continue;
}
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, album, durationMs, null, token);
if (lyricsSearchResult.IsFound)
{
return lyricsSearchResult;
}
}
return lyricsSearchResult;
}
private async Task<(string?, LyricsSearchProvider?)> SearchAsyncCore(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token)
public async Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token)
{
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
_logger.LogInformation("Searching all lyrics for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
var results = new List<LyricsSearchResult>();
foreach (var provider in Enum.GetValues<LyricsSearchProvider>())
{
var searchResult = await SearchSingleAsync(provider, title, artist, album, durationMs, null, token);
results.Add(searchResult);
}
return results;
}
private async Task<LyricsSearchResult> SearchSingleAsync(LyricsSearchProvider provider, string title, string artist, string album, double durationMs, string? songId, CancellationToken token)
{
var lyricsSearchResult = new LyricsSearchResult
{
Provider = provider,
};
try
{
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.LyricsSearchProvidersInfo ?? [])
LyricsFormat lyricsFormat = provider.GetLyricsFormat();
// Check cache first
if (provider.IsRemote())
{
if (!provider.IsEnabled)
var cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
continue;
lyricsSearchResult.Raw = cachedLyrics;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
return lyricsSearchResult;
}
}
string? cachedLyrics;
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
// Check cache first
if (provider.Provider.IsRemote())
if (provider.IsLocal())
{
if (provider == LyricsSearchProvider.LocalMusicFile)
{
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return (cachedLyrics, provider.Provider);
}
}
string? searchedLyrics = null;
if (provider.Provider.IsLocal())
{
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
{
searchedLyrics = SearchEmbedded(title, artist);
}
else
{
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
}
lyricsSearchResult = SearchEmbedded(title, artist);
}
else
{
switch (provider.Provider)
{
case LyricsSearchProvider.LrcLib:
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
break;
case LyricsSearchProvider.QQ:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
break;
default:
break;
}
lyricsSearchResult = await SearchFile(title, artist, lyricsFormat);
}
token.ThrowIfCancellationRequested();
if (!string.IsNullOrWhiteSpace(searchedLyrics))
}
else
{
switch (provider)
{
if (provider.Provider.IsRemote())
{
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
}
case LyricsSearchProvider.LrcLib:
lyricsSearchResult = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
break;
case LyricsSearchProvider.QQ:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
lyricsSearchResult = await SearchAmllTtmlDbAsync(title, artist);
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(title, artist, album, (int)durationMs);
break;
default:
break;
}
}
return (searchedLyrics, provider.Provider);
if (token.IsCancellationRequested)
{
return lyricsSearchResult;
}
if (lyricsSearchResult.IsFound)
{
if (provider.IsRemote())
{
FileHelper.WriteLyricsCache(title, artist, lyricsSearchResult.Raw!, lyricsFormat, provider.GetCacheDirectory());
}
}
}
catch (Exception) { }
catch (Exception)
{
}
return (null, null);
return lyricsSearchResult;
}
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
private async Task<LyricsSearchResult> SearchFile(string title, string artist, LyricsFormat format)
{
var lyricsSearchResult = new LyricsSearchResult
{
Provider = format.ToLyricsSearchProvider(),
};
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
@@ -188,7 +259,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
if (raw != null)
{
return raw;
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
}
}
}
@@ -198,11 +271,16 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
}
return null;
return lyricsSearchResult;
}
private string? SearchEmbedded(string title, string artist)
private LyricsSearchResult SearchEmbedded(string title, string artist)
{
var lyricsSearchResult = new LyricsSearchResult
{
Provider = LyricsSearchProvider.LocalMusicFile,
};
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
@@ -217,23 +295,32 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
var plain = TagLib.File.Create(file).Tag.Lyrics;
if (!plain.IsNullOrEmpty())
{
return plain;
lyricsSearchResult.Raw = plain;
lyricsSearchResult.Title = track.Title;
lyricsSearchResult.Artist = artist;
}
}
}
}
}
}
return null;
return lyricsSearchResult;
}
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
private async Task<LyricsSearchResult> SearchAmllTtmlDbAsync(string title, string artist)
{
var lyricsSearchResult = new LyricsSearchResult
{
Provider = LyricsSearchProvider.AmllTtmlDb,
};
if (IsAmllTtmlDbIndexInvalid())
{
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
if (!downloadOk)
return null;
{
return lyricsSearchResult;
}
}
string? rawLyricFile = null;
@@ -276,7 +363,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
if (string.IsNullOrWhiteSpace(rawLyricFile))
return null;
{
return lyricsSearchResult;
}
// 下载歌词内容
var url = $"{Constants.AmllTTmlDB.QueryPrefix}{rawLyricFile}";
@@ -284,17 +373,29 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
using var response = await _amllTtmlDbHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
return await response.Content.ReadAsStringAsync();
{
return lyricsSearchResult;
}
string lyrics = await response.Content.ReadAsStringAsync();
lyricsSearchResult.Raw = lyrics;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
}
catch
{
return null;
}
return lyricsSearchResult;
}
private async Task<string?> SearchLrcLibAsync(string title, string artist, string album, int duration)
private async Task<LyricsSearchResult> SearchLrcLibAsync(string title, string artist, string album, int duration)
{
var lyricsSearchResult = new LyricsSearchResult
{
Provider = LyricsSearchProvider.LrcLib,
};
// Build API query URL
var url =
$"https://lrclib.net/api/search?" +
@@ -305,7 +406,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
using var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
{
return lyricsSearchResult;
}
var json = await response.Content.ReadAsStringAsync();
@@ -313,31 +416,64 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
json,
Serialization.SourceGenerationContext.Default.JsonElement
);
string? original = null;
string? searchedTitle = null;
string? searchedArtist = null;
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
{
var first = jArr[0];
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
if (!string.IsNullOrWhiteSpace(result))
{
return result;
}
original = first.GetProperty("syncedLyrics").GetString();
searchedTitle = first.GetProperty("trackName").GetString();
searchedArtist = first.GetProperty("artistName").GetString();
}
return null;
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = searchedTitle;
lyricsSearchResult.Artist = searchedArtist;
return lyricsSearchResult;
}
private static async Task<string?> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, Searchers searchers)
private static async Task<LyricsSearchResult> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, string? songId, Searchers searchers)
{
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs = durationMs,
Album = album,
Artists = [artist],
Title = title,
}
);
var lyricsSearchResult = new LyricsSearchResult();
switch (searchers)
{
case Searchers.QQMusic:
lyricsSearchResult.Provider = LyricsSearchProvider.QQ;
break;
case Searchers.Netease:
lyricsSearchResult.Provider = LyricsSearchProvider.Netease;
break;
case Searchers.Kugou:
lyricsSearchResult.Provider = LyricsSearchProvider.Kugou;
break;
case Searchers.Musixmatch:
break;
default:
break;
}
ISearchResult? result;
if (searchers == Searchers.Netease && songId != null)
{
result = new NeteaseSearchResult(title, [artist], album, null, durationMs, songId);
}
else
{
result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs = durationMs,
Album = album,
Artists = [artist],
Title = title,
}
);
}
if (result is QQMusicSearchResult qqResult)
{
@@ -354,11 +490,15 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
PathHelper.QQTranslationCacheDirectory
);
}
return original;
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = qqResult.Title;
lyricsSearchResult.Artist = qqResult.Artists.Join(" | ");
}
else if (result is NeteaseSearchResult neteaseResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
var original = response?.Lrc?.Lyric;
var translated = response?.Tlyric?.Lyric;
if (!string.IsNullOrEmpty(translated))
{
@@ -370,21 +510,59 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
PathHelper.NeteaseTranslationCacheDirectory
);
}
return response?.Lrc.Lyric;
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = neteaseResult.Title;
lyricsSearchResult.Artist = neteaseResult.Artists.Join(" | ");
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
string? original = null;
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
{
return Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(
candidate.Id,
candidate.AccessKey
);
original = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.Id, candidate.AccessKey);
if (candidate.TransId != null)
{
string? translated = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.TransId, candidate.AccessKey);
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
translated,
LyricsFormat.Lrc,
PathHelper.KugouTranslationCacheDirectory
);
}
}
}
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = kugouResult.Title;
lyricsSearchResult.Artist = kugouResult.Artists.Join(" | ");
}
return null;
return lyricsSearchResult;
}
private async Task<LyricsSearchResult> SearchAppleMusicAsync(string title, string artist, string album, int durationMs)
{
var lyricsSearchResult = new LyricsSearchResult
{
Provider = LyricsSearchProvider.AppleMusic,
};
if (await _appleMusic.InitAsync())
{
var raw = await _appleMusic.GetLyricsAsync(title, artist);
_logger.LogInformation("Apple Music lyrics search result for {Title} - {Artist}: {Raw}", title, artist, raw ?? "null");
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
}
return lyricsSearchResult;
}
}
}

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