Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b043f9acd0 | ||
|
|
7b2ff0cc8f | ||
|
|
7c9ab73a34 | ||
|
|
a08bf91784 | ||
|
|
61906670fd | ||
|
|
430b2f4d28 | ||
|
|
eb05c1ea13 | ||
|
|
9bebf36e6a | ||
|
|
d2b0b6afb1 | ||
|
|
9c2f4fbff9 | ||
|
|
1b69493afd | ||
|
|
c524dc013c | ||
|
|
9ca5939e57 | ||
|
|
860abd4037 | ||
|
|
618415016f | ||
|
|
bfcba1425d | ||
|
|
0dc9ebf18e | ||
|
|
366d396b93 | ||
|
|
35ca28ac7b | ||
|
|
1505933107 | ||
|
|
958227d0f2 | ||
|
|
b1978fec09 | ||
|
|
010040b376 | ||
|
|
35272d8324 | ||
|
|
29a714fe87 | ||
|
|
78edc2b3ce | ||
|
|
932f9d3a4f | ||
|
|
a56a7af08c | ||
|
|
5427c1992f | ||
|
|
64d69f44c3 | ||
|
|
d75fe4b27a | ||
|
|
eac9b3e28a | ||
|
|
d6975ba2d4 | ||
|
|
aae8e1322a | ||
|
|
88aac0eaf0 | ||
|
|
c463d9bc5c | ||
|
|
f305b469f2 | ||
|
|
ea276f3e2e | ||
|
|
15601e6ee4 | ||
|
|
44b14cab17 | ||
|
|
0a97c56c77 | ||
|
|
ac5dc5991f | ||
|
|
d22dc673e8 | ||
|
|
9eb2b83796 | ||
|
|
1cf6226a06 | ||
|
|
bce330a9e0 | ||
|
|
687e286c2e | ||
|
|
fc05035053 | ||
|
|
f45f5ead01 | ||
|
|
0eeea77896 | ||
|
|
78ca7d8435 | ||
|
|
98d2ac404d | ||
|
|
b4b1ffd58e | ||
|
|
2522bd00ab | ||
|
|
0601039fcf | ||
|
|
857b95e525 | ||
|
|
4be855f11a | ||
|
|
9e1018770d | ||
|
|
1235a09d19 | ||
|
|
1647c3a2f1 | ||
|
|
588838acaa | ||
|
|
25c772434c | ||
|
|
2c597a3b37 | ||
|
|
80a44977a6 | ||
|
|
0389fa6a56 | ||
|
|
30ca476d8d | ||
|
|
6439dee5ef | ||
|
|
f6e5a24fe4 | ||
|
|
77aad546bc | ||
|
|
7fdbe664ba | ||
|
|
df76074ce9 | ||
|
|
31939630a3 | ||
|
|
50500626f8 | ||
|
|
fb1f0c8fc7 | ||
|
|
9036b3be5f | ||
|
|
51f840c0ef | ||
|
|
b3a98cdaa2 | ||
|
|
7799a8dd94 | ||
|
|
e84d1faf71 | ||
|
|
66f0fd0f8f | ||
|
|
4f0dbe4836 | ||
|
|
d7a53e360a | ||
|
|
cb76341666 | ||
|
|
537584e557 | ||
|
|
e06a50d8e8 | ||
|
|
01776ed9a8 | ||
|
|
1b7f53c055 | ||
|
|
3233d35a05 | ||
|
|
ee52dafa0b | ||
|
|
03ddf42152 | ||
|
|
bb4ee6bae7 | ||
|
|
cb5a9f8042 | ||
|
|
1d5d3bfa72 | ||
|
|
06379ebaba | ||
|
|
45eb2a1506 | ||
|
|
0e96c35d2d | ||
|
|
152ecc7ff0 | ||
|
|
8b38aa1e33 | ||
|
|
79e6995384 | ||
|
|
1d6e19a718 | ||
|
|
23cb4b11a9 | ||
|
|
11461a615b | ||
|
|
e8f0359fb2 | ||
|
|
eba81e8be3 | ||
|
|
2e0d437b08 | ||
|
|
cbb0b87392 | ||
|
|
3f63043150 | ||
|
|
77f2474562 | ||
|
|
8aa5c392df | ||
|
|
a453f4862d | ||
|
|
0e6702f3c3 | ||
|
|
412cdbdaf4 | ||
|
|
02dbbf983c | ||
|
|
8cf9830644 | ||
|
|
b04dfedf7e | ||
|
|
3d4e3209e6 | ||
|
|
e92130af85 | ||
|
|
de3d6f1695 | ||
|
|
da377838e8 | ||
|
|
67cf6e47c8 | ||
|
|
abf4c3498f | ||
|
|
ff8c85b2d0 | ||
|
|
8cbdb32931 | ||
|
|
757f9f4156 | ||
|
|
c632f4b01a | ||
|
|
a843d0a0e3 | ||
|
|
905df92b05 | ||
|
|
7445299537 | ||
|
|
ba4958f837 | ||
|
|
8ca5245bf5 | ||
|
|
89aa97552a | ||
|
|
aa692e2735 | ||
|
|
c7ee26f284 | ||
|
|
b103e6efd1 | ||
|
|
16cd12e22b | ||
|
|
34bdbc89bc | ||
|
|
b649e9761d | ||
|
|
a9807f4f09 | ||
|
|
def287715d | ||
|
|
966f926112 | ||
|
|
4568293b51 | ||
|
|
10115ab0a8 | ||
|
|
ecefaedcb9 | ||
|
|
b9aae0866d | ||
|
|
afbfcc921e | ||
|
|
8f997ca3d9 | ||
|
|
042d557eb1 | ||
|
|
153679228d | ||
|
|
5a4fe54ac2 | ||
|
|
14e8d88cd1 | ||
|
|
79e726db33 | ||
|
|
d4f1949833 | ||
|
|
6135f996bf | ||
|
|
5b97621af4 | ||
|
|
0f084d04f8 | ||
|
|
bcb114a171 | ||
|
|
7bbe2a3045 | ||
|
|
fab4e8fe22 | ||
|
|
bc4a06577e | ||
|
|
22a790fff0 | ||
|
|
5e3d0cb78f | ||
|
|
59ee2a6fc3 | ||
|
|
fd5e752b43 | ||
|
|
df31d03da7 | ||
|
|
a7ebba4a62 | ||
|
|
6ca1a84a54 | ||
|
|
bab5a827f6 | ||
|
|
3b1e0389aa | ||
|
|
ae05a55d31 | ||
|
|
4b6d417db3 | ||
|
|
86118bac02 | ||
|
|
7bfbec4b01 | ||
|
|
a5d6dd1305 | ||
|
|
68f690e1a7 | ||
|
|
34d7f3f319 | ||
|
|
07b82191d0 | ||
|
|
f8c6060d32 | ||
|
|
bfdb36ff95 | ||
|
|
ce83777c1d | ||
|
|
d709e70fa2 | ||
|
|
8fe4f8fd58 | ||
|
|
b6319e522a | ||
|
|
58d74c1515 | ||
|
|
806f3fdd63 | ||
|
|
90d2055dff |
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jayfunc]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
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']
|
||||
18
.github/workflows/issues-translator.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: 'issue-translator'
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: usthe/issues-translate-action@v2.7
|
||||
with:
|
||||
IS_MODIFY_TITLE: false
|
||||
# not require, default false, . Decide whether to modify the issue title
|
||||
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
|
||||
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
|
||||
# not require. Customize the translation robot prefix message.
|
||||
28
.github/workflows/release-to-telegram.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Notify Telegram on GitHub Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send release info to Telegram
|
||||
env:
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
|
||||
RELEASE_URL: ${{ github.event.release.html_url }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
RELEASE_NAME: ${{ github.event.release.name }}
|
||||
RELEASE_BODY: ${{ github.event.release.body }}
|
||||
run: |
|
||||
TEXT="🚀 *New Release:* ${RELEASE_TAG} - ${RELEASE_NAME}%0A"
|
||||
TEXT+="📝 *Description:*%0A${RELEASE_BODY}%0A"
|
||||
TEXT+="🔗 [View on GitHub](${RELEASE_URL})"
|
||||
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
|
||||
-d chat_id="${CHAT_ID}" \
|
||||
-d message_thread_id="${THREAD_ID}" \
|
||||
-d text="${TEXT}"
|
||||
22
.github/workflows/releases-to-discord.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Notify Discord on GitHub Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: GitHub Releases to Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1
|
||||
with:
|
||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||
color: "2105893"
|
||||
username: "GitHub"
|
||||
avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
|
||||
content: "||@everyone||"
|
||||
footer_title: "Changelog"
|
||||
reduce_headings: true
|
||||
1
.gitignore
vendored
@@ -406,3 +406,4 @@ FodyWeavers.xsd
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
|
||||
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LastFM.cs
|
||||
|
||||
@@ -1,149 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
|
||||
<DefaultLanguage>zh-CN</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
|
||||
<Content Include="Images\LargeTile.scale-100.png" />
|
||||
<Content Include="Images\LargeTile.scale-125.png" />
|
||||
<Content Include="Images\LargeTile.scale-150.png" />
|
||||
<Content Include="Images\LargeTile.scale-200.png" />
|
||||
<Content Include="Images\LargeTile.scale-400.png" />
|
||||
<Content Include="Images\SmallTile.scale-100.png" />
|
||||
<Content Include="Images\SmallTile.scale-125.png" />
|
||||
<Content Include="Images\SmallTile.scale-150.png" />
|
||||
<Content Include="Images\SmallTile.scale-200.png" />
|
||||
<Content Include="Images\SmallTile.scale-400.png" />
|
||||
<Content Include="Images\SplashScreen.scale-100.png" />
|
||||
<Content Include="Images\SplashScreen.scale-125.png" />
|
||||
<Content Include="Images\SplashScreen.scale-150.png" />
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\SplashScreen.scale-400.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-100.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-125.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-150.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
|
||||
<Content Include="Images\StoreLogo.scale-100.png" />
|
||||
<Content Include="Images\StoreLogo.scale-125.png" />
|
||||
<Content Include="Images\StoreLogo.scale-150.png" />
|
||||
<Content Include="Images\StoreLogo.scale-200.png" />
|
||||
<Content Include="Images\StoreLogo.scale-400.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-400.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
|
||||
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
|
||||
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
|
||||
<DefaultLanguage>zh-CN</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
|
||||
<Content Include="Images\LargeTile.scale-100.png" />
|
||||
<Content Include="Images\LargeTile.scale-125.png" />
|
||||
<Content Include="Images\LargeTile.scale-150.png" />
|
||||
<Content Include="Images\LargeTile.scale-200.png" />
|
||||
<Content Include="Images\LargeTile.scale-400.png" />
|
||||
<Content Include="Images\SmallTile.scale-100.png" />
|
||||
<Content Include="Images\SmallTile.scale-125.png" />
|
||||
<Content Include="Images\SmallTile.scale-150.png" />
|
||||
<Content Include="Images\SmallTile.scale-200.png" />
|
||||
<Content Include="Images\SmallTile.scale-400.png" />
|
||||
<Content Include="Images\SplashScreen.scale-100.png" />
|
||||
<Content Include="Images\SplashScreen.scale-125.png" />
|
||||
<Content Include="Images\SplashScreen.scale-150.png" />
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\SplashScreen.scale-400.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-100.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-125.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-150.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
|
||||
<Content Include="Images\StoreLogo.scale-100.png" />
|
||||
<Content Include="Images\StoreLogo.scale-125.png" />
|
||||
<Content Include="Images\StoreLogo.scale-150.png" />
|
||||
<Content Include="Images\StoreLogo.scale-200.png" />
|
||||
<Content Include="Images\StoreLogo.scale-400.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-400.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
|
||||
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
</Project>
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.9.0" />
|
||||
Version="1.0.45.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@@ -47,6 +48,11 @@
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
|
||||
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
@@ -64,7 +70,7 @@
|
||||
</Style>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,9,16,11" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
@@ -77,11 +83,14 @@
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Style
|
||||
x:Key="TitleBarToggleButtonStyle"
|
||||
BasedOn="{StaticResource ToggleButtonRevealStyle}"
|
||||
TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Padding" Value="16,9,16,11" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
@@ -91,11 +100,212 @@
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="CardGridStyle" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GhostSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ManipulationMode" Value="None" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Slider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0,1,1,0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource SliderTopHeaderMargin}"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
|
||||
Foreground="{ThemeResource SliderHeaderForeground}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="SliderContainer"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="TopTickBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="HorizontalInlineTickBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="BottomTickBar"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Height="2"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="VerticalTemplate"
|
||||
MinWidth="{ThemeResource SliderVerticalWidth}"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="LeftTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="VerticalInlineTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="RightTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="24"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ListViewStretchedItemContainerStyle" TargetType="ListViewItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
|
||||
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -3,20 +3,33 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LibWatcherService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
|
||||
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Serilog;
|
||||
using ShadowViewer.Controls;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
@@ -33,6 +46,8 @@ namespace BetterLyrics.WinUI3
|
||||
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
|
||||
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
|
||||
|
||||
private static Mutex? _instanceMutex;
|
||||
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -41,8 +56,10 @@ namespace BetterLyrics.WinUI3
|
||||
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
EnsureSingleInstance();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
AppInfo.EnsureDirectories();
|
||||
PathHelper.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
@@ -53,30 +70,35 @@ namespace BetterLyrics.WinUI3
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
private void EnsureSingleInstance()
|
||||
{
|
||||
bool createdNew;
|
||||
_instanceMutex = new Mutex(true, Constants.App.AppName, out createdNew);
|
||||
|
||||
if (!createdNew)
|
||||
{
|
||||
User32.MessageBox(HWND.NULL, ResourceLoader!.GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
WindowHelper.OpenWindow<LyricsWindow>();
|
||||
|
||||
string[] commandLineArguments = Environment.GetCommandLineArgs();
|
||||
if (commandLineArguments.Length > 1)
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
commandLineArguments = commandLineArguments.Skip(1).ToArray();
|
||||
if (commandLineArguments.First() == AppInfo.UnlockWindowTag)
|
||||
{
|
||||
lyricsWindow.AutoSelectLyricsMode(AutoStartWindowType.DesktopMode, false);
|
||||
return;
|
||||
}
|
||||
lyricsWindow.ViewModel.InitLockHotKey();
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
|
||||
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
// Register services
|
||||
@@ -89,16 +111,19 @@ namespace BetterLyrics.WinUI3
|
||||
})
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IPlaybackService, PlaybackService>()
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
|
||||
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
|
||||
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
.AddSingleton<ILibreTranslateService, LibreTranslateService>()
|
||||
.AddSingleton<ITranslateService, TranslateService>()
|
||||
.AddSingleton<ILastFMService, LastFMService>()
|
||||
// ViewModels
|
||||
.AddSingleton<LyricsWindowViewModel>()
|
||||
.AddSingleton<SettingsWindowViewModel>()
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
.AddSingleton<SettingsPageViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<MusicGalleryViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
|
||||
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/AIMP.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/AppleMusic.png
Normal file
|
After Width: | Height: | Size: 836 B |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Chrome.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Discord.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Edge.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyBox.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyState.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/KugouMusic.png
Normal file
|
After Width: | Height: | Size: 304 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/LXMusic.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/LastFM.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Leaf.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 260 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/MusicBee.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/PotPlayer.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/QQ.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/QQMusic.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Question.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Spotify.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Telegram.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
328503
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Wiki82.profile.xml
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/foobar2000.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/iTunes.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
@@ -1,116 +1,226 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="iTunesSearch" Version="1.0.44" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Segoe Fluent Icons.ttf" />
|
||||
<None Remove="Assets\Wiki82.profile.xml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\MusicGalleryPage.xaml" />
|
||||
<None Remove="Views\MusicGalleryWindow.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
|
||||
<PackageReference Include="ColorThief.ImageSharp" Version="1.0.0" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.MetadataControl" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
<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="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<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" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<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" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Hqub.Lastfm">
|
||||
<HintPath>..\..\..\Last.fm\src\Hqub.Lastfm\bin\Release\netstandard2.0\Hqub.Lastfm.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\AIMP.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\AppleMusic.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Chrome.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Discord.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Edge.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\EmptyBox.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\EmptyState.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\foobar2000.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\iTunes.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\KugouMusic.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\LastFM.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Leaf.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Logo.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Logo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\LXMusic.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\MediaPlayerWindows11.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">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\QQ.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\QQMusic.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Question.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\MusicGalleryWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MusicGalleryPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class AmllTTmlDB
|
||||
{
|
||||
private const string BaseUrl = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/";
|
||||
public const string QueryPrefix = $"{BaseUrl}raw-lyrics/";
|
||||
public const string Index = $"{BaseUrl}metadata/raw-lyrics-index.jsonl";
|
||||
}
|
||||
}
|
||||
16
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/App.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class App
|
||||
{
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string AppName = "BetterLyrics";
|
||||
|
||||
public const string AutoStartupTaskId = "AutoStartup";
|
||||
}
|
||||
}
|
||||
13
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LXMusic.cs
Normal 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 LXMusic
|
||||
{
|
||||
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class LastFM
|
||||
{
|
||||
public const string ApiKey = "Your api key here";
|
||||
public const string SharedSecret = "Your shared secret here";
|
||||
public const string UnAuthUrl = "https://www.last.fm/settings/applications";
|
||||
}
|
||||
}
|
||||
16
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/Link.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class Link
|
||||
{
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class PlayerID
|
||||
{
|
||||
public const string LXMusic = "cn.toside.music.desktop";
|
||||
public const string MediaPlayerWindows11 = "Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic";
|
||||
public const string AIMP = "AIMP.exe";
|
||||
public const string Foobar2000 = "foobar2000.exe";
|
||||
public const string MusicBee = "MusicBee.exe";
|
||||
public const string PotPlayer = "PotPlayerMini64.exe";
|
||||
public const string Spotify = "Spotify.exe";
|
||||
public const string AppleMusic = "AppleInc.AppleMusicWin_nzyj5cx40ttqa!App";
|
||||
public const string NetEaseCloudMusic = "cloudmusic.exe";
|
||||
public const string KugouMusic = "kugou";
|
||||
public const string QQMusic = "QQMusic.exe";
|
||||
public const string iTunes = "49586DaveAntoine.MediaControllerforiTunes_9bzempp7dntjg!App";
|
||||
public const string Chrome = "Chrome";
|
||||
public const string Edge = "MSEdge";
|
||||
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
|
||||
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public class PlayerName
|
||||
{
|
||||
public const string LXMusic = "LX Music";
|
||||
public const string MediaPlayerWindows11 = "Media Player";
|
||||
public const string AIMP = "AIMP";
|
||||
public const string Foobar2000 = "foobar2000";
|
||||
public const string MusicBee = "MusicBee";
|
||||
public const string PotPlayer = "PotPlayer";
|
||||
public const string Spotify = "Spotify";
|
||||
public const string AppleMusic = "Apple Music";
|
||||
public const string NetEaseCloudMusic = "网易云音乐";
|
||||
public const string KugouMusic = "酷狗音乐";
|
||||
public const string QQMusic = "QQ 音乐";
|
||||
public const string iTunes = "iTunes";
|
||||
public const string Chrome = "Google Chrome";
|
||||
public const string Edge = "Microsoft Edge";
|
||||
public const string BetterLyrics = "BetterLyrics";
|
||||
public const string BetterLyricsDebug = "BetterLyrics (Debug)";
|
||||
}
|
||||
}
|
||||
13
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/Time.cs
Normal 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 Time
|
||||
{
|
||||
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(300);
|
||||
}
|
||||
}
|
||||
13
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/iTunes.cs
Normal 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 iTunes
|
||||
{
|
||||
public const string QueryPrefix = "https://itunes.apple.com/search?";
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@
|
||||
x:Name="TrayIcon"
|
||||
x:FieldModifier="public"
|
||||
ContextMenuMode="SecondWindow"
|
||||
DoubleClickCommand="{x:Bind ViewModel.OpenLyricsWindowCommand}"
|
||||
IconSource="ms-appx:///Assets/Logo.ico"
|
||||
LeftClickCommand="{x:Bind ViewModel.OpenLyricsWindowCommand}"
|
||||
NoLeftClickDelay="True"
|
||||
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
|
||||
<tb:TaskbarIcon.ContextFlyout>
|
||||
@@ -22,7 +24,10 @@
|
||||
AreOpenCloseAnimationsEnabled="True"
|
||||
LightDismissOverlayMode="On"
|
||||
ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<MenuFlyoutItem x:Uid="SystemTrayMusicGallery" Command="{x:Bind ViewModel.OpenMusicGalleryCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayResetWindowPosition" Command="{x:Bind ViewModel.ResetWindowPositionCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayRestart" Command="{x:Bind ViewModel.RestartAppCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayUnlock"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
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 partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is AlbumArtSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
AlbumArtSearchProvider.Local => App.ResourceLoader!.GetString("AlbumArtSearchLocalProvider"),
|
||||
AlbumArtSearchProvider.SMTC => App.ResourceLoader!.GetString("AlbumArtSearchSMTCProvider"),
|
||||
AlbumArtSearchProvider.iTunes => "iTunes",
|
||||
_ => throw new Exception($"Unknown AlbumArtSearchProvider: {provider}"),
|
||||
};
|
||||
}
|
||||
throw new ArgumentException("Value must be of type AlbumArtSearchProvider", nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
public partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal partial class EnumToIntConverter : IValueConverter
|
||||
public partial class EnumToIntConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -14,19 +14,19 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString("LyricsSearchProviderLrcLib"),
|
||||
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString("LyricsSearchProviderQQ"),
|
||||
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString("LyricsSearchProviderNetease"),
|
||||
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString("LyricsSearchProviderKugou"),
|
||||
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString("LyricsSearchProviderAmllTtmlDb"),
|
||||
LyricsSearchProvider.LrcLib => "LrcLib",
|
||||
LyricsSearchProvider.QQ => "QQ 音乐",
|
||||
LyricsSearchProvider.Netease => "网易云音乐",
|
||||
LyricsSearchProvider.Kugou => "酷狗音乐",
|
||||
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
|
||||
_ => "",
|
||||
_ => "N/A",
|
||||
};
|
||||
}
|
||||
return "";
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
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
|
||||
{
|
||||
public class MediaSourceProviderToDisplayedNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
PlayerID.Spotify => PlayerName.Spotify,
|
||||
PlayerID.AppleMusic => PlayerName.AppleMusic,
|
||||
PlayerID.iTunes => PlayerName.iTunes,
|
||||
PlayerID.KugouMusic => PlayerName.KugouMusic,
|
||||
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
||||
PlayerID.QQMusic => PlayerName.QQMusic,
|
||||
PlayerID.LXMusic => PlayerName.LXMusic,
|
||||
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
||||
PlayerID.AIMP => PlayerName.AIMP,
|
||||
PlayerID.Foobar2000 => PlayerName.Foobar2000,
|
||||
PlayerID.MusicBee => PlayerName.MusicBee,
|
||||
PlayerID.PotPlayer => PlayerName.PotPlayer,
|
||||
PlayerID.Chrome => PlayerName.Chrome,
|
||||
PlayerID.Edge => PlayerName.Edge,
|
||||
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
|
||||
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
||||
_ => provider,
|
||||
};
|
||||
}
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
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
|
||||
{
|
||||
public class MediaSourceProviderToLogoUriConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
|
||||
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.iTunes => PathHelper.iTunesLogoPath,
|
||||
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
|
||||
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
||||
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
|
||||
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
||||
PlayerID.AIMP => PathHelper.AIMPLogoPath,
|
||||
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
||||
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
|
||||
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
|
||||
PlayerID.Chrome => PathHelper.ChromeLogoPath,
|
||||
PlayerID.Edge => PathHelper.EdgeLogoPath,
|
||||
PlayerID.BetterLyrics => PathHelper.LogoPath,
|
||||
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
|
||||
_ => PathHelper.UnknownPlayerLogoPath,
|
||||
};
|
||||
}
|
||||
return PathHelper.UnknownPlayerLogoPath;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 partial class SecondsToFormattedTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is double seconds)
|
||||
{
|
||||
return TimeSpan.FromSeconds(seconds).ToString(@"mm\:ss");
|
||||
}
|
||||
else if (value is int secondsInt)
|
||||
{
|
||||
return TimeSpan.FromSeconds(secondsInt).ToString(@"mm\:ss");
|
||||
}
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class TranslationSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is TranslationSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
TranslationSearchProvider.LrcLib => "LrcLib",
|
||||
TranslationSearchProvider.QQ => "QQ",
|
||||
TranslationSearchProvider.Netease => "Netease",
|
||||
TranslationSearchProvider.Kugou => "Kugou",
|
||||
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
|
||||
TranslationSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
TranslationSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
TranslationSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
TranslationSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
|
||||
TranslationSearchProvider.LibreTranslate => "LibreTranslate",
|
||||
_ => "N/A",
|
||||
};
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 AlbumArtSearchProvider
|
||||
{
|
||||
Local,
|
||||
SMTC,
|
||||
iTunes,
|
||||
}
|
||||
}
|
||||
@@ -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 CommonSongProperty
|
||||
{
|
||||
Title,
|
||||
Album,
|
||||
Artist
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum DockPlacement
|
||||
{
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
public static class DockPlacementExtensions
|
||||
{
|
||||
public static WindowPixelSampleMode ToWindowPixelSampleMode(this DockPlacement placement)
|
||||
{
|
||||
return placement switch
|
||||
{
|
||||
DockPlacement.Top => WindowPixelSampleMode.BelowWindow,
|
||||
DockPlacement.Bottom => WindowPixelSampleMode.AboveWindow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(placement), placement, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LineRenderingType
|
||||
{
|
||||
UntilCurrentChar,
|
||||
CurrentCharOnly,
|
||||
CurrentChar,
|
||||
LineStartToCurrentChar,
|
||||
CurrentLine
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,5 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
AlbumArtOnly,
|
||||
LyricsOnly,
|
||||
SplitView,
|
||||
PlaceholderOnly,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return null;
|
||||
|
||||
// TTML
|
||||
if (content.StartsWith("<?xml") && System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b"))
|
||||
// TTML: 检查 <tt ... xmlns="http://www.w3.org/ns/ttml"
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"<tt\b[^>]*\bxmlns\s*=\s*[""']http://www\.w3\.org/ns/ttml[""']",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
return LyricsFormat.Ttml;
|
||||
}
|
||||
|
||||
@@ -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 LyricsLayoutOrientation
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,11 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => AppInfo.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => AppInfo.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => AppInfo.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => AppInfo.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => AppInfo.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
}
|
||||
@@ -61,5 +61,22 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
return !provider.IsLocal();
|
||||
}
|
||||
|
||||
public static TranslationSearchProvider? ToTranslationSearchProvider(this LyricsSearchProvider? provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => TranslationSearchProvider.LrcLib,
|
||||
LyricsSearchProvider.QQ => TranslationSearchProvider.QQ,
|
||||
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
|
||||
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
|
||||
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
|
||||
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
|
||||
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
|
||||
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,
|
||||
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum MusicSearchMatchMode
|
||||
{
|
||||
TitleAndArtist,
|
||||
TitleArtistAlbumAndDuration,
|
||||
}
|
||||
}
|
||||
@@ -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 PlaybackOrder
|
||||
{
|
||||
RepeatAll,
|
||||
RepeatOne,
|
||||
Shuffle,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum TranslationSearchProvider
|
||||
{
|
||||
QQ,
|
||||
Kugou,
|
||||
Netease,
|
||||
LrcLib,
|
||||
AmllTtmlDb,
|
||||
LocalMusicFile,
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
LibreTranslate,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum WindowColorSampleMode
|
||||
public enum WindowPixelSampleMode
|
||||
{
|
||||
BelowWindow,
|
||||
AboveWindow,
|
||||
WindowArea,
|
||||
WindowEdge,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LastFMIsAuthenticatedChangedEventArgs : EventArgs
|
||||
{
|
||||
public bool IsAuthenticated { get; set; }
|
||||
public LastFMIsAuthenticatedChangedEventArgs(bool isAuthenticated)
|
||||
{
|
||||
IsAuthenticated = isAuthenticated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Hqub.Lastfm.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LastFMUserChangedEventArgs : EventArgs
|
||||
{
|
||||
public User? User { get; set; }
|
||||
public LastFMUserChangedEventArgs(User? user)
|
||||
{
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
public class TimelineChangedEventArgs(TimeSpan position, TimeSpan end) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
public TimeSpan End { get; set; } = end;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
public static class AppInfo
|
||||
{
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppName = "BetterLyrics";
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
|
||||
|
||||
public const string UnlockWindowTag = "UnlockWindow";
|
||||
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
|
||||
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
|
||||
|
||||
public static string iTunesAlbumArtCacheDirectory => Path.Combine(CacheFolder, "itunes-album-art");
|
||||
|
||||
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
public static async Task<DateTime> GetBuildDate()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var filePath = assembly.Location;
|
||||
if (!File.Exists(filePath))
|
||||
return DateTime.MinValue;
|
||||
|
||||
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
// 获取文件基本属性
|
||||
BasicProperties props = await file.GetBasicPropertiesAsync();
|
||||
// 返回修改日期
|
||||
return props.DateModified.DateTime;
|
||||
}
|
||||
|
||||
public static List<LanguageInfo> GetAllTranslationLanguagesInfo() =>
|
||||
[
|
||||
new LanguageInfo("ar", "العربية"),
|
||||
new LanguageInfo("az", "Azərbaycan dili"),
|
||||
new LanguageInfo("zh", "中文"),
|
||||
new LanguageInfo("cs", "Čeština"),
|
||||
new LanguageInfo("da", "Dansk"),
|
||||
new LanguageInfo("nl", "Nederlands"),
|
||||
new LanguageInfo("en", "English"),
|
||||
new LanguageInfo("eo", "Esperanto"),
|
||||
new LanguageInfo("fi", "Suomi"),
|
||||
new LanguageInfo("fr", "Français"),
|
||||
new LanguageInfo("de", "Deutsch"),
|
||||
new LanguageInfo("el", "Ελληνικά"),
|
||||
new LanguageInfo("he", "עברית"),
|
||||
new LanguageInfo("hi", "हिन्दी"),
|
||||
new LanguageInfo("hu", "Magyar"),
|
||||
new LanguageInfo("id", "Bahasa Indonesia"),
|
||||
new LanguageInfo("ga", "Gaeilge"),
|
||||
new LanguageInfo("it", "Italiano"),
|
||||
new LanguageInfo("ja", "日本語"),
|
||||
new LanguageInfo("ko", "한국어"),
|
||||
new LanguageInfo("fa", "فارسی"),
|
||||
new LanguageInfo("pl", "Polski"),
|
||||
new LanguageInfo("pt", "Português"),
|
||||
new LanguageInfo("ru", "Русский"),
|
||||
new LanguageInfo("sk", "Slovenčina"),
|
||||
new LanguageInfo("es", "Español"),
|
||||
new LanguageInfo("sv", "Svenska"),
|
||||
new LanguageInfo("tr", "Türkçe"),
|
||||
new LanguageInfo("uk", "Українська"),
|
||||
new LanguageInfo("vi", "Tiếng Việt"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Windowing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class AppWindowHelper
|
||||
{
|
||||
public static void SetIcons(this AppWindow appWindow)
|
||||
{
|
||||
appWindow.SetIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTaskbarIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTitleBarIcon(PathHelper.LogoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,51 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static T? SafeGet<T>(this IList<T> list, int index)
|
||||
public static ObservableCollection<GroupInfoList> GetGroupedBy<T>(
|
||||
this IEnumerable<T> items,
|
||||
Func<T, object> groupKeySelector,
|
||||
Func<object, object>? orderSelector = null)
|
||||
{
|
||||
if (list == null || index < 0 || index >= list.Count) return default;
|
||||
return list[index];
|
||||
var query = from item in items
|
||||
group item by groupKeySelector(item) into g
|
||||
orderby g.Key
|
||||
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
|
||||
|
||||
return new ObservableCollection<GroupInfoList>(query);
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
|
||||
{
|
||||
if (collection == null) return;
|
||||
if (items == null) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
collection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertRange<T>(this IList<T> list, int index, IEnumerable<T> items)
|
||||
{
|
||||
if (list == null) return;
|
||||
if (items == null) return;
|
||||
if (index < 0 || index > list.Count) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Insert(index++, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.UI;
|
||||
|
||||
using Color = Windows.UI.Color;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ColorHelper
|
||||
@@ -98,5 +105,147 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
|
||||
}
|
||||
|
||||
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
|
||||
int screenWidth = monitorInfo.rcMonitor.Width;
|
||||
switch (mode)
|
||||
{
|
||||
case WindowPixelSampleMode.BelowWindow:
|
||||
{
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.AboveWindow:
|
||||
{
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Top - 1;
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowEdge:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
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
|
||||
)
|
||||
);
|
||||
// Bottom edge
|
||||
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Bottom - (int)edgeThickness.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
|
||||
)
|
||||
);
|
||||
// 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 (edgeColors.Count == 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
foreach (var c in edgeColors)
|
||||
{
|
||||
r += c.R;
|
||||
g += c.G;
|
||||
b += c.B;
|
||||
}
|
||||
return System.Drawing.Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return System.Drawing.Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
private static System.Drawing.Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static System.Drawing.Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
System.Drawing.Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return System.Drawing.Color.Transparent;
|
||||
return System.Drawing.Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
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;
|
||||
@@ -15,7 +18,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
|
||||
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 = [];
|
||||
@@ -47,13 +49,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_originalWindowBounds.Remove(hwnd);
|
||||
}
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
}
|
||||
|
||||
@@ -80,13 +75,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
|
||||
window.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
new Windows.Graphics.RectInt32(
|
||||
targetX,
|
||||
targetY,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
)
|
||||
);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
if (!_originalTopmostStates.ContainsKey(hwnd))
|
||||
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
|
||||
@@ -97,48 +93,30 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
window.SetIsShownInSwitchers(false);
|
||||
}
|
||||
|
||||
public static void Lock(Window window)
|
||||
{
|
||||
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD><EFBFBD><CDB8>
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
|
||||
SetClickThrough(window, true);
|
||||
}
|
||||
|
||||
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);
|
||||
_clickThroughStates[hwnd] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
|
||||
_clickThroughStates[hwnd] = false;
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unlock(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD> Disable ʱ<><CAB1><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD>
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
}
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
SetClickThrough(window, false);
|
||||
|
||||
// To recover the system backdrop, we need to reopen the window
|
||||
WindowHelper.RestartApp(AppInfo.UnlockWindowTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
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;
|
||||
@@ -14,21 +18,29 @@ 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 void Disable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (!_registered.Contains(hwnd)) return;
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetIsAlwaysOnTop(false);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
UnregisterAppBar(hwnd);
|
||||
|
||||
window.SetWindowStyle(_originalWindowStyle[hwnd]);
|
||||
_originalWindowStyle.Remove(hwnd);
|
||||
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
User32.SetWindowPos(
|
||||
@@ -42,14 +54,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
|
||||
UnregisterAppBar(hwnd);
|
||||
}
|
||||
|
||||
public static void Enable(Window window, int appBarHeight)
|
||||
public static void Enable(Window window, string monitorDeviceName, int appBarHeight, DockPlacement dockPlacement)
|
||||
{
|
||||
window.SetIsShownInSwitchers(false);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -58,7 +67,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
_originalWindowStyle[hwnd] = window.GetWindowStyle();
|
||||
}
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
@@ -68,40 +76,55 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
RegisterAppBar(hwnd, monitorDeviceName, appBarHeight, dockPlacement);
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.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;
|
||||
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
monitorInfo.rcMonitor.Left,
|
||||
y,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
User32.SetWindowPosFlags.SWP_HIDEWINDOW
|
||||
);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height)
|
||||
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 = Shell32.ABE.ABE_TOP,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = height,
|
||||
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);
|
||||
@@ -119,40 +142,59 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
|
||||
|
||||
_registered.Remove(hwnd);
|
||||
}
|
||||
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, string monitorDeviceName, int newHeight, DockPlacement dockPlacement)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
App.DispatcherQueueTimer?.Debounce(() =>
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
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()
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = newHeight,
|
||||
},
|
||||
};
|
||||
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_SETPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
newHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
// 同步窗口实际高度和位置
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
public static float CubicBezier(float t, float p0, float p1, float p2, float p3)
|
||||
{
|
||||
float u = 1 - t;
|
||||
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
|
||||
}
|
||||
|
||||
public static float Linear(float t) => t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Ude;
|
||||
@@ -21,5 +23,60 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return Encoding.GetEncoding(encoding);
|
||||
}
|
||||
|
||||
public static string SanitizeFileName(string fileName, char replacement = '_')
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(fileName.Length);
|
||||
foreach (var c in fileName)
|
||||
{
|
||||
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllBytes(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteLyricsCache(string title, string artist, string lyrics, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
public static void WriteAlbumArtCache(string album, string artist, byte[] img, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
File.WriteAllBytes(cacheFilePath, img);
|
||||
}
|
||||
|
||||
public static bool IsSwitchableNormalizedMatch(string fileName, string q1, string q2)
|
||||
{
|
||||
var normFileName = StringHelper.Normalize(fileName.Normalize());
|
||||
var normQ1 = StringHelper.Normalize(q1);
|
||||
var normQ2 = StringHelper.Normalize(q2);
|
||||
|
||||
// 常见两种顺序
|
||||
return normFileName == normQ1 + normQ2
|
||||
|| normFileName == normQ2 + normQ1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
20
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FontHelper.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class FontHelper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
|
||||
|
||||
public static string GetUserPreferredFontFamily() => SystemFontFamilies.ElementAtOrDefault(_settingsService.SelectedFontFamilyIndex) ?? "Segoe UI";
|
||||
}
|
||||
}
|
||||
@@ -4,37 +4,35 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ForegroundWindowWatcherHelper
|
||||
public class ForegroundWindowWatcher
|
||||
{
|
||||
private readonly User32.WinEventProc _winEventDelegate;
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
private HWND _currentForeground = HWND.NULL;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
private const int ThrottleIntervalMs = 1000;
|
||||
private readonly ThrottleHelper _winEventProcThrottle = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
public delegate void WindowChangedHandler(HWND hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
public ForegroundWindowWatcher(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
_onWindowChanged = onWindowChanged;
|
||||
_winEventDelegate = new User32.WinEventProc(WinEventProc);
|
||||
|
||||
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_pollingTimer.Tick += (_, _) =>
|
||||
{
|
||||
if (_currentForeground != IntPtr.Zero && _currentForeground != _selfHwnd)
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
};
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Interval = TimeSpan.FromSeconds(1);
|
||||
_timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@@ -65,7 +63,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
)
|
||||
);
|
||||
|
||||
_pollingTimer.Start();
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
@@ -74,7 +72,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
User32.UnhookWinEvent(hook);
|
||||
|
||||
_hooks.Clear();
|
||||
_pollingTimer.Stop();
|
||||
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void Timer_Tick(object? sender, object e)
|
||||
{
|
||||
if (_currentForeground != HWND.NULL)
|
||||
{
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
}
|
||||
}
|
||||
|
||||
private void WinEventProc(
|
||||
@@ -87,15 +94,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
uint dwmsEventTime
|
||||
)
|
||||
{
|
||||
if (hwnd == IntPtr.Zero || hwnd == _selfHwnd)
|
||||
if (hwnd == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastEventTime).TotalMilliseconds < ThrottleIntervalMs)
|
||||
return;
|
||||
|
||||
_lastEventTime = now;
|
||||
|
||||
if (eventType == User32.EventConstants.EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
_currentForeground = hwnd;
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class GlobalHotKeyHelper
|
||||
{
|
||||
private static Dictionary<int, Action> _hotKeyActions = [];
|
||||
private static int _nextId = 0;
|
||||
|
||||
public static void RegisterHotKey(Window window, User32.HotKeyModifiers modifiers, uint key, Action action)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
int id = _nextId++;
|
||||
User32.RegisterHotKey(hwnd, id, modifiers, key);
|
||||
_hotKeyActions[id] = action;
|
||||
}
|
||||
|
||||
public static void UnregisterAllHotKeys(Window window)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
foreach (var id in _hotKeyActions.Keys.ToList())
|
||||
{
|
||||
User32.UnregisterHotKey(hwnd, id);
|
||||
_hotKeyActions.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryInvokeAction(int id)
|
||||
{
|
||||
if (_hotKeyActions.TryGetValue(id, out var action))
|
||||
{
|
||||
action?.Invoke();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -7,10 +17,6 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
@@ -19,157 +25,117 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
public const int AccentColorCount = 3;
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
|
||||
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
|
||||
{
|
||||
var device = CanvasDevice.GetSharedDevice();
|
||||
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
using var writer = new DataWriter(stream);
|
||||
writer.WriteBytes(bytes);
|
||||
writer.StoreAsync().GetAwaiter().GetResult();
|
||||
writer.FlushAsync().GetAwaiter().GetResult();
|
||||
writer.DetachStream();
|
||||
return RandomAccessStreamReference.CreateFromStream(stream);
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
|
||||
{
|
||||
using var device = CanvasDevice.GetSharedDevice();
|
||||
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
|
||||
// 随机生成渐变色
|
||||
Windows.UI.Color RandomColor()
|
||||
{
|
||||
var rand = new Random(Guid.NewGuid().GetHashCode());
|
||||
double h = rand.NextDouble() * 360;
|
||||
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
|
||||
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
|
||||
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
|
||||
}
|
||||
|
||||
Windows.UI.Color color1 = RandomColor();
|
||||
Windows.UI.Color color2 = RandomColor();
|
||||
|
||||
// 居中绘制文字
|
||||
using (var ds = renderTarget.CreateDrawingSession())
|
||||
{
|
||||
// 背景色
|
||||
ds.Clear(Colors.LightGray);
|
||||
|
||||
// 文字格式
|
||||
var format = new CanvasTextFormat
|
||||
// 绘制线性渐变背景
|
||||
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
|
||||
{
|
||||
FontSize = Math.Min(width, height) / 6f,
|
||||
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Center,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Center,
|
||||
WordWrapping = CanvasWordWrapping.Wrap,
|
||||
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
|
||||
Options = CanvasDrawTextOptions.Default,
|
||||
StartPoint = new Vector2(0, 0),
|
||||
EndPoint = new Vector2(width, height)
|
||||
};
|
||||
|
||||
// 设定边距
|
||||
float margin = Math.Min(width, height) / 12f;
|
||||
float availableWidth = width - 2 * margin;
|
||||
float availableHeight = height - 2 * margin;
|
||||
|
||||
// 计算合适的字体大小以适应内容区域
|
||||
float fontSize = format.FontSize;
|
||||
float minFontSize = 8f;
|
||||
float maxFontSize = format.FontSize;
|
||||
CanvasTextLayout layout;
|
||||
do
|
||||
{
|
||||
format.FontSize = fontSize;
|
||||
layout = new CanvasTextLayout(
|
||||
ds,
|
||||
text,
|
||||
format,
|
||||
availableWidth,
|
||||
availableHeight
|
||||
);
|
||||
if (
|
||||
layout.LayoutBounds.Width <= availableWidth
|
||||
&& layout.LayoutBounds.Height <= availableHeight
|
||||
)
|
||||
break;
|
||||
fontSize -= 1f;
|
||||
} while (fontSize >= minFontSize);
|
||||
|
||||
// 居中绘制文字(在内容区域内居中)
|
||||
var bounds = layout.LayoutBounds;
|
||||
var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X;
|
||||
var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y;
|
||||
ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray);
|
||||
ds.FillRectangle(0, 0, width, height, gradientBrush);
|
||||
}
|
||||
|
||||
// 保存为 PNG 并转为 byte[]
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
|
||||
var buffer = new byte[stream.Size];
|
||||
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
|
||||
{
|
||||
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
|
||||
var buffer = new byte[stream.Size];
|
||||
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
|
||||
{
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
reader.ReadBytes(buffer);
|
||||
}
|
||||
return buffer;
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
reader.ReadBytes(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes, int count, bool? isDark = null)
|
||||
{
|
||||
// 使用 ImageSharp 读取图片
|
||||
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
|
||||
|
||||
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
|
||||
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
|
||||
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
var color = image[x, y];
|
||||
// 可选:忽略透明像素
|
||||
if (color.A < 32) continue;
|
||||
if (colorCount.ContainsKey(color))
|
||||
colorCount[color]++;
|
||||
else
|
||||
colorCount[color] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 按出现次数排序,取前 AccentColorCount 个
|
||||
var topColors = colorCount
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.Take(AccentColorCount)
|
||||
.Select(kv => kv.Key)
|
||||
using var image = Image.Load<Rgba32>(bytes);
|
||||
var colorThief = new ColorThief.ImageSharp.ColorThief();
|
||||
var mainColor = colorThief.GetColor(image, 10, false);
|
||||
var palette = colorThief.GetPalette(image, 255, 10, false);
|
||||
var topColors = palette
|
||||
.OrderByDescending(x => x.Population)
|
||||
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
|
||||
.Select(x => Windows.UI.Color.FromArgb(x.Color.A, x.Color.R, x.Color.G, x.Color.B))
|
||||
.Take(count)
|
||||
.ToList();
|
||||
|
||||
// 转换为 Windows.UI.Color
|
||||
return topColors
|
||||
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
|
||||
.ToList();
|
||||
return topColors;
|
||||
}
|
||||
|
||||
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// var stream = new InMemoryRandomAccessStream();
|
||||
// await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
// stream.Seek(0);
|
||||
|
||||
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
// var bitmapImage = new BitmapImage();
|
||||
// await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
// return bitmapImage;
|
||||
//}
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
//public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
// await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
|
||||
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
//public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// if (imageBytes == null || imageBytes.Length == 0)
|
||||
// return null;
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
|
||||
{
|
||||
if (imageBytes == null || imageBytes.Length == 0)
|
||||
return null;
|
||||
// InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
// await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
return stream;
|
||||
}
|
||||
// return stream;
|
||||
//}
|
||||
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
using var reader = new DataReader(stream);
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
byte[] buffer = new byte[stream.Size];
|
||||
reader.ReadBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static float GetAverageLuminance(CanvasBitmap bitmap)
|
||||
@@ -188,5 +154,70 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return (float)(sum / (pixels.Length / 4));
|
||||
}
|
||||
|
||||
public static byte[] MakeSquareWithThemeColor(byte[] imageBytes)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(imageBytes);
|
||||
|
||||
if (image.Width == image.Height)
|
||||
{
|
||||
// 已经是正方形,直接返回
|
||||
return imageBytes;
|
||||
}
|
||||
|
||||
int size = Math.Max(image.Width, image.Height);
|
||||
|
||||
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes, 1).FirstOrDefault().ToHex());
|
||||
|
||||
using var square = new Image<Rgba32>(size, size, themeColor);
|
||||
|
||||
int offsetX = (size - image.Width) / 2;
|
||||
int offsetY = (size - image.Height) / 2;
|
||||
|
||||
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
square.Save(ms, new JpegEncoder());
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] Resize(byte[] imageBytes, int size)
|
||||
{
|
||||
using (Image image = Image.Load(imageBytes))
|
||||
{
|
||||
var factor = Math.Max((float)size / image.Width, (float)size / image.Height);
|
||||
|
||||
int width = (int)(image.Width * factor);
|
||||
int height = (int)(image.Height * factor);
|
||||
|
||||
if (factor > 1)
|
||||
{
|
||||
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
|
||||
}
|
||||
else
|
||||
{
|
||||
image.Mutate(x => x.Resize(width, height, KnownResamplers.NearestNeighbor));
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
image.Save(ms, new JpegEncoder());
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] GenerateNoiseBGRA(int width, int height)
|
||||
{
|
||||
var random = new Random();
|
||||
var pixelData = new byte[width * height * 4];
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
byte gray = (byte)random.Next(0, 256);
|
||||
pixelData[i * 4 + 0] = gray; // B
|
||||
pixelData[i * 4 + 1] = gray; // G
|
||||
pixelData[i * 4 + 2] = gray; // R
|
||||
pixelData[i * 4 + 3] = 255; // A
|
||||
}
|
||||
return pixelData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LanguageHelper.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
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.Globalization;
|
||||
using System.Linq;
|
||||
using TinyPinyin;
|
||||
using Windows.Globalization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LanguageHelper
|
||||
{
|
||||
private static readonly RankedLanguageIdentifierFactory _factory = new();
|
||||
private static readonly RankedLanguageIdentifier _identifier;
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
|
||||
[
|
||||
new Models.LanguageInfo("ar", "العربية"),
|
||||
new Models.LanguageInfo("az", "Azərbaycan dili"),
|
||||
new Models.LanguageInfo("zh-Hans", "简体中文"),
|
||||
new Models.LanguageInfo("zh-Hant", "繁體中文"),
|
||||
new Models.LanguageInfo("cs", "Čeština"),
|
||||
new Models.LanguageInfo("da", "Dansk"),
|
||||
new Models.LanguageInfo("nl", "Nederlands"),
|
||||
new Models.LanguageInfo("en", "English"),
|
||||
new Models.LanguageInfo("eo", "Esperanto"),
|
||||
new Models.LanguageInfo("fi", "Suomi"),
|
||||
new Models.LanguageInfo("fr", "Français"),
|
||||
new Models.LanguageInfo("de", "Deutsch"),
|
||||
new Models.LanguageInfo("el", "Ελληνικά"),
|
||||
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("pl", "Polski"),
|
||||
new Models.LanguageInfo("pt", "Português"),
|
||||
new Models.LanguageInfo("ru", "Русский"),
|
||||
new Models.LanguageInfo("sk", "Slovenčina"),
|
||||
new Models.LanguageInfo("es", "Español"),
|
||||
new Models.LanguageInfo("sv", "Svenska"),
|
||||
new Models.LanguageInfo("tr", "Türkçe"),
|
||||
new Models.LanguageInfo("uk", "Українська"),
|
||||
new Models.LanguageInfo("vi", "Tiếng Việt"),
|
||||
];
|
||||
|
||||
static LanguageHelper()
|
||||
{
|
||||
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
|
||||
}
|
||||
|
||||
public static string? DetectLanguageCode(string? text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
|
||||
var guessList = _identifier.Identify(text);
|
||||
string? code = guessList?.FirstOrDefault()?.Item1.Iso639_2T;
|
||||
code = code switch
|
||||
{
|
||||
"simple" => "en",
|
||||
"zh_classical" => "zh-Hant",
|
||||
"zh_yue" => "zh-Hant",
|
||||
"zh" => "zh-Hans",
|
||||
_ => code
|
||||
};
|
||||
return code;
|
||||
}
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
return DetectLanguageCode(text)?.Substring(0, 2) switch
|
||||
{
|
||||
"zh" or "ja" or "ko" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static string ConvertToCountryCode(string? languageCode)
|
||||
{
|
||||
if (languageCode == null) return "us";
|
||||
|
||||
return languageCode switch
|
||||
{
|
||||
"zh" => "cn",
|
||||
"zh-Hans" => "cn",
|
||||
"zh-Hant" => "tw",
|
||||
"ja" => "jp",
|
||||
"ko" => "kr",
|
||||
_ => "us"
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetUserTargetLanguageCode()
|
||||
{
|
||||
return SupportedTargetLanguages[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
}
|
||||
|
||||
public static int GetDefaultTargetLanguageIndex()
|
||||
{
|
||||
int found = SupportedTargetLanguages.FindIndex(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
|
||||
if (found == -1) found = 7; // 默认使用英语
|
||||
return found;
|
||||
}
|
||||
|
||||
public static string GetOrderChar(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return "#";
|
||||
char c = text.ElementAtOrDefault(0);
|
||||
if (char.IsLetter(c) && c < 128)
|
||||
return char.ToUpper(c).ToString();
|
||||
|
||||
if (PinyinHelper.IsChinese(c))
|
||||
{
|
||||
return PinyinHelper.GetPinyinInitials($"{c}");
|
||||
}
|
||||
|
||||
return "#";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Nito.AsyncEx;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LatestOnlyTaskRunner
|
||||
{
|
||||
private readonly AsyncLock _mutex = new();
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public async Task RunAsync(Func<CancellationToken, Task> action)
|
||||
{
|
||||
CancellationTokenSource oldCts;
|
||||
|
||||
// 使用 AsyncLock 保证线程安全
|
||||
using (await _mutex.LockAsync())
|
||||
{
|
||||
// 取消旧的
|
||||
oldCts = _cts;
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
oldCts?.Cancel();
|
||||
oldCts?.Dispose();
|
||||
|
||||
CancellationToken token = _cts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
await action(token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 可以选择忽略取消异常
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LauncherHelper
|
||||
{
|
||||
public static async Task SelectAndShowFile(string filePath)
|
||||
{
|
||||
var file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
var folder = await file.GetParentAsync();
|
||||
|
||||
var folderOptions = new FolderLauncherOptions();
|
||||
folderOptions.ItemsToSelect.Add(file);
|
||||
|
||||
await Launcher.LaunchFolderAsync(folder, folderOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,29 +10,21 @@ 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
|
||||
{
|
||||
private List<List<LyricsLine>> _multiLangLyricsLines = [];
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
public List<List<LyricsLine>> Parse(string? raw, int durationMs)
|
||||
public List<LyricsData> Parse(string? raw, int? durationMs)
|
||||
{
|
||||
_multiLangLyricsLines = [];
|
||||
durationMs ??= (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
_lyricsDataArr = [];
|
||||
if (raw == null)
|
||||
{
|
||||
_multiLangLyricsLines.Add(
|
||||
[
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
},
|
||||
]
|
||||
);
|
||||
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder(durationMs.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -42,10 +35,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
ParseLrc(raw);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseUsingLyricify(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseUsingLyricify(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw);
|
||||
@@ -54,8 +47,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
break;
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(durationMs);
|
||||
return _multiLangLyricsLines;
|
||||
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
private void ParseLrc(string raw)
|
||||
@@ -116,49 +109,57 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
// 按时间分组
|
||||
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
|
||||
int languageCount = grouped.Max(g => g.Count());
|
||||
int languageCount = 0;
|
||||
if (grouped != null && grouped.Count > 0)
|
||||
{
|
||||
// 计算最大语言数量
|
||||
languageCount = grouped.Max(g => g.Count());
|
||||
}
|
||||
|
||||
// 初始化每种语言的歌词列表
|
||||
_multiLangLyricsLines.Clear();
|
||||
for (int i = 0; i < languageCount; i++)
|
||||
_multiLangLyricsLines.Add(new List<LyricsLine>());
|
||||
_lyricsDataArr.Clear();
|
||||
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
|
||||
|
||||
// 遍历每个时间分组
|
||||
foreach (var group in grouped)
|
||||
if (grouped != null)
|
||||
{
|
||||
var linesInGroup = group.ToList();
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
// 如果该语言有翻译,取对应行,否则用原文(第一行)
|
||||
var (start, text, syllables) =
|
||||
langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
|
||||
var line = new LyricsLine
|
||||
var linesInGroup = group.ToList();
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
{
|
||||
StartMs = start,
|
||||
EndMs = 0, // 稍后统一修正
|
||||
OriginalText = text,
|
||||
CharTimings = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
// 只添加有对应行的语言,否则跳过
|
||||
if (langIdx < linesInGroup.Count)
|
||||
{
|
||||
var (charStart, charText) = syllables[j];
|
||||
int startIndex = currentIndex;
|
||||
line.CharTimings.Add(
|
||||
new CharTiming
|
||||
var (start, text, syllables) = linesInGroup[langIdx];
|
||||
var line = new LyricsLine
|
||||
{
|
||||
StartMs = start,
|
||||
OriginalText = text,
|
||||
LyricsChars = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
{
|
||||
StartMs = charStart,
|
||||
EndMs = 0, // Fixed later
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
var (charStart, charText) = syllables[j];
|
||||
int startIndex = currentIndex;
|
||||
line.LyricsChars.Add(
|
||||
new LyricsChar
|
||||
{
|
||||
StartMs = charStart,
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
}
|
||||
);
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
);
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
_lyricsDataArr[langIdx].LyricsLines.Add(line);
|
||||
}
|
||||
// 没有翻译行则不补原文,直接跳过
|
||||
}
|
||||
_multiLangLyricsLines[langIdx].Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,8 +168,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
try
|
||||
{
|
||||
List<LyricsLine> singleLangLyricsLine = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
List<LyricsLine> originalLines = [];
|
||||
List<LyricsLine> translationLines = [];
|
||||
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null) return;
|
||||
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
|
||||
@@ -180,49 +182,94 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 处理分词分时
|
||||
var spans = p.Elements().Where(s => s.Name.LocalName == "span").ToList();
|
||||
// 只获取一级span,且排除ttm:role="x-bg"的span
|
||||
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")
|
||||
.ToList();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s.Value));
|
||||
var charTimings = new List<CharTiming>();
|
||||
// 原文和翻译分离
|
||||
var originalTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-translation")
|
||||
.ToList();
|
||||
var translationTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
|
||||
.ToList();
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
// 处理原文span后的空白
|
||||
for (int i = 0; i < originalTextSpans.Count; i++)
|
||||
{
|
||||
var span = originalTextSpans[i];
|
||||
var nextNode = span.NodesAfterSelf().FirstOrDefault();
|
||||
if (nextNode is XText textNode)
|
||||
{
|
||||
span.Value += textNode.Value;
|
||||
}
|
||||
}
|
||||
// 拼接空白字符后的原文
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
|
||||
var originalCharTimings = new List<LyricsChar>();
|
||||
int originalStartIndex = 0;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
var span = spans[i];
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
|
||||
if (sStartMs == 0 && sEndMs == 0)
|
||||
continue;
|
||||
|
||||
if (sEndMs == 0)
|
||||
sEndMs =
|
||||
(i + 1 < spans.Count)
|
||||
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
|
||||
: pEndMs;
|
||||
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = 0, StartIndex = startIndex, Text = span.Value });
|
||||
startIndex += span.Value.Length;
|
||||
originalCharTimings.Add(new LyricsChar
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = sEndMs,
|
||||
StartIndex = originalStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
originalStartIndex += span.Value.Length;
|
||||
}
|
||||
if (originalTextSpans.Count == 0)
|
||||
originalText = p.Value;
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value;
|
||||
originalLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = pEndMs,
|
||||
OriginalText = originalText,
|
||||
LyricsChars = originalCharTimings,
|
||||
});
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
// 翻译
|
||||
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
|
||||
var translationCharTimings = new List<LyricsChar>();
|
||||
int translationStartIndex = 0;
|
||||
foreach (var span in translationTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
translationCharTimings.Add(new LyricsChar
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = sEndMs,
|
||||
StartIndex = translationStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
translationStartIndex += span.Value.Length;
|
||||
}
|
||||
if (translationTextSpans.Count > 0)
|
||||
{
|
||||
translationLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
OriginalText = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
EndMs = pEndMs,
|
||||
OriginalText = translationText,
|
||||
LyricsChars = translationCharTimings,
|
||||
});
|
||||
}
|
||||
}
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
_lyricsDataArr.Add(new LyricsData(originalLines));
|
||||
if (translationLines.Count > 0)
|
||||
_lyricsDataArr.Add(new LyricsData(translationLines));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -230,7 +277,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
private int ParseTtmlTime(string? t)
|
||||
private static int ParseTtmlTime(string? t)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(t))
|
||||
return 0;
|
||||
@@ -291,7 +338,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ParseUsingLyricify(List<ILineInfo>? lines)
|
||||
private void ParseQQNeteaseKugou(List<ILineInfo>? lines)
|
||||
{
|
||||
lines = lines?.Where(x => x.Text != string.Empty).ToList();
|
||||
List<LyricsLine> lyricsLines = [];
|
||||
@@ -305,9 +352,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var lineWrite = new LyricsLine
|
||||
{
|
||||
StartMs = lineRead.StartTime ?? 0,
|
||||
EndMs = 0,
|
||||
EndMs = lineRead.EndTime ?? 0,
|
||||
OriginalText = lineRead.Text,
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
};
|
||||
|
||||
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
|
||||
@@ -321,22 +368,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
)
|
||||
{
|
||||
var syllable = syllables[syllableIndex];
|
||||
var charTiming = new CharTiming
|
||||
var charTiming = new LyricsChar
|
||||
{
|
||||
StartMs = syllable.StartTime,
|
||||
EndMs = 0,
|
||||
EndMs = syllable.EndTime,
|
||||
Text = syllable.Text,
|
||||
StartIndex = startIndex,
|
||||
};
|
||||
if (syllableIndex + 1 < syllables.Count)
|
||||
{
|
||||
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
charTiming.EndMs = lineWrite.EndMs;
|
||||
}
|
||||
lineWrite.CharTimings.Add(charTiming);
|
||||
lineWrite.LyricsChars.Add(charTiming);
|
||||
startIndex += syllable.Text.Length;
|
||||
}
|
||||
}
|
||||
@@ -345,56 +384,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
_multiLangLyricsLines.Add(lyricsLines);
|
||||
}
|
||||
|
||||
private void PostProcessLyricsLines(int durationMs)
|
||||
{
|
||||
for (int langIdx = 0; langIdx < _multiLangLyricsLines.Count; langIdx++)
|
||||
{
|
||||
var linesInSingleLang = _multiLangLyricsLines[langIdx];
|
||||
for (int i = 0; i < linesInSingleLang.Count; i++)
|
||||
{
|
||||
if (i + 1 < linesInSingleLang.Count)
|
||||
{
|
||||
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
linesInSingleLang[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = linesInSingleLang[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
{
|
||||
if (j + 1 < timings.Count)
|
||||
{
|
||||
timings[j].EndMs = timings[j + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
timings[j].EndMs = linesInSingleLang[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (linesInSingleLang.Count > 0 && linesInSingleLang[0].StartMs > 0)
|
||||
{
|
||||
linesInSingleLang.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = linesInSingleLang[0].StartMs,
|
||||
OriginalText = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
_lyricsDataArr.Add(new LyricsData(lyricsLines));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
public static class MetadataHelper
|
||||
{
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class MonitorHelper
|
||||
{
|
||||
public static IEnumerable<string> GetAllMonitorDeviceNames()
|
||||
{
|
||||
var deviceNames = new List<string>();
|
||||
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
|
||||
{
|
||||
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
|
||||
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
|
||||
{
|
||||
deviceNames.Add(monitorInfoEx.szDevice);
|
||||
}
|
||||
return true; // 继续枚举
|
||||
}, IntPtr.Zero);
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
public static User32.MONITORINFOEX GetMonitorInfoExFromDeviceName(string deviceName)
|
||||
{
|
||||
User32.MONITORINFOEX? result = null;
|
||||
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
|
||||
{
|
||||
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
|
||||
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
|
||||
{
|
||||
if (string.Equals(monitorInfoEx.szDevice, deviceName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = monitorInfoEx;
|
||||
return false; // 找到后停止枚举
|
||||
}
|
||||
}
|
||||
return true; // 继续枚举
|
||||
}, IntPtr.Zero);
|
||||
return result ?? GetPrimaryMonitorInfoEx();
|
||||
}
|
||||
|
||||
public static User32.MONITORINFOEX GetPrimaryMonitorInfoEx()
|
||||
{
|
||||
// (0,0) 总是在主屏
|
||||
var ptZero = new POINT(0, 0);
|
||||
HMONITOR hMonitor = User32.MonitorFromPoint(ptZero, User32.MonitorFlags.MONITOR_DEFAULTTOPRIMARY);
|
||||
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
|
||||
User32.GetMonitorInfo(hMonitor, ref monitorInfoEx);
|
||||
return monitorInfoEx;
|
||||
}
|
||||
|
||||
public static string GetPrimaryMonitorDeviceName()
|
||||
{
|
||||
var primaryMonitorInfo = GetPrimaryMonitorInfoEx();
|
||||
return primaryMonitorInfo.szDevice;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/NetHelper.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class NetHelper
|
||||
{
|
||||
public static async Task<bool> CheckConnectivity(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new System.Net.Http.HttpClient();
|
||||
// Try to reach a reliable endpoint
|
||||
var res = await client.GetAsync(url);
|
||||
return res.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // If any exception occurs, assume no connectivity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ObjectHelper
|
||||
{
|
||||
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
|
||||
public static bool IsDisposed(this IDisposable obj)
|
||||
{
|
||||
/*
|
||||
TIM C: This hacky code is because MSFT does not provide a standard way to interrogate if an object is disposed or not.
|
||||
I wrote this based upon streams, but it should work for many other types of MSFT objects (maybe).
|
||||
*/
|
||||
if (obj == null) { return true; }
|
||||
|
||||
var objType = obj.GetType();
|
||||
//var foo = new System.IO.BufferedStream();
|
||||
|
||||
// the _disposed pattern should catch a lot of msft objects.... hopefully
|
||||
var isDisposedField = objType.GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance) ??
|
||||
objType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
isDisposedField = objType.GetField("_isOpen", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
// Windows.Graphics.Imaging.SoftwareBitmap
|
||||
isDisposedField = objType.GetField("_objRef_global__System_IDisposable", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
// System.IO.FileStream
|
||||
var strategyField = objType.GetField("_strategy", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (strategyField != null)
|
||||
{
|
||||
var strategy = strategyField.GetValue(obj);
|
||||
var isClosedField = strategy.GetType().GetProperty("IsClosed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isClosedField != null) { return Convert.ToBoolean(isClosedField.GetValue(strategy)); }
|
||||
}
|
||||
|
||||
// other streams that use this pattern to determine if they are disposed
|
||||
if (obj is Stream stream) { return !stream.CanRead && !stream.CanWrite; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class PathHelper
|
||||
{
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
//public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
|
||||
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Wiki82.profile.xml");
|
||||
public static string LogoPath => Path.Combine(AssetsFolder, "Logo.ico");
|
||||
public static string AIMPLogoPath => Path.Combine(AssetsFolder, "AIMP.png");
|
||||
public static string Foobar2000LogoPath => Path.Combine(AssetsFolder, "foobar2000.png");
|
||||
public static string MusicBeeLogoPath => Path.Combine(AssetsFolder, "MusicBee.png");
|
||||
public static string SpotifyLogoPath => Path.Combine(AssetsFolder, "Spotify.png");
|
||||
public static string AppleMusicLogoPath => Path.Combine(AssetsFolder, "AppleMusic.png");
|
||||
public static string iTunesLogoPath => Path.Combine(AssetsFolder, "iTunes.png");
|
||||
public static string KugouMusicLogoPath => Path.Combine(AssetsFolder, "KugouMusic.png");
|
||||
public static string NetEaseCloudMusicLogoPath => Path.Combine(AssetsFolder, "NetEaseCloudMusic.png");
|
||||
public static string QQMusicLogoPath => Path.Combine(AssetsFolder, "QQMusic.png");
|
||||
public static string LXMusicLogoPath => Path.Combine(AssetsFolder, "LXMusic.png");
|
||||
public static string MediaPlayerWindows11LogoPath => Path.Combine(AssetsFolder, "MediaPlayerWindows11.png");
|
||||
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 UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
|
||||
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string LyricsCacheDirectory => Path.Combine(CacheFolder, "lyrics");
|
||||
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "lrclib");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "netease");
|
||||
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 AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.json");
|
||||
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
|
||||
|
||||
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
|
||||
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
|
||||
public static string iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class StringHelper
|
||||
{
|
||||
// 去除空格、括号、下划线、横杠、点、大小写等
|
||||
public static string Normalize(string s) =>
|
||||
new string(s
|
||||
.Where(c => char.IsLetterOrDigit(c))
|
||||
.ToArray())
|
||||
.ToLowerInvariant();
|
||||
public static string NewLine = "\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using Vanara.Extensions;
|
||||
using Vanara.PInvoke;
|
||||
using static Vanara.PInvoke.CoreAudio;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class SystemVolumeHelper
|
||||
{
|
||||
private readonly static IMMDeviceEnumerator _deviceEnumerator = new();
|
||||
private static IAudioEndpointVolume? _endpointVolume = null;
|
||||
private static VolumeCallbackImpl? _callbackImpl;
|
||||
private static int _masterVolume = 0;
|
||||
private static DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public static event Action<int>? VolumeChanged;
|
||||
|
||||
static SystemVolumeHelper()
|
||||
{
|
||||
var device = _deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
|
||||
if (device != null)
|
||||
{
|
||||
device.Activate(typeof(IAudioEndpointVolume).GUID, 0, null, out var obj);
|
||||
if (obj is IAudioEndpointVolume endpointVolume)
|
||||
{
|
||||
_endpointVolume = endpointVolume;
|
||||
_callbackImpl = new VolumeCallbackImpl();
|
||||
_endpointVolume.RegisterControlChangeNotify(_callbackImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前系统主音量(0~100)。
|
||||
/// </summary>
|
||||
public static int GetMasterVolume()
|
||||
{
|
||||
if (_endpointVolume != null)
|
||||
{
|
||||
float level = _endpointVolume.GetMasterVolumeLevelScalar();
|
||||
_masterVolume = (int)(level * 100);
|
||||
}
|
||||
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前系统主音量(0~100)。
|
||||
/// </summary>
|
||||
public static void SetMasterVolume(int volume)
|
||||
{
|
||||
if (_masterVolume == volume) return;
|
||||
|
||||
_masterVolume = volume;
|
||||
_endpointVolume?.SetMasterVolumeLevelScalar(_masterVolume / 100f, Guid.Empty);
|
||||
}
|
||||
|
||||
// 内部回调实现
|
||||
private class VolumeCallbackImpl : IAudioEndpointVolumeCallback
|
||||
{
|
||||
HRESULT IAudioEndpointVolumeCallback.OnNotify(nint pNotify)
|
||||
{
|
||||
var data = pNotify.ToStructure<AUDIO_VOLUME_NOTIFICATION_DATA>();
|
||||
_masterVolume = (int)(data.fMasterVolume * 100);
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
VolumeChanged?.Invoke(_masterVolume);
|
||||
});
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ThrottleHelper
|
||||
{
|
||||
private DateTime _lastTriggerTime = DateTime.MinValue;
|
||||
private readonly TimeSpan _interval;
|
||||
|
||||
public ThrottleHelper(TimeSpan interval)
|
||||
{
|
||||
_interval = interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以触发(距离上次触发已超过设定间隔),如果可以则更新时间戳并返回 true,否则返回 false。
|
||||
/// </summary>
|
||||
public bool CanTrigger()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastTriggerTime) >= _interval)
|
||||
{
|
||||
_lastTriggerTime = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置触发时间
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_lastTriggerTime = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,6 @@ using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class AnimationHelper
|
||||
{
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
}
|
||||
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
{
|
||||
@@ -52,11 +45,18 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
else
|
||||
{
|
||||
_easingType = EasingType.SmoothStep;
|
||||
_easingType = EasingType.Linear;
|
||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDuration(float seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
|
||||
_durationSeconds = seconds;
|
||||
}
|
||||
|
||||
private void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
if (!_isTransitioning) return;
|
||||
|
||||
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
|
||||
_progress += (float)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
@@ -1,148 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class WindowColorHelper
|
||||
{
|
||||
public static Color GetDominantColor(IntPtr myHwnd, WindowColorSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return Color.Transparent;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case WindowColorSampleMode.BelowWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowColorSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return Color.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
case WindowColorSampleMode.WindowEdge:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return Color.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
List<Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.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
|
||||
)
|
||||
);
|
||||
// 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
|
||||
)
|
||||
);
|
||||
// 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 (edgeColors.Count == 0)
|
||||
return Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
foreach (var c in edgeColors)
|
||||
{
|
||||
r += c.R;
|
||||
g += c.G;
|
||||
b += c.B;
|
||||
}
|
||||
return Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return Color.Transparent;
|
||||
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
@@ -25,17 +26,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitAllWindows()
|
||||
{
|
||||
while (_activeWindows.Count > 0)
|
||||
{
|
||||
var window = _activeWindows[0];
|
||||
((Window)window).Close();
|
||||
_activeWindows.Remove(window);
|
||||
}
|
||||
App.Current.Exit();
|
||||
}
|
||||
|
||||
public static T? GetWindowByWindowType<T>()
|
||||
{
|
||||
foreach (var window in _activeWindows)
|
||||
@@ -47,32 +37,33 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public static void OpenOrShowWindow<T>()
|
||||
public static void OpenWindow<T>()
|
||||
{
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window != null)
|
||||
if (window == null)
|
||||
{
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
}
|
||||
else
|
||||
{
|
||||
object newWindow;
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
newWindow = new LyricsWindow();
|
||||
window = new LyricsWindow();
|
||||
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
}
|
||||
else if (typeof(T) == typeof(SettingsWindow))
|
||||
{
|
||||
newWindow = new SettingsWindow();
|
||||
window = new SettingsWindow();
|
||||
}
|
||||
else if (typeof(T) == typeof(MusicGalleryWindow))
|
||||
{
|
||||
window = new MusicGalleryWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unsupported window type", nameof(T));
|
||||
}
|
||||
((Window)newWindow).Activate();
|
||||
TrackWindow(newWindow);
|
||||
TrackWindow(window);
|
||||
}
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
castedWindow.Activate();
|
||||
}
|
||||
|
||||
public static void RestartApp(string args = "")
|
||||
@@ -95,10 +86,32 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitApp()
|
||||
{
|
||||
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
DockModeHelper.Disable(lyricsWindow);
|
||||
}
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private static void TrackWindow(object window)
|
||||
{
|
||||
if (!_activeWindows.Contains(window))
|
||||
{
|
||||
_activeWindows.Add(window);
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Closed += WindowHelper_Closed;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
if (_activeWindows.Contains(sender))
|
||||
{
|
||||
_activeWindows.Remove(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class AlbumArtSearchProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AlbumArtSearchProvider Provider { get; set; }
|
||||
|
||||
public AlbumArtSearchProviderInfo() { }
|
||||
|
||||
public AlbumArtSearchProviderInfo(AlbumArtSearchProvider provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class DetectLanguageResult
|
||||
{
|
||||
[JsonPropertyName("confidence")]
|
||||
public double Confidence { get; set; }
|
||||
|
||||
[JsonPropertyName("language")]
|
||||
public string Language { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class GroupInfoList : List<object>
|
||||
{
|
||||
public required object Key { get; set; }
|
||||
|
||||
public GroupInfoList(IEnumerable<object> items, Func<object, object>? orderSelector = null)
|
||||
: base(orderSelector != null
|
||||
? items.OrderBy(orderSelector)
|
||||
: items)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Key}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LocalLyricsFolder : ObservableObject
|
||||
public partial class LocalMediaFolder : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
@@ -12,9 +12,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string Path { get; set; }
|
||||
|
||||
public LocalLyricsFolder() { }
|
||||
public LocalMediaFolder() { }
|
||||
|
||||
public LocalLyricsFolder(string path, bool isEnabled)
|
||||
public LocalMediaFolder(string path, bool isEnabled)
|
||||
{
|
||||
Path = path;
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class CharTiming
|
||||
public class LyricsChar
|
||||
{
|
||||
public int EndMs { get; set; }
|
||||
public int? EndMs { get; set; }
|
||||
public int StartIndex { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||