Compare commits

...

388 Commits

Author SHA1 Message Date
Zhe Fang
78bafb8508 fix: sponsor date 2026-01-06 22:45:09 -05:00
Zhe Fang
b4d24c5570 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-06 22:43:07 -05:00
Zhe Fang
83c9f9806d chores: update sponsors 2026-01-06 22:43:06 -05:00
Zhe Fang
adde74afb0 Update SPONSORS.md 2026-01-06 22:39:10 -05:00
Zhe Fang
67b4d4e409 chores: delete unused pre-defined path 2026-01-06 21:43:30 -05:00
Zhe Fang
8d7fbe63c5 fix: amll-ttml-db lyrics searching issue (due to metadata matching calculator error) 2026-01-06 16:43:16 -05:00
Zhe Fang
5037b92913 chores: change lyrics cache system from .json based to database based 2026-01-06 11:17:06 -05:00
Zhe Fang
c1ee7a6779 feat: add bg support for lrc (extended) 2026-01-05 19:43:41 -05:00
Zhe Fang
7ddfd1118b feat: support bg lyrics 2026-01-05 19:32:30 -05:00
Zhe Fang
97f20decf2 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-05 09:54:59 -05:00
Zhe Fang
81eb4e1c96 chores: update patron name 2026-01-05 09:54:57 -05:00
Zhe Fang
00ee4a051c Update SPONSORS.md 2026-01-05 09:17:36 -05:00
Zhe Fang
a13bb6e8e4 chores: bump to 1.2.243.0 2026-01-04 16:43:02 -05:00
Zhe Fang
0b436c1ea9 chores: rollback UpdateLyrics 2026-01-04 16:21:33 -05:00
Zhe Fang
5d332fdfc6 fix: media sessions record issue 2026-01-04 16:02:11 -05:00
Zhe Fang
572d2cd8ba fix 2026-01-04 14:54:15 -05:00
Zhe Fang
1e5a95c55e chores: bump to v1.2.240.0 2026-01-04 12:05:53 -05:00
Zhe Fang
18ce6d3a57 chores: improve thanks list 2026-01-04 11:39:47 -05:00
Zhe Fang
427aed6857 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-04 09:51:53 -05:00
Zhe Fang
ebfa484a2e fix: GSMTC 2026-01-04 09:51:51 -05:00
Zhe Fang
3ef9d81bea Update README.CN.md 2026-01-04 06:45:25 -05:00
Zhe Fang
e999d07834 Update README.md 2026-01-04 06:43:52 -05:00
Zhe Fang
838b8de94f Update README.md 2026-01-04 06:43:20 -05:00
Zhe Fang
b3059dbeb1 chores: improve GSMTC service 2026-01-03 22:02:08 -05:00
Zhe Fang
6fea88a6a1 fix: stats dashboard ui 2026-01-03 17:27:47 -05:00
Zhe Fang
abca9ae5fb fix: auto-play in music gallery wont show song title and artist when first play 2026-01-03 12:50:33 -05:00
Zhe Fang
a062897e1a fix: music gallery play issue (and improve ui/ux) 2026-01-03 12:14:54 -05:00
Zhe Fang
8b4748df1b fix: stats dashboard data selection issue 2026-01-02 12:05:10 -05:00
Zhe Fang
1df5ea6bab fix: playback realtime info encoded cache path 2026-01-02 08:27:03 -05:00
Zhe Fang
c576635af2 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-01 21:49:33 -05:00
Zhe Fang
c8590202ec chores: improve music stats 2026-01-01 21:49:31 -05:00
Zhe Fang
2dc8b1283f Update README.CN.md 2026-01-01 12:38:31 -05:00
Zhe Fang
c482edea0f Update README.md 2026-01-01 12:36:39 -05:00
Zhe Fang
315722252c Update README.md 2026-01-01 12:32:01 -05:00
Zhe Fang
32ba453264 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 16:30:16 -05:00
Zhe Fang
d4902329bb chores: update readme 2025-12-31 16:30:14 -05:00
Zhe Fang
83aee8948b Update README.CN.md 2025-12-31 16:13:48 -05:00
Zhe Fang
1f9fab3228 Update README.CN.md 2025-12-31 16:10:42 -05:00
Zhe Fang
7a3a659dfc Update README.CN.md 2025-12-31 16:09:17 -05:00
Zhe Fang
a14afd3eb5 Update README.md 2025-12-31 16:01:37 -05:00
Zhe Fang
c2af7f3186 Update README.CN.md 2025-12-31 16:00:16 -05:00
Zhe Fang
cd026dd2bf Update README.CN.md 2025-12-31 15:56:08 -05:00
Zhe Fang
4bc1a9975d Update README.md 2025-12-31 15:52:05 -05:00
Zhe Fang
07eecf0930 Update README.md 2025-12-31 15:49:22 -05:00
Zhe Fang
35fba5abb0 chores 2025-12-31 15:44:58 -05:00
Zhe Fang
03ef231a3f chores 2025-12-31 15:39:44 -05:00
Zhe Fang
f41879f4e5 chores: add pic 2025-12-31 15:38:18 -05:00
Zhe Fang
bda7510ed6 chores: update readme 2025-12-31 14:28:54 -05:00
Zhe Fang
5ec8c7c61f chores: bump to 1.2.236 2025-12-31 13:32:45 -05:00
Zhe Fang
7e6bd9dade fix: _scrobbleStopwatch 2025-12-31 10:55:56 -05:00
Zhe Fang
56244cb793 fix: scrollable timer 2025-12-31 10:47:43 -05:00
Zhe Fang
cb5f70ab55 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 10:12:29 -05:00
Zhe Fang
3cc018bb1f chores: bump to 1.2.234.0 2025-12-31 10:12:28 -05:00
Zhe Fang
c517d2b008 Update README.md 2025-12-31 10:08:58 -05:00
Zhe Fang
e79f2a0223 Update README.CN.md 2025-12-31 10:07:53 -05:00
Zhe Fang
39122b9147 Update README.CN.md 2025-12-31 10:07:01 -05:00
Zhe Fang
accbdc1806 Update README.md 2025-12-31 10:06:01 -05:00
Zhe Fang
de014d1ad7 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 09:52:12 -05:00
Zhe Fang
cc2ce5f8cf chores: i18n 2025-12-31 09:52:10 -05:00
Zhe Fang
2a2d80436e Update README.CN.md 2025-12-31 09:34:29 -05:00
Zhe Fang
ce3f79f35c Update README.md 2025-12-31 09:33:01 -05:00
Zhe Fang
12e6000cb3 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 08:43:44 -05:00
Zhe Fang
c1dc684411 fix: scrobble timer is still running when music is paused 2025-12-31 08:43:43 -05:00
Zhe Fang
69ea2cb495 Update README.md 2025-12-31 08:21:33 -05:00
Zhe Fang
e2ee03c4be Update README.CN.md 2025-12-31 08:21:03 -05:00
Zhe Fang
c6fe33d6ae Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 08:12:12 -05:00
Zhe Fang
7744e145fa chores: i18n 2025-12-31 08:12:10 -05:00
Zhe Fang
0284b1de81 Update README.CN.md 2025-12-30 21:46:32 -05:00
Zhe Fang
108c2cd34b Update README.md 2025-12-30 21:45:56 -05:00
Zhe Fang
390e30f7f5 chores: bump to v1.2.232.0 2025-12-30 20:54:40 -05:00
Zhe Fang
900774668d chores: i18n 2025-12-30 20:09:02 -05:00
Zhe Fang
6ca2d1f897 chores 2025-12-30 19:49:54 -05:00
Zhe Fang
164bd077b8 fix: app crash when audio device was not found 2025-12-30 14:30:10 -05:00
Zhe Fang
8ec71fcfb7 chores: update StatsDashboardControl 2025-12-30 14:04:08 -05:00
Zhe Fang
f39ad54df8 fix: record play history 2025-12-30 13:02:23 -05:00
Zhe Fang
9b809983df chores: i18n 2025-12-30 08:59:30 -05:00
Zhe Fang
8006b3a443 chores: i18n 2025-12-30 08:41:12 -05:00
Zhe Fang
26a7454de2 feat: play history 2025-12-30 08:02:48 -05:00
Zhe Fang
0793a074cf fix: add to playlists 2025-12-29 16:11:21 -05:00
Zhe Fang
125bf1682e chores 2025-12-29 12:15:39 -05:00
Zhe Fang
48bdffb2fe Merge pull request #179 from jayfunc/l10n_dev
New Crowdin updates
2025-12-28 21:37:57 -05:00
Zhe Fang
d324a7552f New translations resources.resw (Chinese Traditional) 2025-12-28 21:37:17 -05:00
Zhe Fang
78c308c393 New translations resources.resw (Japanese) 2025-12-28 21:37:15 -05:00
Zhe Fang
a1bba00db6 chores: i18n 2025-12-28 21:31:37 -05:00
Zhe Fang
0787f5b111 fix 2025-12-28 21:10:02 -05:00
Zhe Fang
884026594b fix: ignore system, hidden files and unsupported format files 2025-12-28 21:03:56 -05:00
Zhe Fang
b0a777db8d chores: adjust layout and fix bugs 2025-12-28 20:01:41 -05:00
Zhe Fang
83f3a3bd6d chores: fix SMB and local file system, add auto-sync, improve lyruics search, album art search, local music gallery load speed (after 1st time) 2025-12-27 15:25:49 -05:00
Zhe Fang
bfb2ed29e5 fix: window title is synced with config name 2025-12-25 13:44:43 -05:00
Zhe Fang
131a0f0eb1 Merge pull request #172 from jayfunc/l10n_dev
New Crowdin updates
2025-12-24 19:24:20 -05:00
Zhe Fang
ac2a7b3f7b New translations resources.resw (Malay) 2025-12-24 19:22:52 -05:00
Zhe Fang
36eea7f8f2 New translations resources.resw (Hindi) 2025-12-24 19:22:52 -05:00
Zhe Fang
6b338deb55 New translations resources.resw (Thai) 2025-12-24 19:22:51 -05:00
Zhe Fang
af323ecd00 New translations resources.resw (Indonesian) 2025-12-24 19:22:50 -05:00
Zhe Fang
c79d01c75b New translations resources.resw (Vietnamese) 2025-12-24 19:22:49 -05:00
Zhe Fang
b51ec1e60f New translations resources.resw (Chinese Traditional) 2025-12-24 19:22:48 -05:00
Zhe Fang
7fe925bcba New translations resources.resw (Chinese Simplified) 2025-12-24 19:22:47 -05:00
Zhe Fang
0626472d66 New translations resources.resw (Russian) 2025-12-24 19:22:46 -05:00
Zhe Fang
33099bc186 New translations resources.resw (Portuguese) 2025-12-24 19:22:45 -05:00
Zhe Fang
e653efc227 New translations resources.resw (Korean) 2025-12-24 19:22:44 -05:00
Zhe Fang
074fef3faf New translations resources.resw (Japanese) 2025-12-24 19:22:43 -05:00
Zhe Fang
029cbbd343 New translations resources.resw (German) 2025-12-24 19:22:42 -05:00
Zhe Fang
802b2a4c1c New translations resources.resw (Arabic) 2025-12-24 19:22:41 -05:00
Zhe Fang
eccc4d519c New translations resources.resw (Spanish) 2025-12-24 19:22:39 -05:00
Zhe Fang
5f274ea28a New translations resources.resw (French) 2025-12-24 19:22:37 -05:00
Zhe Fang
aa1a1f5d58 chores: i18n 2025-12-24 19:22:16 -05:00
Zhe Fang
3a56d53487 New translations resources.resw (Malay) 2025-12-24 19:15:10 -05:00
Zhe Fang
bbc5eb772c New translations resources.resw (Hindi) 2025-12-24 19:15:09 -05:00
Zhe Fang
05b491052b New translations resources.resw (Thai) 2025-12-24 19:15:08 -05:00
Zhe Fang
8accbf0431 New translations resources.resw (Indonesian) 2025-12-24 19:15:07 -05:00
Zhe Fang
1174209c2a New translations resources.resw (Vietnamese) 2025-12-24 19:15:06 -05:00
Zhe Fang
23ed719046 New translations resources.resw (Chinese Traditional) 2025-12-24 19:15:05 -05:00
Zhe Fang
a34f00662e New translations resources.resw (Chinese Simplified) 2025-12-24 19:15:04 -05:00
Zhe Fang
f783314258 New translations resources.resw (Russian) 2025-12-24 19:15:03 -05:00
Zhe Fang
215a39c5d5 New translations resources.resw (Portuguese) 2025-12-24 19:15:02 -05:00
Zhe Fang
16bcef5f64 New translations resources.resw (Korean) 2025-12-24 19:15:01 -05:00
Zhe Fang
fbba9a3c36 New translations resources.resw (Japanese) 2025-12-24 19:15:00 -05:00
Zhe Fang
f205ab0364 New translations resources.resw (German) 2025-12-24 19:14:59 -05:00
Zhe Fang
10314f3c2f New translations resources.resw (Arabic) 2025-12-24 19:14:58 -05:00
Zhe Fang
b4710e87d3 New translations resources.resw (Spanish) 2025-12-24 19:14:57 -05:00
Zhe Fang
282a934cd2 New translations resources.resw (French) 2025-12-24 19:14:56 -05:00
Zhe Fang
b4c4e394ef Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-24 18:36:28 -05:00
Zhe Fang
17cfdf37bd chores: i18n 2025-12-24 18:36:26 -05:00
Zhe Fang
900a8e1e7c Update credit wording in CONTRIBUTING.md 2025-12-24 11:55:18 -05:00
Zhe Fang
ea9a9c2f5f Update translation contribution instructions in README
Added a link for contributors to find more information.
2025-12-24 11:52:00 -05:00
Zhe Fang
0c4d02b337 Revise translation assistance information
Updated translation assistance section with a link for contributions.
2025-12-24 11:51:38 -05:00
Zhe Fang
d137d82ecf Update translation assistance section in README
Added a section for translation assistance and removed the previous translation section.
2025-12-24 11:51:07 -05:00
Zhe Fang
02551e2053 Add translation contribution section to README
Added a section encouraging users to help translate the project and contribute.
2025-12-24 11:50:10 -05:00
Zhe Fang
026926e9b8 Update CONTRIBUTING.md 2025-12-24 11:39:08 -05:00
Zhe Fang
4c811db16a Update README.CN.md with translation and donation info
Added a section for translation contributions and donation support.
2025-12-24 11:36:29 -05:00
Zhe Fang
6f83fa11db Revise contributing guidelines for translations
Updated the contributing guidelines to include translation information in both English and Chinese. Added a status table for languages and contributors.
2025-12-24 11:34:55 -05:00
Zhe Fang
bc8e15c144 Update translation section in README.md
Added a section for translation contributions and removed the previous translation details.
2025-12-24 11:32:16 -05:00
Zhe Fang
85de1eb2cd Add Chinese translation guidelines to CONTRIBUTING.md 2025-12-24 11:29:00 -05:00
Zhe Fang
d2bf19ed3d Add translation contribution guidelines
Added instructions for translating BetterLyrics using Crowdin.
2025-12-24 11:27:23 -05:00
Zhe Fang
43c205c839 Update Japanese language support status in README 2025-12-24 11:07:35 -05:00
Zhe Fang
9664b1ab78 Update Japanese language entry in README.CN.md 2025-12-24 11:07:00 -05:00
Zhe Fang
08c5f6b515 Add contributor link for Japanese language support 2025-12-24 11:06:31 -05:00
Zhe Fang
260de40f81 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-24 11:05:41 -05:00
Zhe Fang
c00d0eb005 fix: duration issue 2025-12-24 11:05:39 -05:00
Zhe Fang
32e761724c Merge pull request #170 from jayfunc/l10n_dev
New Crowdin updates
2025-12-24 11:05:05 -05:00
Zhe Fang
9fd08af582 New translations resources.resw (Japanese) 2025-12-24 11:03:57 -05:00
Zhe Fang
266dcfc930 New translations resources.resw (Chinese Simplified) 2025-12-24 10:43:52 -05:00
Zhe Fang
8764585f2c New translations resources.resw (Japanese) 2025-12-24 10:43:50 -05:00
Zhe Fang
91ab3a48c0 New translations resources.resw (Chinese Simplified) 2025-12-24 09:23:59 -05:00
Zhe Fang
80fa34d9e8 New translations resources.resw (Japanese) 2025-12-24 09:23:58 -05:00
Zhe Fang
b4ca4fd990 New translations resources.resw (Chinese Traditional) 2025-12-24 08:00:35 -05:00
Zhe Fang
86527f6b82 New translations resources.resw (Japanese) 2025-12-24 08:00:34 -05:00
Zhe Fang
d8066bc683 New translations resources.resw (Japanese) 2025-12-24 06:04:12 -05:00
Zhe Fang
b261a86791 New translations resources.resw (Japanese) 2025-12-24 04:35:20 -05:00
Zhe Fang
34f2a51b74 New translations resources.resw (Japanese) 2025-12-24 00:10:01 -05:00
Zhe Fang
b1e9c25e01 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-23 20:43:31 -05:00
Zhe Fang
346de93c3f fix: AlbumArtThemeColors is not updated when PaletteGeneratorType is changed 2025-12-23 20:43:29 -05:00
Zhe Fang
6f48cbcd16 Add Contributor Covenant Code of Conduct
This document outlines the Contributor Covenant Code of Conduct, detailing our pledge, standards, enforcement responsibilities, and consequences for violations.
2025-12-23 18:02:06 -05:00
Zhe Fang
85b3121479 chores 2025-12-23 15:48:39 -05:00
Zhe Fang
94f00d1a31 chores 2025-12-23 14:10:35 -05:00
Zhe Fang
be9e4bba0f chores: update readme 2025-12-23 14:04:48 -05:00
Zhe Fang
2454927582 Merge pull request #169 from jayfunc/l10n_dev
New Crowdin updates
2025-12-23 13:44:27 -05:00
Zhe Fang
aca5f8e00d New translations resources.resw (Malay) 2025-12-23 13:43:43 -05:00
Zhe Fang
09709e8e62 New translations resources.resw (Hindi) 2025-12-23 13:43:42 -05:00
Zhe Fang
98fd8b43c4 New translations resources.resw (Thai) 2025-12-23 13:43:41 -05:00
Zhe Fang
3051180eb9 New translations resources.resw (Indonesian) 2025-12-23 13:43:38 -05:00
Zhe Fang
d48c81cfa1 New translations resources.resw (Vietnamese) 2025-12-23 13:43:37 -05:00
Zhe Fang
695147be9b New translations resources.resw (Portuguese) 2025-12-23 13:43:36 -05:00
Zhe Fang
e782944a44 New translations resources.resw (Arabic) 2025-12-23 13:43:35 -05:00
Zhe Fang
01462d42ce New translations resources.resw (Russian) 2025-12-23 13:43:33 -05:00
Zhe Fang
65b7dfcc44 New translations resources.resw (Japanese) 2025-12-23 13:43:31 -05:00
Zhe Fang
ec3146d4a7 New translations resources.resw (German) 2025-12-23 13:43:30 -05:00
Zhe Fang
9ec0bf0b1a New translations resources.resw (Spanish) 2025-12-23 13:43:29 -05:00
Zhe Fang
47e4b93613 New translations resources.resw (French) 2025-12-23 13:43:28 -05:00
Zhe Fang
192ad4a503 fix: i18n 2025-12-23 13:08:42 -05:00
Zhe Fang
091e33ae08 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-23 13:04:44 -05:00
Zhe Fang
3b010ed674 add: more langs 2025-12-23 13:04:42 -05:00
Zhe Fang
a9f685d51b Merge pull request #168 from jayfunc/l10n_dev
New Crowdin updates
2025-12-23 13:04:04 -05:00
Zhe Fang
c6c31f8839 New translations resources.resw (Chinese Traditional) 2025-12-23 13:03:26 -05:00
Zhe Fang
78c53760cc New translations resources.resw (Chinese Simplified) 2025-12-23 13:03:25 -05:00
Zhe Fang
0bb6b5a204 New translations resources.resw (Chinese Traditional) 2025-12-23 12:34:51 -05:00
Zhe Fang
dff36a5e4d New translations resources.resw (Chinese Simplified) 2025-12-23 12:34:50 -05:00
Zhe Fang
0188e443db New translations resources.resw (Chinese Traditional) 2025-12-23 11:05:21 -05:00
Zhe Fang
5a9cdedc0c New translations resources.resw (Chinese Simplified) 2025-12-23 11:05:19 -05:00
Zhe Fang
31460fcc6d New translations resources.resw (Russian) 2025-12-23 11:05:18 -05:00
Zhe Fang
c12fc6f381 New translations resources.resw (Korean) 2025-12-23 11:05:16 -05:00
Zhe Fang
e5e0342994 New translations resources.resw (Japanese) 2025-12-23 11:05:15 -05:00
Zhe Fang
061958f20c New translations resources.resw (German) 2025-12-23 11:05:14 -05:00
Zhe Fang
95c73d0a34 New translations resources.resw (Spanish) 2025-12-23 11:05:13 -05:00
Zhe Fang
026a12ac87 New translations resources.resw (French) 2025-12-23 11:05:11 -05:00
Zhe Fang
da53f2166f Update contributors for Simplified Chinese section 2025-12-23 09:59:48 -05:00
Zhe Fang
717277e17c Update contributors for Simplified Chinese translation 2025-12-23 09:59:02 -05:00
Zhe Fang
1dc3ea57e9 chores: delete unused res items 2025-12-23 09:54:07 -05:00
Zhe Fang
4ec2ba8b59 chores: delete unused res item 2025-12-23 09:49:38 -05:00
Zhe Fang
91d9f253f0 Merge pull request #167 from jayfunc/l10n_dev
New Crowdin updates
2025-12-23 09:38:59 -05:00
Zhe Fang
90cf373e50 New translations resources.resw (Chinese Simplified) 2025-12-23 09:37:57 -05:00
Zhe Fang
cf2778da7a Add contributor table and translation invitation link 2025-12-23 09:27:10 -05:00
Zhe Fang
45ff7d7aa8 Update README with language and contributor information
Added a table listing supported languages and contributors.
2025-12-23 09:24:18 -05:00
Zhe Fang
eb37cb1b55 New translations resources.resw (Chinese Traditional) 2025-12-23 09:15:18 -05:00
Zhe Fang
45aa1d787d New translations resources.resw (Chinese Simplified) 2025-12-23 09:15:15 -05:00
Zhe Fang
0b28419ab5 New translations resources.resw (Russian) 2025-12-23 09:15:13 -05:00
Zhe Fang
258bf9220e New translations resources.resw (Korean) 2025-12-23 09:15:12 -05:00
Zhe Fang
9ece9f3edc New translations resources.resw (Japanese) 2025-12-23 09:15:11 -05:00
Zhe Fang
40c1f0a5ce New translations resources.resw (German) 2025-12-23 09:15:10 -05:00
Zhe Fang
5f75e6c63c New translations resources.resw (Spanish) 2025-12-23 09:15:09 -05:00
Zhe Fang
43387ce4c8 New translations resources.resw (French) 2025-12-23 09:15:08 -05:00
Zhe Fang
34eda9a262 fix: i18n 2025-12-23 08:51:38 -05:00
Zhe Fang
804673696f fix: i18n 2025-12-23 07:35:19 -05:00
Zhe Fang
b69e3bb24b chores: i18n 2025-12-23 07:12:36 -05:00
Zhe Fang
c028aa8e46 chores 2025-12-23 06:18:16 -05:00
Zhe Fang
fe3e257215 chores 2025-12-22 18:13:11 -05:00
Zhe Fang
eae2428d85 fix: lang 2025-12-22 17:19:09 -05:00
Zhe Fang
b078365136 fix: lang 2025-12-22 17:07:16 -05:00
Zhe Fang
1ede8dbef4 fix: song info fade 2025-12-22 16:45:41 -05:00
Zhe Fang
a66051b937 chores: i18n 2025-12-22 16:01:43 -05:00
Zhe Fang
1eca21c285 chores: i18n 2025-12-22 14:17:43 -05:00
Zhe Fang
2254a28e40 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-22 11:47:30 -05:00
Zhe Fang
812eca369d chores: improve i18n method 2025-12-22 11:47:29 -05:00
Zhe Fang
132d3d8ac8 Merge pull request #164 from jayfunc/l10n_dev
New Crowdin updates
2025-12-22 11:46:56 -05:00
Zhe Fang
641a23621f New translations resources.resw (Chinese Simplified) 2025-12-22 11:46:00 -05:00
Zhe Fang
6802d10142 Merge pull request #163 from jayfunc/l10n_dev
New Crowdin updates
2025-12-22 06:47:45 -05:00
Zhe Fang
36f43e6d54 New translations resources.resw (Chinese Simplified) 2025-12-22 06:46:42 -05:00
Zhe Fang
e8298ec7bd New translations resources.resw (Chinese Simplified) 2025-12-22 06:33:43 -05:00
Zhe Fang
99a21cb935 New translations resources.resw (German) 2025-12-21 19:17:28 -05:00
Zhe Fang
b6da7bea5d New translations resources.resw (Chinese Traditional) 2025-12-21 19:17:27 -05:00
Zhe Fang
cf5bf75346 New translations resources.resw (Russian) 2025-12-21 19:17:26 -05:00
Zhe Fang
7497d7014d New translations resources.resw (Korean) 2025-12-21 19:17:24 -05:00
Zhe Fang
dd8c62ffa5 New translations resources.resw (Japanese) 2025-12-21 19:17:24 -05:00
Zhe Fang
15b147ba06 New translations resources.resw (Spanish) 2025-12-21 19:17:23 -05:00
Zhe Fang
85146ffc95 New translations resources.resw (French) 2025-12-21 19:17:22 -05:00
Zhe Fang
e9dce765e4 fix: i18n 2025-12-21 18:18:25 -05:00
Zhe Fang
3b2c4477b5 Merge pull request #162 from jayfunc/l10n_dev
New Crowdin updates
2025-12-21 18:07:48 -05:00
Zhe Fang
9d71c4aecf New translations resources.resw (German) 2025-12-21 18:02:53 -05:00
Zhe Fang
7184c148c4 New translations resources.resw (Chinese Traditional) 2025-12-21 18:02:52 -05:00
Zhe Fang
85f928ce3b New translations resources.resw (Chinese Simplified) 2025-12-21 18:02:51 -05:00
Zhe Fang
7c5032b0c2 New translations resources.resw (Russian) 2025-12-21 18:02:50 -05:00
Zhe Fang
2c3bd056b7 New translations resources.resw (Korean) 2025-12-21 18:02:49 -05:00
Zhe Fang
9f2843b7a0 New translations resources.resw (Japanese) 2025-12-21 18:02:48 -05:00
Zhe Fang
7fb6d5346e New translations resources.resw (Spanish) 2025-12-21 18:02:47 -05:00
Zhe Fang
27125d9051 New translations resources.resw (French) 2025-12-21 18:02:46 -05:00
Zhe Fang
5b2fb8b345 fix: english 2025-12-21 17:06:10 -05:00
Zhe Fang
d558811cb4 Merge pull request #161 from jayfunc/l10n_dev
New Crowdin updates
2025-12-21 16:58:03 -05:00
Zhe Fang
6e30aa7ebd New translations resources.resw (Chinese Traditional) 2025-12-21 16:56:21 -05:00
Zhe Fang
15fc337944 Merge pull request #160 from jayfunc/l10n_dev
New Crowdin updates
2025-12-21 16:46:11 -05:00
Zhe Fang
b7ef159b9e New translations resources.resw (German) 2025-12-21 16:44:37 -05:00
Zhe Fang
393b33ed83 New translations resources.resw (Chinese Traditional) 2025-12-21 16:44:36 -05:00
Zhe Fang
23dfda4413 New translations resources.resw (Chinese Simplified) 2025-12-21 16:44:35 -05:00
Zhe Fang
fde7340f4d New translations resources.resw (Russian) 2025-12-21 16:44:34 -05:00
Zhe Fang
22330d7fe9 New translations resources.resw (Korean) 2025-12-21 16:44:33 -05:00
Zhe Fang
c64e5776e8 New translations resources.resw (Japanese) 2025-12-21 16:44:32 -05:00
Zhe Fang
ffa2cd75a0 New translations resources.resw (Spanish) 2025-12-21 16:44:31 -05:00
Zhe Fang
873e75a7e9 New translations resources.resw (French) 2025-12-21 16:44:30 -05:00
Zhe Fang
ffa4101d5f chores: i18n 2025-12-21 16:26:35 -05:00
Zhe Fang
1c12b582c2 chores: add help us translate message 2025-12-21 16:22:32 -05:00
Zhe Fang
c50d31ced7 chores 2025-12-21 16:03:10 -05:00
Zhe Fang
f8108151b6 Merge pull request #159 from jayfunc/l10n_dev
New Crowdin updates
2025-12-21 16:02:22 -05:00
Zhe Fang
2932366767 New translations resources.resw (German) 2025-12-21 16:01:50 -05:00
Zhe Fang
cbf643ca70 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-21 15:53:46 -05:00
Zhe Fang
a72d0f5c28 chores 2025-12-21 15:53:44 -05:00
Zhe Fang
3b4d98f9a3 Merge pull request #158 from jayfunc/l10n_dev
New Crowdin updates
2025-12-21 15:52:58 -05:00
Zhe Fang
d5828101d8 New translations resources.resw (Chinese Traditional) 2025-12-21 15:45:59 -05:00
Zhe Fang
56051537ea New translations resources.resw (Chinese Simplified) 2025-12-21 15:45:58 -05:00
Zhe Fang
6b465a09b1 New translations resources.resw (Russian) 2025-12-21 15:45:57 -05:00
Zhe Fang
450b86ebaf New translations resources.resw (Dutch) 2025-12-21 15:45:56 -05:00
Zhe Fang
c0078baa13 New translations resources.resw (Korean) 2025-12-21 15:45:55 -05:00
Zhe Fang
6b28212ec3 New translations resources.resw (Japanese) 2025-12-21 15:45:54 -05:00
Zhe Fang
9a3c2f5f70 New translations resources.resw (Spanish) 2025-12-21 15:45:54 -05:00
Zhe Fang
31be2bd8f7 New translations resources.resw (French) 2025-12-21 15:45:52 -05:00
Zhe Fang
47056e07a1 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-21 15:03:32 -05:00
Zhe Fang
f30673b9d3 chores: add langs 2025-12-21 15:03:31 -05:00
Zhe Fang
d8624c49d0 Update Crowdin configuration file 2025-12-21 14:45:18 -05:00
Zhe Fang
72810e7440 Update Crowdin configuration file 2025-12-21 14:39:11 -05:00
Zhe Fang
e881d36743 chores: i18n 2025-12-21 14:34:34 -05:00
Zhe Fang
aa3e79d3ff chores: i18n 2025-12-21 13:28:22 -05:00
Zhe Fang
9979474ce1 fix 2025-12-21 12:24:46 -05:00
Zhe Fang
2e7cd93cfe fix: renderer error 2025-12-21 08:04:26 -05:00
Zhe Fang
bdc31c3e0d fix: lyrics source search issue when id is not null; feat: support ftp, smb, webdav (still testing) 2025-12-21 06:34:11 -05:00
Zhe Fang
631d079aa2 chores: adjustment for album art info flyout 2025-12-19 16:29:36 -05:00
Zhe Fang
f76ef87167 feat: save album art to local 2025-12-19 16:10:14 -05:00
Zhe Fang
76aa5ee8d0 fix: search control result can not invoke play selected lyrics line 2025-12-19 14:46:23 -05:00
Zhe Fang
d7f4978a66 chores: update deps 2025-12-19 09:36:53 -05:00
Zhe Fang
0905c46e45 fix: scroll 2025-12-19 08:54:24 -05:00
Zhe Fang
d0991c5ddb fix: 3d lyrics effect incorrect y center 2025-12-17 20:22:26 -05:00
Zhe Fang
619a3ba196 chores: bump to v1.1.198.0 2025-12-17 15:55:33 -05:00
Zhe Fang
13526bb85c chores: i18n 2025-12-17 15:43:48 -05:00
Zhe Fang
61f4f608db fix 2025-12-17 15:06:22 -05:00
Zhe Fang
f690da8501 feat: add language info in playback settings 2025-12-17 14:52:01 -05:00
Zhe Fang
145c13a0e6 chores: improve lyrics window switch exp 2025-12-17 14:05:28 -05:00
Zhe Fang
cea4fbb54d fix: PasswordVaultHelper save issue 2025-12-17 10:54:44 -05:00
Zhe Fang
1d489c68e9 chores: bump to v1.1.194.0 2025-12-15 15:45:25 -05:00
Zhe Fang
90e7fa42d0 chores 2025-12-15 15:36:50 -05:00
Zhe Fang
29a6879e45 chores: remove csharp-kana 2025-12-15 14:58:51 -05:00
Zhe Fang
58499a2d09 chores: update i18n 2025-12-15 14:55:37 -05:00
Zhe Fang
580255699b chores: change romaji method 2025-12-15 14:42:11 -05:00
Zhe Fang
9cac7818f1 fix: system tray left click 2025-12-15 10:45:24 -05:00
Zhe Fang
118668a457 fix: timeline 2025-12-15 07:36:26 -05:00
Zhe Fang
37621dbf2a chores: adjust margin for NowPlayingBar 2025-12-15 07:29:26 -05:00
Zhe Fang
aa7d56f1cb chores: bump to v1.1.190.0 2025-12-14 11:56:24 -05:00
Zhe Fang
8dbe76e790 chores: change bg color for top title bar and now playing bar 2025-12-14 11:45:09 -05:00
Zhe Fang
de6410492e fix: Canvas_CreateResources 2025-12-14 11:20:04 -05:00
Zhe Fang
26df7c7f67 chores: bump to 1.1.188.0 2025-12-14 09:48:01 -05:00
Zhe Fang
3c411374bd chores: undo 2025-12-14 09:40:13 -05:00
Zhe Fang
99f0b9443b fix: spectrum count is not updating when chaning amount 2025-12-14 09:26:19 -05:00
Zhe Fang
a3bc148816 fix: prop clone 2025-12-14 09:18:49 -05:00
Zhe Fang
cea757702b feat: add settings item for always hiding unlock button 2025-12-14 09:17:27 -05:00
Zhe Fang
8938a5c798 feat: add settings item for stop music when closing music gallery window 2025-12-14 08:18:21 -05:00
Zhe Fang
46f4589b64 chores: dispose cover renderer when closed 2025-12-14 08:03:58 -05:00
Zhe Fang
adb02658f4 feat: add cover background 2025-12-14 07:58:41 -05:00
Zhe Fang
3d7e6061e9 fix: lyrics window settings config panel wont auto hide when open again 2025-12-14 06:52:08 -05:00
Zhe Fang
a51220c7b9 fix: playing line top offset out of bounds 2025-12-14 05:51:49 -05:00
Zhe Fang
22b813e687 chores: bump to v1.1.186.0 2025-12-13 17:02:55 -05:00
Zhe Fang
fda94d5020 fix: settings save issue 2025-12-13 16:54:08 -05:00
Zhe Fang
205cbe8fb6 fix: settings storage issue 2025-12-13 16:19:05 -05:00
Zhe Fang
816f7064db fix: fan style lyrics animation 2025-12-13 15:45:41 -05:00
Zhe Fang
132c5267b0 chores: bump to v1.1.184.0 2025-12-13 12:20:20 -05:00
Zhe Fang
4e866818df fix: font family issue 2025-12-13 12:11:50 -05:00
Zhe Fang
9b7b56a0ee Revise acknowledgments in README.CN.md
Updated the acknowledgments section to reflect ongoing support and gratitude towards contributors and users.
2025-12-13 09:46:47 -05:00
Zhe Fang
66f2da0e4c Revise acknowledgments and donation list in README
Updated the acknowledgments section to reflect user support and removed the manual donation list.
2025-12-13 09:46:04 -05:00
Zhe Fang
1735c6a7e6 Add sponsors list and gratitude section to SPONSORS.md 2025-12-13 09:44:10 -05:00
Zhe Fang
8c06c98068 Update README.CN.md to acknowledge supporters
Added a section to thank supporters and users.
2025-12-13 09:36:00 -05:00
Zhe Fang
e2ac4c166c Add acknowledgments for supporters in README
Added a section to acknowledge and thank supporters.
2025-12-13 09:34:22 -05:00
Zhe Fang
728397cafa chores: code cleanup 2025-12-13 08:39:48 -05:00
Zhe Fang
059787a28f chores: bump to v1.1.183.0 2025-12-13 08:16:23 -05:00
Zhe Fang
4c4231b48c fix: white line in fullscreen mode 2025-12-13 07:58:28 -05:00
Zhe Fang
2412927b29 Add Zread badge to README.CN.md 2025-12-12 16:53:45 -05:00
Zhe Fang
f3bdbba83e Add Zread badge to README 2025-12-12 16:53:30 -05:00
Zhe Fang
4c811b12ca Create initial wiki structure for BetterLyrics
Added comprehensive documentation for BetterLyrics, including sections on installation, configuration, architecture, user interface, media integration, and development.
2025-12-12 16:20:42 -05:00
Zhe Fang
933103c57f Update badge links in README.CN.md 2025-12-12 15:55:15 -05:00
Zhe Fang
718e7bdad3 Fix badge formatting in README.md 2025-12-12 15:52:48 -05:00
Zhe Fang
42284b4f45 add: CoverBackgroundRenderer 2025-12-11 15:20:51 -05:00
Zhe Fang
7da8af7c2a fix: lyrics window manager tab not showing 2025-12-11 08:24:51 -05:00
Zhe Fang
a4fc457065 chores: bump to v1.1.181.0 2025-12-11 07:44:20 -05:00
Zhe Fang
d3c2ee592c fix: selectorbar section disappear 2025-12-11 07:19:05 -05:00
Zhe Fang
2eef88523c Modify taskbar.json for layout and style changes
Updated taskbar settings including bounds, opacity, and effects.
2025-12-10 16:17:25 -05:00
Zhe Fang
220b1063ac fix: taskbar mode is not working in release mode 2025-12-10 16:15:58 -05:00
Zhe Fang
28bcd8ddfc chores: remove taskbar mode entry 2025-12-10 14:09:16 -05:00
Zhe Fang
5750bd3ad7 add: more info in thanks control 2025-12-10 11:54:03 -05:00
Zhe Fang
1667c701b0 feat: taskbar mode 2025-12-10 11:09:53 -05:00
Zhe Fang
160398f7ab fix: add null check for Window.AppWindow 2025-12-10 10:51:45 -05:00
Zhe Fang
c25ddf770f fix: when open app with window locked, then unlock the window, title bar and bottom bar will not be interactive 2025-12-10 10:42:06 -05:00
Zhe Fang
44fa3312b2 fix: docked mode ux exp 2025-12-10 07:09:08 -05:00
Zhe Fang
81f3d1f6bf fix: cannot exit app when docked mode is on 2025-12-10 06:54:21 -05:00
Zhe Fang
2f627d7531 feat: add credit list in settings 2025-12-09 19:09:39 -05:00
Zhe Fang
caed76e9b9 fix: chinese to tc or sc 2025-12-09 13:16:00 -05:00
Zhe Fang
a74dc705c9 feat: search for file name in music gallery 2025-12-09 13:02:42 -05:00
Zhe Fang
21eb8fcf9c fix: add apply traditional or simplified chinese condition 2025-12-09 12:22:37 -05:00
Zhe Fang
4662d3d54d fix: auto show and hide lyrics window issue 2025-12-09 11:03:32 -05:00
Zhe Fang
b10f108f93 chores: hide lock button when in fullscreen mode 2025-12-09 10:33:03 -05:00
Zhe Fang
2defe620c7 fix: NowPlayingWIndow titlebar and lock/unlock button 2025-12-09 10:22:34 -05:00
Zhe Fang
315c10c83c fix: translation and roman 2025-12-09 08:23:11 -05:00
Zhe Fang
981bc3f933 fix: fullscreen maximize button error 2025-12-08 23:02:46 -05:00
Zhe Fang
88460899bd fix: listen on lyrics color change 2025-12-08 22:05:20 -05:00
Zhe Fang
8341642658 feat: lyrics opacity settings 2025-12-08 21:40:50 -05:00
Zhe Fang
26f4ff3a58 chores: set host lyrics matching threshold to 40% 2025-12-08 20:30:05 -05:00
Zhe Fang
b1b763c6fe fix: ui color is not updated for the first time when in docked mode or desktop mode 2025-12-08 20:27:37 -05:00
Zhe Fang
28323c39f6 chores: code cleanup 2025-12-08 19:57:49 -05:00
Zhe Fang
b099965715 fix: music gallery init 2025-12-08 19:57:26 -05:00
Zhe Fang
8f9fdc18bb fix: album art sizing issue 2025-12-08 19:37:28 -05:00
Zhe Fang
5a549bd5f5 fix: auto asearch is not using cache for some cases 2025-12-08 18:48:29 -05:00
Zhe Fang
48e94f275b fix: translation 2025-12-08 18:08:16 -05:00
Zhe Fang
691071d725 chores: code cleanup 2025-12-08 16:37:01 -05:00
Zhe Fang
591c1a6d00 fix: lock button status 2025-12-08 15:22:13 -05:00
Zhe Fang
4e6a2df2cb fix 2025-12-08 15:07:12 -05:00
Zhe Fang
8b196e45a4 fix 2025-12-07 22:41:37 -05:00
Zhe Fang
0ddeaef126 fix: auto play only be valid when first opening gallery window 2025-12-07 17:26:41 -05:00
Zhe Fang
e3747c113a feat: multi window 2025-12-07 17:06:50 -05:00
Zhe Fang
1b1449ce3b fix: add scroll bar for playback sources settings 2025-12-05 11:59:55 -05:00
Zhe Fang
ea246a96be fix: album art only paddings 2025-12-05 11:43:39 -05:00
Zhe Fang
28722d325a feat: multi lyrics window 2025-12-04 22:07:02 -05:00
Zhe Fang
7238713ff5 chores: bump to 1.1.167.0 2025-12-04 12:53:45 -05:00
Zhe Fang
66c42f81f2 fix: song timeline offset, lyrics scroll overlay offset 2025-12-04 12:34:46 -05:00
Zhe Fang
3e6ba725d2 Update README.CN.md 2025-12-03 18:45:19 -05:00
Zhe Fang
ffb0c58a58 Update README.md 2025-12-03 18:41:37 -05:00
Zhe Fang
3b61f568d0 Update README.md 2025-12-03 18:31:26 -05:00
Zhe Fang
b925a10d69 Update README.CN.md 2025-12-03 18:31:11 -05:00
Zhe Fang
720f4b311e Update README.md 2025-12-03 18:30:20 -05:00
Zhe Fang
6c03002051 Delete LICENSE.txt 2025-12-03 18:28:44 -05:00
Zhe Fang
9cebd56bd5 Change LICENSE 2025-12-03 18:27:59 -05:00
Zhe Fang
f0a4c1251d chores: bump to 1.1.166.0 2025-12-03 16:38:22 -05:00
Zhe Fang
a8418d4234 feat: album art switch animation 2025-12-03 16:31:21 -05:00
Zhe Fang
53abc4526c fix: album art shadow 2025-12-03 14:31:57 -05:00
Zhe Fang
4d3b982904 feat: add settings for changing playing line top offset; fix: scroll bar visibility for lyrics placeholder 2025-12-03 11:44:50 -05:00
Zhe Fang
5faace562d feat: enable/disable blur effect; auto/manually resize album art height 2025-12-03 09:41:59 -05:00
Zhe Fang
290b7f38b4 fix: songinfo display issue 2025-12-02 21:33:39 -05:00
Zhe Fang
6f02a1a46c fix: font selector 2025-12-02 19:19:15 -05:00
Zhe Fang
4f74e48cfb chores: bump version code to 1.1.164.0 2025-12-02 17:33:23 -05:00
Zhe Fang
06f08558cc fix: i18n 2025-12-02 17:24:57 -05:00
Zhe Fang
a2b21ed3d5 chores: improve font family selection ux 2025-12-02 16:59:36 -05:00
Zhe Fang
4cb1ca0bb3 chores: improve layout 2025-12-02 15:10:18 -05:00
Zhe Fang
81ed341e47 fix: phonetic opacity 2025-12-02 14:04:53 -05:00
Zhe Fang
a9f92f4cb7 fix: local file lyrics read algo;
feat: support lyrics scrolling and clicking
2025-12-02 13:47:21 -05:00
Zhe Fang
62719ed513 chores: bump version code to 1.1.161.0 2025-12-01 15:18:21 -05:00
Zhe Fang
c9bd7725d0 fix: lyrics style not refreshed when switching lyrics window status 2025-12-01 14:54:32 -05:00
290 changed files with 36890 additions and 10583 deletions

291
.devin/wiki.json Normal file
View File

@@ -0,0 +1,291 @@
{
"repo_notes": [
{
"content": "Always use the latest files in the repo to generate the wiki"
}
],
"pages": [
{
"title": "Overview",
"purpose": "Introduce BetterLyrics, its purpose as a WinUI3 lyrics display application, key features, and supported music players",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Getting Started",
"purpose": "Guide users through installation, initial setup, and basic usage of BetterLyrics",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Installation and Deployment",
"purpose": "Explain how to install BetterLyrics from Microsoft Store or build from source, system requirements, and supported Windows versions",
"parent": "Getting Started",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Initial Configuration",
"purpose": "Walk through first-time setup including media player configuration, folder selection, and basic settings",
"parent": "Getting Started",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Architecture",
"purpose": "Provide technical overview of BetterLyrics' internal architecture, design patterns, and component organization",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Application Entry Point and Dependency Injection",
"purpose": "Document the App.xaml.cs entry point, service registration, and dependency injection container configuration",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Service Layer Architecture",
"purpose": "Explain the service-oriented architecture, service interfaces, and their implementations including MediaSessionsService, LyricsSearchService, and SettingsService",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Data Models",
"purpose": "Document core data models including LyricsLine, LyricsData, SongInfo, and configuration models",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Multi-Window System",
"purpose": "Explain how BetterLyrics manages multiple simultaneous lyrics windows, window lifecycle, and state management",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "User Interface",
"purpose": "Document the UI components, windows, and user interaction patterns in BetterLyrics",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Now Playing Window",
"purpose": "Detail the main lyrics display window, its components, and integration with the rendering system",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Display Modes and Window Configurations",
"purpose": "Explain different display modes (Standard, Desktop, Docked, Fullscreen, Narrow, Taskbar) and how to configure them",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Settings and Customization",
"purpose": "Document the settings interface, configuration options, and how to customize lyrics appearance and behavior",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Music Gallery",
"purpose": "Explain the local music library management feature, including playback, playlist management, and integration with media controls",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "System Tray and Global Controls",
"purpose": "Document the system tray integration, global hotkeys, and application-wide controls",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Lyrics System",
"purpose": "Comprehensive documentation of the lyrics acquisition, processing, and display pipeline",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Lyrics Search and Providers",
"purpose": "Document the lyrics search system, supported providers (QQ Music, Netease, Kugou, LrcLib, Apple Music, local files), and search strategies",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Parsing and Translation",
"purpose": "Explain how lyrics are parsed from different formats (LRC, QRC, TTML), translation system using LibreTranslate, and metadata matching",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Rendering Pipeline",
"purpose": "Document the Win2D-based rendering system, LyricsCanvas, PlayingLineRenderer, UnplayingLineRenderer, and LyricsLayoutManager",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Visual Effects and Animation",
"purpose": "Explain character-level effects (glow, float, scale), background effects (fluid, snow, fog, spectrum), and the animation transition system",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Media Integration",
"purpose": "Document how BetterLyrics integrates with music players and manages media sessions",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Windows Media Transport Controls Integration",
"purpose": "Explain how BetterLyrics uses Windows SMTC to work universally with media players",
"parent": "Media Integration",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Supported Players and Special Configurations",
"purpose": "List supported media players, document special configurations (Apple Music token, LX Music SSE), and player-specific handling",
"parent": "Media Integration",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Album Art and Theme Colors",
"purpose": "Document album art retrieval from multiple sources, color palette generation (MedianCut, OctTree), and adaptive theming",
"parent": "Media Integration",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Internationalization",
"purpose": "Explain the localization system, supported languages, and how resources are managed",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Development",
"purpose": "Technical documentation for developers contributing to or extending BetterLyrics",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Build Configuration and Deployment",
"purpose": "Document the build process, publish profiles for different architectures, CI/CD pipeline, and packaging for Microsoft Store",
"parent": "Development",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Helper Utilities and Extensions",
"purpose": "Document utility classes including TaskbarHook, WindowHook, ColorHelper, ImageHelper, and various extension methods",
"parent": "Development",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "External Dependencies and Libraries",
"purpose": "List and explain third-party dependencies including Win2D, NAudio, ATL.NET, FlaUI, and their usage in the application",
"parent": "Development",
"page_notes": [
{
"content": ""
}
]
}
]
}

View File

@@ -53,27 +53,27 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en</DefaultLanguage>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en</DefaultLanguage>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en</DefaultLanguage>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en</DefaultLanguage>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en</DefaultLanguage>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en</DefaultLanguage>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AppxManifest Include="Package.appxmanifest"> <AppxManifest Include="Package.appxmanifest">

View File

@@ -12,7 +12,7 @@
<Identity <Identity
Name="37412.BetterLyrics" Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9" Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.1.159.0" /> Version="1.2.244.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/> <mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
@@ -28,11 +28,22 @@
</Dependencies> </Dependencies>
<Resources> <Resources>
<Resource Language="en-US"/> <Resource Language="ar"/>
<Resource Language="zh-CN"/> <Resource Language="de"/>
<Resource Language="zh-TW"/> <Resource Language="en"/>
<Resource Language="ja-JP"/> <Resource Language="es"/>
<Resource Language="ko-KR"/> <Resource Language="fr"/>
<Resource Language="hi"/>
<Resource Language="id"/>
<Resource Language="ja"/>
<Resource Language="ko"/>
<Resource Language="ms"/>
<Resource Language="pt"/>
<Resource Language="ru"/>
<Resource Language="th"/>
<Resource Language="vi"/>
<Resource Language="zh-Hans"/>
<Resource Language="zh-Hant"/>
</Resources> </Resources>
<Applications> <Applications>

View File

@@ -3,8 +3,6 @@
x:Class="BetterLyrics.WinUI3.App" x:Class="BetterLyrics.WinUI3.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="using:BetterLyrics.WinUI3.Converter"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:globalization="using:Windows.Globalization" xmlns:globalization="using:Windows.Globalization"
xmlns:local="using:BetterLyrics.WinUI3" xmlns:local="using:BetterLyrics.WinUI3"
xmlns:media="using:CommunityToolkit.WinUI.Media"> xmlns:media="using:CommunityToolkit.WinUI.Media">
@@ -13,8 +11,14 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<!-- Merged dictionaries here --> <!-- Merged dictionaries here -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" /> <ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<ResourceDictionary Source="ms-appx:///DevWinUI.Controls/Themes/Generic.xaml" /> <ResourceDictionary Source="ms-appx:///DevWinUI.Controls/Themes/Generic.xaml" />
<ResourceDictionary Source="/Styles/Converters.xaml" />
<ResourceDictionary Source="/Styles/InteractiveListViewHeaderStyle.xaml" />
<ResourceDictionary Source="/Styles/GhostSliderStyle.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<!-- Theme --> <!-- Theme -->
@@ -42,44 +46,6 @@
<ExponentialEase x:Key="EaseOut" EasingMode="EaseOut" /> <ExponentialEase x:Key="EaseOut" EasingMode="EaseOut" />
<ExponentialEase x:Key="EaseIn" EasingMode="EaseIn" /> <ExponentialEase x:Key="EaseIn" EasingMode="EaseIn" />
<!-- Converter -->
<converter:EnumToIntConverter x:Key="EnumToIntConverter" />
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<converter:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
<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:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
<converter:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
<converter:BoolToPartialOpacityConverter x:Key="BoolToPartialOpacityConverter" />
<converter:BoolNegationToOpacityConverter x:Key="BoolNegationToOpacityConverter" />
<converter:RectToMarginConverter x:Key="RectToMarginConverter" />
<converter:LanguageCodeToDisplayedNameConverter x:Key="LanguageCodeToDisplayedNameConverter" />
<converter:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
<converter:DisplayLanguageCodeToIndexConverter x:Key="DisplayLanguageCodeToIndexConverter" />
<converter:PathToParentFolderConverter x:Key="PathToParentFolderConverter" />
<converter:TrackToLyricsConverter x:Key="TrackToLyricsConverter" />
<converter:IntToBoolConverter x:Key="IntToBoolConverter" />
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
<converter:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
<converter:PictureInfosToImageSourceConverter x:Key="PictureInfosToImageSourceConverter" />
<converter:LyricsFontWeightToFontWeightConverter x:Key="LyricsFontWeightToFontWeightConverter" />
<converter:TextAlignmentTypeToHorizontalAlignmentConverter x:Key="TextAlignmentTypeToHorizontalAlignmentConverter" />
<converter:LyricsLayoutOrientationToOrientationConverter x:Key="LyricsLayoutOrientationToOrientationConverter" />
<converter:LyricsLayoutOrientationNegationToOrientationConverter x:Key="LyricsLayoutOrientationNegationToOrientationConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
<x:Double x:Key="SettingsCardSpacing">4</x:Double> <x:Double x:Key="SettingsCardSpacing">4</x:Double>
<!-- Style --> <!-- Style -->
@@ -95,8 +61,7 @@
<Setter Property="VerticalAlignment" Value="Top" /> <Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,9,16,11" /> <Setter Property="Padding" Value="16,9,16,9" />
<Setter Property="Margin" Value="0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</Style> </Style>
<Style x:Key="GhostButtonStyle" TargetType="Button"> <Style x:Key="GhostButtonStyle" TargetType="Button">
@@ -108,15 +73,18 @@
</Style> </Style>
<Style <Style
x:Key="TitleBarToggleButtonStyle" x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}" BasedOn="{StaticResource DefaultToggleButtonStyle}"
TargetType="ToggleButton"> TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,9,16,11" /> <Setter Property="Padding" Value="14,6,14,9" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</Style> </Style>
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton"> <Style
x:Key="GhostToggleButtonStyle"
BasedOn="{StaticResource DefaultToggleButtonStyle}"
TargetType="ToggleButton">
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
@@ -130,190 +98,6 @@
<Setter Property="CornerRadius" Value="6" /> <Setter Property="CornerRadius" Value="6" />
</Style> </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 <Style
x:Key="ListViewStretchedItemContainerStyle" x:Key="ListViewStretchedItemContainerStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}" BasedOn="{StaticResource DefaultListViewItemStyle}"
@@ -357,10 +141,6 @@
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" /> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
</Style> </Style>
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
<!-- Dimensions --> <!-- Dimensions -->
<!-- Fonts --> <!-- Fonts -->

View File

@@ -1,47 +1,58 @@
// 2025/6/23 by Zhe Fang using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks; using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models.Db;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService; using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.DiscordService; using BetterLyrics.WinUI3.Services.DiscordService;
using BetterLyrics.WinUI3.Services.FileSystemService;
using BetterLyrics.WinUI3.Services.GSMTCService;
using BetterLyrics.WinUI3.Services.LastFMService; using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService; using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Services.LiveStatesService; using BetterLyrics.WinUI3.Services.LyricsCacheService;
using BetterLyrics.WinUI3.Services.LyricsSearchService; using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService; using BetterLyrics.WinUI3.Services.PlayHistoryService;
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.SettingsService; using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService; using BetterLyrics.WinUI3.Services.SMTCService;
using BetterLyrics.WinUI3.Services.SongSearchMapService;
using BetterLyrics.WinUI3.Services.TranslationService;
using BetterLyrics.WinUI3.Services.TransliterationService;
using BetterLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views; using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources; using Microsoft.Windows.AppLifecycle; // 关键App生命周期管理
using Serilog; using Serilog;
using System; using System;
using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3 namespace BetterLyrics.WinUI3
{ {
public partial class App : Application public partial class App : Application
{ {
private Window? m_window;
private readonly ILogger<App> _logger; private readonly ILogger<App> _logger;
public static new App Current => (App)Application.Current; public static new App Current => (App)Application.Current;
private static Mutex? _instanceMutex; private readonly string _appKey = Windows.ApplicationModel.Package.Current.Id.FamilyName;
public App() public App()
{ {
this.InitializeComponent(); // Must be done before InitializeComponent
if (!TryHandleSingleInstance())
{
// 如果移交成功直接退出当前进程
Environment.Exit(0);
return;
}
EnsureSingleInstance(); this.InitializeComponent();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
PathHelper.EnsureDirectories(); PathHelper.EnsureDirectories();
@@ -49,58 +60,197 @@ namespace BetterLyrics.WinUI3
_logger = Ioc.Default.GetRequiredService<ILogger<App>>(); _logger = Ioc.Default.GetRequiredService<ILogger<App>>();
// 注册全局异常捕获
UnhandledException += App_UnhandledException; UnhandledException += App_UnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException; AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
} }
private void EnsureSingleInstance() /// <summary>
/// 处理单实例逻辑。
/// 返回 true 表示我是主实例,继续运行。
/// 返回 false 表示我是第二个实例,已通知主实例,我应该退出。
/// </summary>
private bool TryHandleSingleInstance()
{ {
_instanceMutex = new Mutex(true, Constants.App.AppName, out bool createdNew); // 尝试查找或注册当前实例
var mainInstance = AppInstance.FindOrRegisterForKey(_appKey);
if (!createdNew) // 如果当前实例就是注册的那个主实例
if (mainInstance.IsCurrent)
{ {
User32.MessageBox(HWND.NULL, new ResourceLoader().GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL); // 监听 "Activated" 事件。
Environment.Exit(0); // 当第二个实例启动并重定向过来时,这个事件会被触发。
mainInstance.Activated += OnMainInstanceActivated;
return true;
}
else
{
// 我不是主实例,我是后来者。
// 获取当前实例的激活参数(比如是通过文件双击打开的,这里能拿到文件路径)
var args = AppInstance.GetCurrent().GetActivatedEventArgs();
// 将激活请求重定向给主实例
// 注意:这里是同步等待,确保发送成功后再退出
try
{
mainInstance.RedirectActivationToAsync(args).AsTask().Wait();
}
catch (Exception)
{
// 即使重定向失败,作为第二个实例也应该退出
}
return false;
} }
} }
protected override void OnLaunched(LaunchActivatedEventArgs args) /// <summary>
/// 当第二个实例试图启动时,主实例会收到此回调
/// </summary>
private void OnMainInstanceActivated(object? sender, AppActivationArguments e)
{ {
WindowHook.OpenOrShowWindow<LyricsWindow>(); // 这个事件是在后台线程触发的,必须切回 UI 线程操作窗口
if (Ioc.Default.GetRequiredService<ISettingsService>().AppSettings.MusicGallerySettings.AutoOpen) m_window?.DispatcherQueue.TryEnqueue(() =>
{
HandleActivation();
});
}
/// <summary>
/// 唤醒逻辑
/// </summary>
private void HandleActivation()
{
WindowHook.OpenOrShowWindow<LyricsWindowSwitchWindow>();
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
await InitDatabasesAsync();
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
// Migrate MappedSongSearchQueries
var songSearchMapService = Ioc.Default.GetRequiredService<ISongSearchMapService>();
var obsoleteSongSearchMap = settingsService.AppSettings.MappedSongSearchQueries;
if (obsoleteSongSearchMap.Count > 0)
{
foreach (var item in obsoleteSongSearchMap)
{
await songSearchMapService.SaveMappingAsync(item);
}
obsoleteSongSearchMap.Clear();
}
// Start scan tasks in background
var fileSystemService = Ioc.Default.GetRequiredService<IFileSystemService>();
foreach (var item in settingsService.AppSettings.LocalMediaFolders)
{
if (item.LastSyncTime == null)
{
_ = Task.Run(async () => await fileSystemService.ScanMediaFolderAsync(item, CancellationToken.None));
}
}
fileSystemService.StartAllFolderTimers();
// Init system tray
m_window = WindowHook.OpenOrShowWindow<SystemTrayWindow>();
// Open lyrics window if set
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
{
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
if (defaultStatus != null)
{
foreach (var item in defaultStatus)
{
WindowHook.OpenOrShowWindow<NowPlayingWindow>(item);
if (!settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
{
break;
}
}
}
}
// Open music gallery if set
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
{ {
WindowHook.OpenOrShowWindow<MusicGalleryWindow>(); WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
} }
} }
private async Task InitDatabasesAsync()
{
// Init databases
var playHistoryFactory = Ioc.Default.GetRequiredService<IDbContextFactory<PlayHistoryDbContext>>();
var songSearchMapFactory = Ioc.Default.GetRequiredService<IDbContextFactory<SongSearchMapDbContext>>();
var filesIndexFactory = Ioc.Default.GetRequiredService<IDbContextFactory<FilesIndexDbContext>>();
var lyricsCacheFactory = Ioc.Default.GetRequiredService<IDbContextFactory<LyricsCacheDbContext>>();
using (var playHistoryDb = await playHistoryFactory.CreateDbContextAsync())
{
await playHistoryDb.Database.EnsureCreatedAsync();
}
using (var songSearchMapDb = await songSearchMapFactory.CreateDbContextAsync())
{
await songSearchMapDb.Database.EnsureCreatedAsync();
}
using (var filesIndexDb = await filesIndexFactory.CreateDbContextAsync())
{
await filesIndexDb.Database.EnsureCreatedAsync();
}
using (var lyricsCacheDb = await lyricsCacheFactory.CreateDbContextAsync())
{
await lyricsCacheDb.Database.EnsureCreatedAsync();
}
}
private static void ConfigureServices() private static void ConfigureServices()
{ {
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose) .MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Error)
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day) .WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
.CreateLogger(); .CreateLogger();
// Register services
Ioc.Default.ConfigureServices( Ioc.Default.ConfigureServices(
new ServiceCollection() new ServiceCollection()
// 数据库工厂
.AddDbContextFactory<PlayHistoryDbContext>(options => options.UseSqlite($"Data Source={PathHelper.PlayHistoryPath}"))
.AddDbContextFactory<FilesIndexDbContext>(options => options.UseSqlite($"Data Source={PathHelper.FilesIndexPath}"))
.AddDbContextFactory<LyricsCacheDbContext>(options => options.UseSqlite($"Data Source={PathHelper.LyricsCachePath}"))
.AddDbContextFactory<SongSearchMapDbContext>(options => options.UseSqlite($"Data Source={PathHelper.SongSearchMapPath}"))
// 日志
.AddLogging(loggingBuilder => .AddLogging(loggingBuilder =>
{ {
loggingBuilder.ClearProviders(); loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog(); loggingBuilder.AddSerilog();
}) })
// Services // Services
.AddSingleton<ILiveStatesService, LiveStatesService>()
.AddSingleton<ISettingsService, SettingsService>() .AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>() .AddSingleton<ISMTCService, SMTCService>()
.AddSingleton<IGSMTCService, GSMTCService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>() .AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>() .AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>() .AddSingleton<ITranslationService, TranslationService>()
.AddSingleton<ITranslateService, TranslateService>() .AddSingleton<ITransliterationService, TransliterationService>()
.AddSingleton<ILastFMService, LastFMService>() .AddSingleton<ILastFMService, LastFMService>()
.AddSingleton<IResourceService, ResourceService>()
.AddSingleton<IDiscordService, DiscordService>() .AddSingleton<IDiscordService, DiscordService>()
.AddSingleton<ILocalizationService, LocalizationService>()
.AddSingleton<IFileSystemService, FileSystemService>()
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
.AddSingleton<ILyricsCacheService, LyricsCacheService>()
.AddSingleton<ISongSearchMapService, SongSearchMapService>()
// ViewModels // ViewModels
.AddSingleton<AppSettingsControlViewModel>() .AddSingleton<AppSettingsControlViewModel>()
.AddSingleton<PlaybackSettingsControlViewModel>() .AddSingleton<PlaybackSettingsControlViewModel>()
@@ -109,13 +259,19 @@ namespace BetterLyrics.WinUI3
.AddSingleton<LyricsWindowSettingsControlViewModel>() .AddSingleton<LyricsWindowSettingsControlViewModel>()
.AddSingleton<LyricsWindowSwitchControlViewModel>() .AddSingleton<LyricsWindowSwitchControlViewModel>()
.AddSingleton<LyricsWindowSwitchWindowViewModel>() .AddSingleton<LyricsWindowSwitchWindowViewModel>()
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>() .AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>() .AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsPageViewModel>() .AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>() .AddSingleton<MusicGalleryPageViewModel>()
.AddSingleton<MusicGalleryViewModel>()
.AddSingleton<AboutControlViewModel>() .AddSingleton<AboutControlViewModel>()
.AddSingleton<MusicGalleryWindowViewModel>()
.AddSingleton<StatsDashboardControlViewModel>()
.AddSingleton<PlayQueueViewModel>()
.AddTransient<NowPlayingWindowViewModel>()
.AddTransient<NowPlayingPageViewModel>()
.AddTransient<NowPlayingBarViewModel>()
.BuildServiceProvider() .BuildServiceProvider()
); );
} }
@@ -128,7 +284,8 @@ namespace BetterLyrics.WinUI3
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e) private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
{ {
_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException"); // FirstChance 异常非常多(比如内部 try-catch 也会触发),通常建议只在 Debug 模式记录,或者过滤特定类型
// _logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
} }
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
@@ -141,4 +298,4 @@ namespace BetterLyrics.WinUI3
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException"); _logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

View File

@@ -10,39 +10,55 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>preview</LangVersion> <LangVersion>preview</LangVersion>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AppxDefaultResourceQualifiers>Language=ar;de;en;es;fr;hi;id;ja;ko;ms;pt;ru;th;vi;zh-Hans;zh-Hant;</AppxDefaultResourceQualifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="TemplateSelector\**" />
<Compile Remove="ViewModels\Lyrics\**" /> <Compile Remove="ViewModels\Lyrics\**" />
<Content Remove="TemplateSelector\**" />
<Content Remove="ViewModels\Lyrics\**" /> <Content Remove="ViewModels\Lyrics\**" />
<EmbeddedResource Remove="TemplateSelector\**" />
<EmbeddedResource Remove="ViewModels\Lyrics\**" /> <EmbeddedResource Remove="ViewModels\Lyrics\**" />
<None Remove="TemplateSelector\**" />
<None Remove="ViewModels\Lyrics\**" /> <None Remove="ViewModels\Lyrics\**" />
<Page Remove="TemplateSelector\**" />
<Page Remove="ViewModels\Lyrics\**" /> <Page Remove="ViewModels\Lyrics\**" />
<PRIResource Remove="TemplateSelector\**" />
<PRIResource Remove="ViewModels\Lyrics\**" /> <PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Assets\Segoe Fluent Icons.ttf" /> <None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Assets\Wiki82.profile.xml" /> <None Remove="Assets\Wiki82.profile.xml" />
<None Remove="Controls\AboutControl.xaml" /> <None Remove="Controls\AboutControl.xaml" />
<None Remove="Controls\AlbumArtLayoutSettingsControl.xaml" /> <None Remove="Controls\AlbumArtAreaEffectSettingsControl.xaml" />
<None Remove="Controls\AppSettingsControl.xaml" /> <None Remove="Controls\AppSettingsControl.xaml" />
<None Remove="Controls\DemoWindowGrid.xaml" /> <None Remove="Controls\DemoWindowGrid.xaml" />
<None Remove="Controls\Dragger.xaml" />
<None Remove="Controls\ExtendedSlider.xaml" /> <None Remove="Controls\ExtendedSlider.xaml" />
<None Remove="Controls\FontFamilyAutoSuggestBox.xaml" /> <None Remove="Controls\FontFamilyAutoSuggestBox.xaml" />
<None Remove="Controls\ImageSwitcher.xaml" />
<None Remove="Controls\LyricsSearchControl.xaml" /> <None Remove="Controls\LyricsSearchControl.xaml" />
<None Remove="Controls\LyricsStyleSettingsControl.xaml" /> <None Remove="Controls\LyricsStyleSettingsControl.xaml" />
<None Remove="Controls\LyricsWindowSettingsControl.xaml" /> <None Remove="Controls\LyricsWindowSettingsControl.xaml" />
<None Remove="Controls\LyricsWindowSwitchControl.xaml" /> <None Remove="Controls\LyricsWindowSwitchControl.xaml" />
<None Remove="Controls\MediaSettingsControl.xaml" /> <None Remove="Controls\MediaSettingsControl.xaml" />
<None Remove="Controls\NowPlayingBar.xaml" />
<None Remove="Controls\PatronControl.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" /> <None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\PlayQueue.xaml" />
<None Remove="Controls\PropertyRow.xaml" /> <None Remove="Controls\PropertyRow.xaml" />
<None Remove="Controls\RemoteServerConfigControl.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" /> <None Remove="Controls\ShortcutTextBox.xaml" />
<None Remove="Controls\StatsDashboardControl.xaml" />
<None Remove="Controls\SystemTray.xaml" /> <None Remove="Controls\SystemTray.xaml" />
<None Remove="Controls\WindowSettingsControl.xaml" />
<None Remove="Styles\GhostSliderStyle.xaml" />
<None Remove="Styles\InteractiveListViewHeaderStyle.xaml" />
<None Remove="Views\LyricsSearchWindow.xaml" /> <None Remove="Views\LyricsSearchWindow.xaml" />
<None Remove="Views\LyricsWindowSwitchWindow.xaml" /> <None Remove="Views\LyricsWindowSwitchWindow.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" /> <None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" /> <None Remove="Views\MusicGalleryWindow.xaml" />
<None Remove="Views\SettingsWindow.xaml" /> <None Remove="Views\SettingsWindow.xaml" />
<None Remove="Views\SystemTrayWindow.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Logo.ico" /> <Content Include="Logo.ico" />
@@ -55,37 +71,43 @@
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" Version="0.1.251021-build.2365" /> <PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" Version="0.1.251021-build.2365" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" 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.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.251219" />
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" /> <PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
<PackageReference Include="csharp-kana" Version="1.0.2" />
<PackageReference Include="csharp-pinyin" Version="1.0.1" /> <PackageReference Include="csharp-pinyin" Version="1.0.1" />
<PackageReference Include="DevWinUI.Controls" Version="9.6.0" /> <PackageReference Include="DevWinUI.Controls" Version="9.8.1" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" /> <PackageReference Include="Dubya.WindowsMediaController" Version="2.5.6" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.0" /> <PackageReference Include="F23.StringSimilarity" Version="7.0.1" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" /> <PackageReference Include="FlaUI.UIA3" Version="5.0.0" />
<PackageReference Include="FluentFTP" Version="53.0.2" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.4.1" />
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" /> <PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WinUI" Version="2.0.0-rc6.1" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" /> <PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" /> <PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" /> <PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" /> <PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0" /> <PackageReference Include="SMBLibrary" Version="1.5.5.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.0" /> <PackageReference Include="System.Drawing.Common" Version="10.0.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
<PackageReference Include="TagLibSharp" Version="2.3.0" /> <PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" /> <PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.2.1" /> <PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.2.1" />
@@ -94,6 +116,7 @@
<PackageReference Include="Vanara.PInvoke.User32" Version="4.2.1" /> <PackageReference Include="Vanara.PInvoke.User32" Version="4.2.1" />
<PackageReference Include="Vanara.Windows.Shell" Version="4.2.1" /> <PackageReference Include="Vanara.Windows.Shell" Version="4.2.1" />
<PackageReference Include="VCollab.DiscordRichPresence" Version="1.7.0" /> <PackageReference Include="VCollab.DiscordRichPresence" Version="1.7.0" />
<PackageReference Include="WebDav.Client" Version="2.9.0" />
<PackageReference Include="WinUIEx" Version="2.9.0" /> <PackageReference Include="WinUIEx" Version="2.9.0" />
<PackageReference Include="z440.atl.core" Version="7.9.0" /> <PackageReference Include="z440.atl.core" Version="7.9.0" />
</ItemGroup> </ItemGroup>
@@ -113,6 +136,12 @@
</ItemGroup> </ItemGroup>
<!--Disable Trimming for Specific Packages--> <!--Disable Trimming for Specific Packages-->
<ItemGroup> <ItemGroup>
<TrimmerRootAssembly Include="FlaUI.UIA3" />
<TrimmerRootAssembly Include="Interop.UIAutomationClient" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Abstractions" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Relational" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Sqlite" />
<TrimmerRootAssembly Include="NAudio.Wasapi" /> <TrimmerRootAssembly Include="NAudio.Wasapi" />
<TrimmerRootAssembly Include="TagLibSharp" /> <TrimmerRootAssembly Include="TagLibSharp" />
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" /> <TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
@@ -155,6 +184,9 @@
<Content Update="Assets\EmptyState.png"> <Content Update="Assets\EmptyState.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Update="Assets\Folder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\foobar2000.png"> <Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
@@ -197,6 +229,9 @@
<Content Update="Assets\NetEaseCloudMusic.png"> <Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Update="Assets\OriginalSoundHQPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Page.png"> <Content Update="Assets\Page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
@@ -212,6 +247,9 @@
<Content Update="Assets\Question.png"> <Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Update="Assets\RevolvingHearts.gif">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\SaltPlayerForWindows.png"> <Content Update="Assets\SaltPlayerForWindows.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
@@ -228,6 +266,31 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Update="Controls\PatronControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Styles\GhostSliderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Styles\Converters.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PlayQueue.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\StatsDashboardControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="Controls\FontFamilyAutoSuggestBox.xaml"> <Page Update="Controls\FontFamilyAutoSuggestBox.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@@ -294,7 +357,7 @@
</Page> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="Controls\AlbumArtLayoutSettingsControl.xaml"> <Page Update="Controls\AlbumArtAreaStyleSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
</ItemGroup> </ItemGroup>
@@ -329,15 +392,37 @@
</Page> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PRIResource Update="Strings\en-US\Resources.resw"> <Page Update="Controls\RemoteServerConfigControl.xaml">
<Generator></Generator> <Generator>MSBuild:Compile</Generator>
</PRIResource> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="TemplateSelector\" /> <Page Update="Controls\NowPlayingBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="Controls\Dragger.xaml"> <Page Update="Views\SystemTrayWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\WindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AlbumArtAreaEffectSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ImageSwitcher.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ShadowImage.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
</ItemGroup> </ItemGroup>
@@ -351,6 +436,11 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Update="Styles\InteractiveListViewHeaderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!-- Publish Properties --> <!-- Publish Properties -->
<PropertyGroup> <PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun> <PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>

View File

@@ -3,6 +3,8 @@
public static class App public static class App
{ {
public const string AppAuthor = "Zhe Fang"; public const string AppAuthor = "Zhe Fang";
public const string AppAuthorNicknameEN = "jayfunc";
public const string AppAuthorNicknameZH = "摘叶飞镖";
public const string AppName = "BetterLyrics"; public const string AppName = "BetterLyrics";
public const string AutoStartupTaskId = "AutoStartup"; public const string AutoStartupTaskId = "AutoStartup";

View File

@@ -4,15 +4,25 @@
{ {
public const string MicrosoftStore = "https://apps.microsoft.com/detail/9p1wcd1p597r"; public const string MicrosoftStore = "https://apps.microsoft.com/detail/9p1wcd1p597r";
public const string GitHub = "https://github.com/jayfunc/BetterLyrics"; public const string AuthorGitHub = "https://github.com/jayfunc";
public const string ShareHub = $"{GitHub}/blob/dev/ShareHub/index.md";
public const string TermsOfService = $"{GitHub}/blob/dev/TermsofService.md"; public const string Crowdin = "https://crowdin.com/project/betterlyrics/invite?h=413bb0df7afa420247a98fefdae5e12c2647410";
public const string PrivacyPolicy = $"{GitHub}/blob/dev/PrivacyPolicy.md";
public const string UserGuide = $"{GitHub}/wiki/User-Guide"; public const string BetterLyricsGitHub = $"{AuthorGitHub}/BetterLyrics";
public const string ShareHub = $"{BetterLyricsGitHub}/blob/dev/ShareHub/index.md";
public const string TermsOfService = $"{BetterLyricsGitHub}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{BetterLyricsGitHub}/blob/dev/PrivacyPolicy.md";
public const string UserGuide = $"{BetterLyricsGitHub}/wiki/User-Guide";
public const string AppleMusicCfg = $"{UserGuide}#lyrics-source-configuration"; public const string AppleMusicCfg = $"{UserGuide}#lyrics-source-configuration";
public const string QQGroup = "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 QQGroup = "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 Discord = "https://discord.gg/5yAQPnyCKv"; public const string Discord = "https://discord.gg/5yAQPnyCKv";
public const string Telegram = "https://t.me/+svhSLZ7awPsxNGY1"; public const string Telegram = "https://t.me/+svhSLZ7awPsxNGY1";
public const string BuyMeACoffee = "https://buymeacoffee.com/founchoo";
public const string PayPal = "https://paypal.me/zhefangpay";
public const string Afdian = "https://afdian.com/a/jayfunc";
} }
} }

View File

@@ -1,6 +1,6 @@
namespace BetterLyrics.WinUI3.Constants namespace BetterLyrics.WinUI3.Constants
{ {
public static class PlayerID public static class PlayerId
{ {
public const string LXMusic = "cn.toside.music.desktop"; public const string LXMusic = "cn.toside.music.desktop";
public const string LXMusicPortable = "lx-music-desktop.exe"; public const string LXMusicPortable = "lx-music-desktop.exe";
@@ -20,9 +20,11 @@
public const string Edge = "MSEdge"; public const string Edge = "MSEdge";
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App"; public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App"; public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
public const string SaltPlayerForWindows = "Sakawish.SaltPlayerforWindows_q65q631pyh094!SaltPlayerforWindows"; public const string SaltPlayerForWindowsMS = "Sakawish.SaltPlayerforWindows_q65q631pyh094!SaltPlayerforWindows";
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows.exe";
public const string MoeKoeMusic = "cn.MoeKoe.Music"; public const string MoeKoeMusic = "cn.MoeKoe.Music";
public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music"; public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music";
public const string Listen1 = "com.listen1.listen1"; public const string Listen1 = "com.listen1.listen1";
public const string OriginalSoundHQPlayer = "SennpaiStudio.528762A6196EF_z79ft30j24epr!App";
} }
} }

View File

@@ -20,8 +20,10 @@
public const string Edge = "Microsoft Edge"; public const string Edge = "Microsoft Edge";
public const string BetterLyrics = "BetterLyrics"; public const string BetterLyrics = "BetterLyrics";
public const string BetterLyricsDebug = "BetterLyrics (Debug)"; public const string BetterLyricsDebug = "BetterLyrics (Debug)";
public const string SaltPlayerForWindows = "Salt Player for Windows"; public const string SaltPlayerForWindowsMS = "Salt Player for Windows (Microsoft Store)";
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows (Steam)";
public const string MoeKoeMusic = "MoeKoe Music"; public const string MoeKoeMusic = "MoeKoe Music";
public const string Listen1 = "Listen 1"; public const string Listen1 = "Listen 1";
public const string OriginalSoundHQPlayer = "Original Sound HQ Player";
} }
} }

View File

@@ -6,5 +6,6 @@ namespace BetterLyrics.WinUI3.Constants
{ {
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250); public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250);
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350); public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350);
public static readonly TimeSpan WaitingDuration = TimeSpan.FromMilliseconds(300);
} }
} }

View File

@@ -28,34 +28,41 @@
</StackPanel> </StackPanel>
</dev:SettingsExpander.Header> </dev:SettingsExpander.Header>
<dev:SettingsExpander.Description> <dev:SettingsExpander.Description>
<StackPanel <StackPanel Orientation="Horizontal">
Margin="0,2,0,0" <StackPanel
Orientation="Horizontal" Margin="0,2,0,0"
Spacing="2"> Orientation="Horizontal"
<TextBlock Text="©" /> Spacing="2">
<HyperlinkButton <TextBlock Text="©" />
Margin="0,-1,0,0" <HyperlinkButton
Content="Zhe Fang" Margin="0,-1,0,0"
NavigateUri="https://github.com/jayfunc" /> Content="{x:Bind const:App.AppAuthor}"
<TextBlock Text="2025" /> NavigateUri="https://github.com/jayfunc" />
<TextBlock Text="2025" />
</StackPanel>
</StackPanel> </StackPanel>
</dev:SettingsExpander.Description> </dev:SettingsExpander.Description>
<RichTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}"> <StackPanel Orientation="Horizontal" Spacing="6">
<Paragraph> <RichTextBlock
<Run x:Uid="SettingsPageVersion" /> Margin="0,-1,0,0"
<Run Text="{x:Bind helper:MetadataHelper.AppVersion}" /> VerticalAlignment="Center"
</Paragraph> Foreground="{ThemeResource TextFillColorSecondaryBrush}">
</RichTextBlock> <Paragraph>
<Run x:Uid="SettingsPageVersion" />
<Run Text="{x:Bind helper:MetadataHelper.AppVersion}" />
</Paragraph>
</RichTextBlock>
</StackPanel>
<dev:SettingsExpander.Items> <dev:SettingsExpander.Items>
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left"> <dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal"> <dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHub}" /> <HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.BetterLyricsGitHub}" />
<HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" /> <HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" />
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" /> <HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" /> <HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" />
</StackPanel> </dev:WrapPanel>
</StackPanel> </StackPanel>
</dev:SettingsCard> </dev:SettingsCard>
@@ -63,26 +70,26 @@
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left"> <dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageFeedback" /> <TextBlock x:Uid="SetingsPageFeedback" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal"> <dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton x:Uid="SettingsPageQQGroup" NavigateUri="{x:Bind const:Link.QQGroup}" /> <HyperlinkButton Content="QQ 反馈交流群" NavigateUri="{x:Bind const:Link.QQGroup}" />
<HyperlinkButton x:Uid="SettingsPageDiscord" NavigateUri="{x:Bind const:Link.Discord}" /> <HyperlinkButton Content="Discord" NavigateUri="{x:Bind const:Link.Discord}" />
<HyperlinkButton x:Uid="SettingsPageTelegram" NavigateUri="{x:Bind const:Link.Telegram}" /> <HyperlinkButton Content="Telegram" NavigateUri="{x:Bind const:Link.Telegram}" />
</StackPanel> </dev:WrapPanel>
</StackPanel> </StackPanel>
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left"> <dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageDonation" /> <TextBlock x:Uid="SetingsPageDonation" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal"> <dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="Buy Me a Coffee" NavigateUri="https://buymeacoffee.com/founchoo" /> <HyperlinkButton Content="Buy Me a Coffee" NavigateUri="{x:Bind const:Link.BuyMeACoffee}" />
<HyperlinkButton Content="PayPal" NavigateUri="https://paypal.me/zhefangpay" /> <HyperlinkButton Content="PayPal" NavigateUri="{x:Bind const:Link.PayPal}" />
<Button <HyperlinkButton
Content="支付宝" x:Name="AlipayButton"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Click="AlipayButton_Click"
Style="{StaticResource GhostButtonStyle}"> Content="支付宝">
<Button.Flyout> <HyperlinkButton.ContextFlyout>
<Flyout> <Flyout x:Name="AlipayFlyout">
<Flyout.FlyoutPresenterStyle> <Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter"> <Style TargetType="FlyoutPresenter">
<Setter Property="CornerRadius" Value="12" /> <Setter Property="CornerRadius" Value="12" />
@@ -91,14 +98,14 @@
</Flyout.FlyoutPresenterStyle> </Flyout.FlyoutPresenterStyle>
<Image Height="300" Source="/Assets/Alipay.jpg" /> <Image Height="300" Source="/Assets/Alipay.jpg" />
</Flyout> </Flyout>
</Button.Flyout> </HyperlinkButton.ContextFlyout>
</Button> </HyperlinkButton>
<Button <HyperlinkButton
Content="微信" x:Name="WeChatButton"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Click="WeChat_Click"
Style="{StaticResource GhostButtonStyle}"> Content="微信">
<Button.Flyout> <HyperlinkButton.ContextFlyout>
<Flyout> <Flyout x:Name="WeChatFlyout">
<Flyout.FlyoutPresenterStyle> <Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter"> <Style TargetType="FlyoutPresenter">
<Setter Property="CornerRadius" Value="12" /> <Setter Property="CornerRadius" Value="12" />
@@ -107,28 +114,25 @@
</Flyout.FlyoutPresenterStyle> </Flyout.FlyoutPresenterStyle>
<Image Height="300" Source="/Assets/WeChatReward.png" /> <Image Height="300" Source="/Assets/WeChatReward.png" />
</Flyout> </Flyout>
</Button.Flyout> </HyperlinkButton.ContextFlyout>
</Button> </HyperlinkButton>
</StackPanel> <HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
<StackPanel Orientation="Horizontal" Spacing="6"> </dev:WrapPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="*" /> <Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="*" />
<TextBlock <TextBlock
x:Uid="SetingsPageThanks" x:Uid="SetingsPageThanks"
Grid.Column="1"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</StackPanel> </Grid>
</StackPanel>
</dev:SettingsCard>
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageContributors" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="jayfunc" NavigateUri="https://github.com/jayfunc" />
<HyperlinkButton Content="Raspberry-Monster" NavigateUri="https://github.com/Raspberry-Monster" />
<HyperlinkButton Content="ZHider" NavigateUri="https://github.com/ZHider" />
<HyperlinkButton Content="kusutori" NavigateUri="https://github.com/kusutori" />
</StackPanel>
</StackPanel> </StackPanel>
</dev:SettingsCard> </dev:SettingsCard>
@@ -144,6 +148,107 @@
</dev:SettingsExpander.ItemsFooter> </dev:SettingsExpander.ItemsFooter>
</dev:SettingsExpander> </dev:SettingsExpander>
<dev:SettingsExpander x:Uid="SettingsPageThanksList">
<dev:SettingsExpander.HeaderIcon>
<ImageIcon Source="ms-appx:///Assets/RevolvingHearts.gif" />
</dev:SettingsExpander.HeaderIcon>
<dev:SettingsExpander.Items>
<!-- 贡献者 -->
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<RichTextBlock>
<Paragraph>
<Run x:Uid="SetingsPageContributors" />
<Run Text="-" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Code" />
</Paragraph>
</RichTextBlock>
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="jayfunc" NavigateUri="https://github.com/jayfunc" />
<HyperlinkButton Content="Raspberry-Monster" NavigateUri="https://github.com/Raspberry-Monster" />
<HyperlinkButton Content="ZHider" NavigateUri="https://github.com/ZHider" />
<HyperlinkButton Content="kusutori" NavigateUri="https://github.com/kusutori" />
</dev:WrapPanel>
</StackPanel>
</dev:SettingsCard>
<!-- 贡献者 (Translator) -->
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<RichTextBlock>
<Paragraph>
<Run x:Uid="SetingsPageContributors" />
<Run Text="-" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Translator" />
</Paragraph>
</RichTextBlock>
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="borcolasky" NavigateUri="https://crowdin.com/profile/borcolasky" />
<HyperlinkButton Content="SuHeAndZl" NavigateUri="https://crowdin.com/profile/SuHeAndZl" />
</dev:WrapPanel>
</StackPanel>
</dev:SettingsCard>
<!-- 赞助 -->
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<TextBlock x:Uid="SettingsPagePatrons" />
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<uc:PatronControl Date="Jan 3, 2026" PatronName="**轩" />
<uc:PatronControl Date="Dec 13, 2025" PatronName="&lt;Anonymous&gt;" />
<uc:PatronControl Date="Dec 3, 2025" PatronName="YE" />
<uc:PatronControl Date="Dec 2, 2025" PatronName="&lt;Anonymous&gt;" />
<uc:PatronControl Date="Nov 23, 2025" PatronName="**玄" />
<uc:PatronControl Date="Nov 21, 2025" PatronName="**智" />
<uc:PatronControl Date="Nov 17, 2025" PatronName="SuHeAndZl" />
<uc:PatronControl Date="Nov 2, 2025" PatronName="借过" />
<uc:PatronControl Date="Aug 28, 2025" PatronName="**华" />
<TextBlock
x:Uid="SettingsPageUserWhoPurchased"
Margin="12,8"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</dev:WrapPanel>
</StackPanel>
</dev:SettingsCard>
<!-- 特别鸣谢 -->
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageSpecialThanks" />
<TextBlock
x:Uid="SettingsPageYouNowUsing"
Margin="0,8"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</dev:SettingsCard>
<!-- 代码参考 -->
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageDeps" />
<HyperlinkButton Margin="-12,0,0,0" NavigateUri="https://github.com/jayfunc/BetterLyrics/network/dependencies">
<TextBlock x:Uid="SetingsPageDeps" />
</HyperlinkButton>
</StackPanel>
</dev:SettingsCard>
<!-- UI/UX 参考 -->
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageUIUXRef" />
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="refined-now-playing-netease" NavigateUri="https://github.com/solstice23/refined-now-playing-netease" />
<HyperlinkButton Content="Lyricify" NavigateUri="https://github.com/WXRIW/Lyricify-App" />
<HyperlinkButton Content="椒盐音乐 Salt Player" NavigateUri="https://moriafly.com/program/salt-player" />
<HyperlinkButton Content="MyToolBar" NavigateUri="https://github.com/TwilightLemon/MyToolBar" />
</dev:WrapPanel>
</StackPanel>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageMockMusicPlaying"> <dev:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
<HyperlinkButton x:Uid="SettingsPagePlayingMockMusicButton" NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" /> <HyperlinkButton x:Uid="SettingsPagePlayingMockMusicButton" NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
</dev:SettingsCard> </dev:SettingsCard>
@@ -177,8 +282,10 @@
</dev:SettingsExpander.ItemsHeader> </dev:SettingsExpander.ItemsHeader>
</dev:SettingsExpander> </dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageDebugOverlay"> <dev:SettingsCard x:Uid="SettingsPageSettingsPlayHistory" Visibility="Collapsed">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDebugOverlayEnabled, Mode=TwoWay}" /> <StackPanel Orientation="Horizontal" Spacing="6">
<Button x:Uid="SettingsPageExportPlayHistoryButton" Command="{x:Bind ViewModel.ExportPlayHistoryCommand}" />
</StackPanel>
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed"> <dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
@@ -194,6 +301,25 @@
Value="{x:Bind ViewModel.AppSettings.AdvancedSettings.FPS, Mode=TwoWay}" /> Value="{x:Bind ViewModel.AppSettings.AdvancedSettings.FPS, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<RichTextBlock
Margin="0,16,0,0"
HorizontalAlignment="Center"
HorizontalTextAlignment="Center"
LineHeight="28">
<Paragraph FontWeight="Bold">
<Run Text="{x:Bind const:App.AppName}" />
</Paragraph>
<Paragraph>
<Run Text="An elegant and deeply customizable lyrics visualizer &amp; versatile music player" />
</Paragraph>
<Paragraph>
<Run Text="Proudly built by" />
<Hyperlink NavigateUri="{x:Bind const:Link.AuthorGitHub}">
<Run Text="{x:Bind const:App.AppAuthor}" />
</Hyperlink>
</Paragraph>
</RichTextBlock>
</StackPanel> </StackPanel>
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>

View File

@@ -1,6 +1,8 @@
using BetterLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info. // and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -16,5 +18,15 @@ namespace BetterLyrics.WinUI3.Controls
InitializeComponent(); InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<AboutControlViewModel>(); DataContext = Ioc.Default.GetRequiredService<AboutControlViewModel>();
} }
private void WeChat_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
WeChatFlyout.ShowAt(WeChatButton);
}
private void AlipayButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
AlipayFlyout.ShowAt(AlipayButton);
}
} }
} }

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AlbumArtAreaEffectSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageAlbumEffect" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageImageSwitchType" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8EB;}">
<ComboBox SelectedIndex="{x:Bind AlbumArtAreaEffectSettings.ImageSwitchType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageCrossfade" />
<ComboBoxItem x:Uid="SettingsPageSlide" />
</ComboBox>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,25 @@
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class AlbumArtAreaEffectSettingsControl : UserControl
{
public static readonly DependencyProperty AlbumArtAreaEffectSettingsProperty =
DependencyProperty.Register(nameof(AlbumArtAreaEffectSettings), typeof(AlbumArtAreaEffectSettings), typeof(AlbumArtAreaEffectSettingsControl), new PropertyMetadata(default));
public AlbumArtAreaEffectSettings AlbumArtAreaEffectSettings
{
get => (AlbumArtAreaEffectSettings)GetValue(AlbumArtAreaEffectSettingsProperty);
set => SetValue(AlbumArtAreaEffectSettingsProperty, value);
}
public AlbumArtAreaEffectSettingsControl()
{
InitializeComponent();
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<UserControl <UserControl
x:Class="BetterLyrics.WinUI3.Controls.AlbumArtLayoutSettingsControl" x:Class="BetterLyrics.WinUI3.Controls.AlbumArtAreaStyleSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
@@ -16,8 +16,9 @@
<Grid Style="{StaticResource SettingsGridStyle}"> <Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="SettingsPageAlbumStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<!-- 整体对齐 -->
<dev:SettingsCard x:Uid="SettingsPageAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}"> <dev:SettingsCard x:Uid="SettingsPageAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}"> <ComboBox SelectedIndex="{x:Bind AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLeft" /> <ComboBoxItem x:Uid="SettingsPageLeft" />
@@ -28,6 +29,26 @@
<TextBlock x:Uid="SettingsPageAlbumArt" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="SettingsPageAlbumArt" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<!-- 专辑高度 -->
<dev:SettingsExpander
x:Uid="SettingsPageAlbumArtHeight"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE784;}"
IsExpanded="True">
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.IsAutoCoverImageHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.IsAutoCoverImageHeight, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<NumberBox
Minimum="0"
SpinButtonPlacementMode="Inline"
Value="{x:Bind AlbumArtLayoutSettings.CoverImageHeight, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 专辑圆角 -->
<dev:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}"> <dev:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}">
<local:ExtendedSlider <local:ExtendedSlider
Default="12" Default="12"
@@ -37,6 +58,7 @@
Value="{x:Bind AlbumArtLayoutSettings.CoverImageRadius, Mode=TwoWay}" /> Value="{x:Bind AlbumArtLayoutSettings.CoverImageRadius, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<!-- 专辑阴影 -->
<dev:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF5EF;}"> <dev:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF5EF;}">
<local:ExtendedSlider <local:ExtendedSlider
Default="12" Default="12"

View File

@@ -7,18 +7,18 @@ using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.Controls namespace BetterLyrics.WinUI3.Controls
{ {
public sealed partial class AlbumArtLayoutSettingsControl : UserControl public sealed partial class AlbumArtAreaStyleSettingsControl : UserControl
{ {
public static readonly DependencyProperty AlbumArtLayoutSettingsProperty = public static readonly DependencyProperty AlbumArtLayoutSettingsProperty =
DependencyProperty.Register(nameof(AlbumArtLayoutSettings), typeof(AlbumArtLayoutSettings), typeof(AlbumArtLayoutSettingsControl), new PropertyMetadata(default)); DependencyProperty.Register(nameof(AlbumArtLayoutSettings), typeof(AlbumArtAreaStyleSettings), typeof(AlbumArtAreaStyleSettingsControl), new PropertyMetadata(default));
public AlbumArtLayoutSettings AlbumArtLayoutSettings public AlbumArtAreaStyleSettings AlbumArtLayoutSettings
{ {
get => (AlbumArtLayoutSettings)GetValue(AlbumArtLayoutSettingsProperty); get => (AlbumArtAreaStyleSettings)GetValue(AlbumArtLayoutSettingsProperty);
set => SetValue(AlbumArtLayoutSettingsProperty, value); set => SetValue(AlbumArtLayoutSettingsProperty, value);
} }
public AlbumArtLayoutSettingsControl() public AlbumArtAreaStyleSettingsControl()
{ {
InitializeComponent(); InitializeComponent();
} }

View File

@@ -3,6 +3,7 @@
x:Class="BetterLyrics.WinUI3.Controls.AppSettingsControl" x:Class="BetterLyrics.WinUI3.Controls.AppSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:consts="using:BetterLyrics.WinUI3.Constants"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI" xmlns:dev="using:DevWinUI"
@@ -42,11 +43,19 @@
<Button x:Uid="SettingsPageRestart" Command="{x:Bind ViewModel.RestartAppCommand}" /> <Button x:Uid="SettingsPageRestart" Command="{x:Bind ViewModel.RestartAppCommand}" />
</dev:SettingsCard> </dev:SettingsCard>
</dev:SettingsExpander.Items> </dev:SettingsExpander.Items>
<dev:SettingsExpander.ItemsFooter>
<InfoBar IsClosable="False" IsOpen="True">
<HyperlinkButton
x:Uid="SettingsPageHelpUsTranslate"
Padding="0"
NavigateUri="{x:Bind consts:Link.Crowdin}" />
</InfoBar>
</dev:SettingsExpander.ItemsFooter>
</dev:SettingsExpander> </dev:SettingsExpander>
<!-- App behavior --> <!-- Startup -->
<TextBlock x:Uid="SettingsPageAppBehavior" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="SettingsPageStartup" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageAutoStart" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF71C;}"> <dev:SettingsCard x:Uid="SettingsPageAutoStart" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF71C;}">
<ToggleSwitch <ToggleSwitch
@@ -59,42 +68,46 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoOpen, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoOpen, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAutoOpenLyricsWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE90B;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.AutoStartLyricsWindow, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- Music gallery window -->
<TextBlock x:Uid="SettingsPageMusicGallery" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageAutoPlayWhenOpenMusicGalleryWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE768;}"> <dev:SettingsCard x:Uid="SettingsPageAutoPlayWhenOpenMusicGalleryWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE768;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoPlay, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoPlay, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageStopTrackOnGalleryWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE71A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.StopOnWindowClosed, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageExitOnGalleryWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE711;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.ExitOnWindowClosed, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- Lyrics window -->
<TextBlock x:Uid="SettingsPageLyricsWindow" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageExitOnLyricsWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE711;}"> <dev:SettingsCard x:Uid="SettingsPageExitOnLyricsWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE711;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ExitOnLyricsWindowClosed, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ExitOnLyricsWindowClosed, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageListenNewSession" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF270;}"> <!-- Playback shortcut -->
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D8;}"> <TextBlock x:Uid="SettingsPageShortcut" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreCacheWhenSearching, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowHideHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}"> <dev:SettingsCard x:Uid="SettingsPageShowHideHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ShowOrHideLyricsWindowShortcut, Mode=TwoWay}" /> <local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ShowOrHideLyricsWindowShortcut, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBorderlessHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.BorderlessShortcut, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageClickThroughHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ClickThroughShortcut, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsWindowSwitchHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}"> <dev:SettingsCard x:Uid="SettingsPageLyricsWindowSwitchHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.LyricsWindowSwitchShortcut, Mode=TwoWay}" /> <local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.LyricsWindowSwitchShortcut, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<!-- Playback shortcut -->
<TextBlock x:Uid="SettingsPagePlaybackShortcut" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPagePlayOrPauseSongHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}"> <dev:SettingsCard x:Uid="SettingsPagePlayOrPauseSongHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.PlayOrPauseShortcut, Mode=TwoWay}" /> <local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.PlayOrPauseShortcut, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>

View File

@@ -7,6 +7,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls" xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid <Grid
@@ -91,6 +92,47 @@
Text="{x:Bind LyricsWindowStatus.Name, Mode=OneWay}" Text="{x:Bind LyricsWindowStatus.Name, Mode=OneWay}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</Grid> </Grid>
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}" Opacity="0">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Click="OpenButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE78B;}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SystemTrayLyrics" />
</ToolTipService.ToolTip>
</Button>
<Button
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Click="CloseButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE711;}"
IsEnabled="{x:Bind LyricsWindowStatus.IsOpened, Mode=OneWay}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SettingsPageCloseStatus" />
</ToolTipService.ToolTip>
</Button>
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -1,6 +1,11 @@
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System.Linq;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info. // and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -9,6 +14,8 @@ namespace BetterLyrics.WinUI3.Controls;
public sealed partial class DemoWindowGrid : UserControl public sealed partial class DemoWindowGrid : UserControl
{ {
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public DemoWindowGrid() public DemoWindowGrid()
{ {
InitializeComponent(); InitializeComponent();
@@ -22,4 +29,32 @@ public sealed partial class DemoWindowGrid : UserControl
get => (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty); get => (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty);
set => SetValue(LyricsWindowStatusProperty, value); set => SetValue(LyricsWindowStatusProperty, value);
} }
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
var data = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
var window = WindowHook.GetWindows<NowPlayingWindow>().FirstOrDefault(x => x.LyricsWindowStatus == data);
window?.CloseWindow();
}
private void OpenButton_Click(object sender, RoutedEventArgs e)
{
var status = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
// <20>࿪ģʽ
if (_settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
{
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
}
// <20><><EFBFBD><EFBFBD>ģʽ
else
{
var openedWindows = WindowHook.GetWindows<NowPlayingWindow>();
foreach (var item in openedWindows.Where(x => x.LyricsWindowStatus != status))
{
item.CloseWindow();
}
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
}
}
} }

View File

@@ -1,25 +0,0 @@
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.Dragger"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Background="Transparent"
PointerCanceled="Grid_PointerReleased"
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_PointerExited"
PointerMoved="Grid_PointerMoved"
PointerPressed="Grid_PointerPressed"
PointerReleased="Grid_PointerReleased">
<Border x:Name="HitArea" Background="{ThemeResource SystemControlForegroundBaseMediumLowBrush}">
<Grid
x:Name="HandlePill"
Background="{ThemeResource TextFillColorSecondaryBrush}"
CornerRadius="4" />
</Border>
</Grid>
</UserControl>

View File

@@ -1,135 +0,0 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using System;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Controls
{
public class DragDeltaEventArgs : EventArgs
{
public double HorizontalChange { get; }
public double VerticalChange { get; }
public DragDeltaEventArgs(double hChange, double vChange)
{
HorizontalChange = hChange;
VerticalChange = vChange;
}
}
public sealed partial class Dragger : UserControl
{
public event EventHandler DragStarted;
public event EventHandler<DragDeltaEventArgs> DragDelta;
public event EventHandler DragCompleted;
private bool _isDragging = false;
private Point _lastPoint;
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(Dragger),
new PropertyMetadata(Orientation.Vertical, OnOrientationChanged));
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Dragger)d;
control.UpdateVisuals();
}
public Dragger()
{
this.InitializeComponent();
this.Loaded += (s, e) => UpdateVisuals();
}
private void UpdateVisuals()
{
if (Orientation == Orientation.Vertical)
{
this.ProtectedCursor = InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast);
this.Width = 16;
this.Height = double.NaN; // Auto
if (HitArea != null && HandlePill != null)
{
HitArea.Width = 16;
HitArea.Height = double.NaN;
HandlePill.Width = 8;
HandlePill.Height = 32;
}
}
else
{
this.ProtectedCursor = InputSystemCursor.Create(InputSystemCursorShape.SizeNorthSouth);
this.Height = 16;
this.Width = double.NaN; // Auto
if (HitArea != null && HandlePill != null)
{
HitArea.Height = 16;
HitArea.Width = double.NaN;
HandlePill.Height = 8;
HandlePill.Width = 32;
}
}
}
private void Grid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
}
private void Grid_PointerExited(object sender, PointerRoutedEventArgs e)
{
}
private void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var element = sender as UIElement;
if (element.CapturePointer(e.Pointer))
{
_isDragging = true;
_lastPoint = e.GetCurrentPoint(this.XamlRoot.Content).Position;
DragStarted?.Invoke(this, EventArgs.Empty);
}
}
private void Grid_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_isDragging)
{
var currentPoint = e.GetCurrentPoint(this.XamlRoot.Content).Position;
double dx = currentPoint.X - _lastPoint.X;
double dy = currentPoint.Y - _lastPoint.Y;
if (dx != 0 || dy != 0)
{
DragDelta?.Invoke(this, new DragDeltaEventArgs(dx, dy));
_lastPoint = currentPoint;
}
}
}
private void Grid_PointerReleased(object sender, PointerRoutedEventArgs e)
{
if (_isDragging)
{
var element = sender as UIElement;
_isDragging = false;
element.ReleasePointerCapture(e.Pointer);
DragCompleted?.Invoke(this, EventArgs.Empty);
}
}
}
}

View File

@@ -6,25 +6,54 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls" xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid> <Grid>
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
x:Name="SelectedLocalizedFontFamilyTextBlock"
VerticalAlignment="Center"
Foreground="{StaticResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True" />
<AutoSuggestBox <AutoSuggestBox
x:Name="AutoSuggestBox" x:Name="AutoSuggestBox"
Width="150" MinWidth="180"
GotFocus="AutoSuggestBox_GotFocus" GotFocus="AutoSuggestBox_GotFocus"
LostFocus="AutoSuggestBox_LostFocus" LostFocus="AutoSuggestBox_LostFocus"
SuggestionChosen="AutoSuggestBox_SuggestionChosen" SuggestionChosen="AutoSuggestBox_SuggestionChosen"
Text="{x:Bind SelectedFontFamily, Mode=OneWay}" Text="{x:Bind SelectedFontFamily, Mode=OneWay}"
TextChanged="AutoSuggestBox_TextChanged" /> TextChanged="AutoSuggestBox_TextChanged">
<!--<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="models:ExtendedFontFamily">
<StackPanel>
<TextBlock Text="{x:Bind LocalizedFontFamily}" TextWrapping="Wrap" />
<TextBlock
FontSize="12"
Foreground="{StaticResource TextFillColorSecondaryBrush}"
Text="{x:Bind FontFamily}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>-->
</AutoSuggestBox>
<Button <Button
Click="Button_Click" Click="Button_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12, FontSize=12,
Glyph=&#xE72C;}" Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" /> Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageRefreshDropdown" />
</ToolTipService.ToolTip>
</Button>
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE74A;}" Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageCollapseDropdown" />
</ToolTipService.ToolTip>
</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -1,4 +1,7 @@
using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic; using System.Collections.Generic;
@@ -11,11 +14,15 @@ namespace BetterLyrics.WinUI3.Controls
{ {
public sealed partial class FontFamilyAutoSuggestBox : UserControl public sealed partial class FontFamilyAutoSuggestBox : UserControl
{ {
private List<string> SystemFontNames { get; set; } = [.. FontHelper.SystemFontFamilies]; private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
//private List<ExtendedFontFamily> FontFamilies { get; set; } = [];
private List<string> FontFamilies { get; set; } = [];
public FontFamilyAutoSuggestBox() public FontFamilyAutoSuggestBox()
{ {
InitializeComponent(); InitializeComponent();
RefreshFontFamilies();
} }
public static readonly DependencyProperty SelectedFontFamilyProperty = public static readonly DependencyProperty SelectedFontFamilyProperty =
@@ -27,20 +34,47 @@ namespace BetterLyrics.WinUI3.Controls
set => SetValue(SelectedFontFamilyProperty, value); set => SetValue(SelectedFontFamilyProperty, value);
} }
private void RefreshFontFamilies()
{
//Task.Run(() =>
//{
// var fontFamilies = FontHelper.SystemFontFamilies.Select(x => new ExtendedFontFamily()
// {
// FontFamily = x,
// LocalizedFontFamily = FontHelper.GetLocalizedFontFamilyName(x, _settingsService.AppSettings.GeneralSettings.LanguageCode)
// }).OrderBy(x => x.LocalizedFontFamily).ToList();
// DispatcherQueue.TryEnqueue(() =>
// {
// FontFamilies = fontFamilies;
// });
//});
FontFamilies = FontHelper.GetSystemFontFamilies();
}
private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{ {
SelectedFontFamily = args.SelectedItem.ToString() ?? ""; if (args.SelectedItem is ExtendedFontFamily extendedFontFamily)
{
SelectedFontFamily = extendedFontFamily.FontFamily;
}
else
{
SelectedFontFamily = args.SelectedItem.ToString() ?? "";
}
} }
private void UpdateAutoSuggestBoxItemsSource() private void UpdateAutoSuggestBoxItemsSource(string? query = null)
{ {
query ??= AutoSuggestBox.Text;
//var suitableItems = new List<ExtendedFontFamily>();
var suitableItems = new List<string>(); var suitableItems = new List<string>();
var splitText = AutoSuggestBox.Text.ToLower().Split(" "); var splitText = query.ToLower().Split(" ");
foreach (var fontFamily in SystemFontNames) foreach (var fontFamily in FontFamilies)
{ {
var found = splitText.All((key) => bool found = splitText.All((key) =>
{ {
//return fontFamily.FontFamily.ToLower().Contains(key) || fontFamily.LocalizedFontFamily.ToLower().Contains(key);
return fontFamily.ToLower().Contains(key); return fontFamily.ToLower().Contains(key);
}); });
if (found) if (found)
@@ -50,9 +84,15 @@ namespace BetterLyrics.WinUI3.Controls
} }
if (suitableItems.Count == 0) if (suitableItems.Count == 0)
{ {
//suitableItems.Add(new ExtendedFontFamily()
//{
// FontFamily = "",
// LocalizedFontFamily = "N/A"
//});
suitableItems.Add("N/A"); suitableItems.Add("N/A");
} }
AutoSuggestBox.ItemsSource = suitableItems.Order(); //AutoSuggestBox.ItemsSource = suitableItems.OrderBy(x => x.LocalizedFontFamily);
AutoSuggestBox.ItemsSource = suitableItems.OrderBy(x => x);
} }
private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
@@ -63,16 +103,18 @@ namespace BetterLyrics.WinUI3.Controls
{ {
UpdateAutoSuggestBoxItemsSource(); UpdateAutoSuggestBoxItemsSource();
} }
SelectedLocalizedFontFamilyTextBlock.Text = FontHelper.GetLocalizedFontFamilyName(SelectedFontFamily, _settingsService.AppSettings.GeneralSettings.LanguageCode);
} }
private void Button_Click(object sender, RoutedEventArgs e) private void Button_Click(object sender, RoutedEventArgs e)
{ {
SystemFontNames = [.. FontHelper.SystemFontFamilies]; RefreshFontFamilies();
} }
private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e) private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e)
{ {
UpdateAutoSuggestBoxItemsSource(); UpdateAutoSuggestBoxItemsSource("");
} }
private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e) private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e)

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.ImageSwitcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:const="using:BetterLyrics.WinUI3.Constants"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Grid.OpacityTransition>
<local:ShadowImage
x:Name="LastAlbumArtImage"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadiusAmount="{x:Bind CornerRadiusAmount, Mode=OneWay}"
ShadowAmount="{x:Bind ShadowAmount, Mode=OneWay}" />
<local:ShadowImage
x:Name="AlbumArtImage"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadiusAmount="{x:Bind CornerRadiusAmount, Mode=OneWay}"
ShadowAmount="{x:Bind ShadowAmount, Mode=OneWay}" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,133 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class ImageSwitcher : UserControl
{
public int CornerRadiusAmount
{
get { return (int)GetValue(CornerRadiusAmountProperty); }
set { SetValue(CornerRadiusAmountProperty, value); }
}
public static readonly DependencyProperty CornerRadiusAmountProperty =
DependencyProperty.Register(nameof(CornerRadiusAmount), typeof(int), typeof(ImageSwitcher), new PropertyMetadata(0));
public int ShadowAmount
{
get { return (int)GetValue(ShadowAmountProperty); }
set { SetValue(ShadowAmountProperty, value); }
}
public static readonly DependencyProperty ShadowAmountProperty =
DependencyProperty.Register(nameof(ShadowAmount), typeof(int), typeof(ImageSwitcher), new PropertyMetadata(0));
public ImageSource? Source
{
get { return (ImageSource?)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(ImageSource), typeof(ImageSwitcher), new PropertyMetadata(null, OnDependencyPropertyChanged));
public ImageSwitchType SwitchType
{
get { return (ImageSwitchType)GetValue(SwitchTypeProperty); }
set { SetValue(SwitchTypeProperty, value); }
}
public static readonly DependencyProperty SwitchTypeProperty =
DependencyProperty.Register(nameof(SwitchType), typeof(ImageSwitchType), typeof(ImageSwitcher), new PropertyMetadata(ImageSwitchType.Crossfade));
public ImageSwitcher()
{
InitializeComponent();
}
private void UpdateSource()
{
switch (SwitchType)
{
case ImageSwitchType.Crossfade:
UpdateSourceCrossfade();
break;
case ImageSwitchType.Slide:
UpdateSourceSlide();
break;
default:
break;
}
}
private void UpdateSourceCrossfade()
{
// Ϊ<><CEAA><EFBFBD><EFBFBD>ͼƬ<CDBC><C6AC><EFBFBD>þ<EFBFBD>Դ
LastAlbumArtImage.Source = AlbumArtImage.Source;
// ʹ<><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>
LastAlbumArtImage.TranslationTransition = null;
LastAlbumArtImage.OpacityTransition = null;
LastAlbumArtImage.Translation = new();
LastAlbumArtImage.Opacity = 1;
LastAlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ʹǰ<CAB9><C7B0>ͼƬ<CDBC><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>
AlbumArtImage.TranslationTransition = null;
AlbumArtImage.OpacityTransition = null;
AlbumArtImage.Translation = new();
AlbumArtImage.Opacity = 0;
AlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ֮<><D6AE>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
AlbumArtImage.Source = Source;
// <20><><EFBFBD><EFBFBD><E6B5AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LastAlbumArtImage.Opacity = 0;
AlbumArtImage.Opacity = 1;
}
private void UpdateSourceSlide()
{
// Ϊ<><CEAA><EFBFBD><EFBFBD>ͼƬ<CDBC><C6AC><EFBFBD>þ<EFBFBD>Դ
LastAlbumArtImage.Source = AlbumArtImage.Source;
// ʹ<><CAB9><EFBFBD><EFBFBD>λ
LastAlbumArtImage.TranslationTransition = null;
LastAlbumArtImage.OpacityTransition = null;
LastAlbumArtImage.Translation = new();
LastAlbumArtImage.Opacity = 1;
LastAlbumArtImage.TranslationTransition = new Vector3Transition { Duration = Constants.Time.AnimationDuration };
LastAlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ʹǰ<CAB9><C7B0>ͼƬ<CDBC><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>
AlbumArtImage.TranslationTransition = null;
AlbumArtImage.OpacityTransition = null;
AlbumArtImage.Translation = new(-(float)ActualWidth, 0, 0);
AlbumArtImage.Opacity = 0;
AlbumArtImage.TranslationTransition = new Vector3Transition { Duration = Constants.Time.AnimationDuration };
AlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ֮<><D6AE>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
AlbumArtImage.Source = Source;
// <20><><EFBFBD><EFBFBD>
LastAlbumArtImage.Opacity = 0;
AlbumArtImage.Opacity = 1;
LastAlbumArtImage.Translation = new(-(float)ActualWidth, 0, 0);
AlbumArtImage.Translation = new();
}
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageSwitcher imageSwitcher)
{
if (e.Property == SourceProperty)
{
imageSwitcher.UpdateSource();
}
}
}
}

View File

@@ -1,172 +0,0 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Controls
{
public static class InstantTip
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.RegisterAttached("Content", typeof(object), typeof(InstantTip), new PropertyMetadata(null, OnContentChanged));
public static void SetContent(DependencyObject element, object value) => element.SetValue(ContentProperty, value);
public static object GetContent(DependencyObject element) => element.GetValue(ContentProperty);
private static Dictionary<int, Popup> _activePopups = [];
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element)
{
element.PointerEntered -= Element_PointerEntered;
element.PointerExited -= Element_PointerExited;
element.Unloaded -= Element_Unloaded;
if (e.NewValue != null)
{
element.PointerEntered += Element_PointerEntered;
element.PointerExited += Element_PointerExited;
element.Unloaded += Element_Unloaded;
}
}
}
private static void Element_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
int hashCode = element.GetHashCode();
if (_activePopups.ContainsKey(hashCode))
{
_activePopups.TryGetValue(hashCode, out var popup);
if (popup != null)
{
popup.IsOpen = true;
}
}
else
{
var rawContent = GetContent(element);
if (rawContent == null) return;
// 创建可视卡片容器 (Visual Card)
var visualCard = new Grid
{
Background = (Brush)Application.Current.Resources["AcrylicBackgroundFillColorDefaultBrush"],
CornerRadius = new CornerRadius(4),
Shadow = new ThemeShadow(),
Translation = new Vector3(0, 0, 32),
Padding = new Thickness(8, 4, 8, 4)
};
var popupContent = new Grid
{
IsHitTestVisible = false,
Opacity = 0,
Padding = new Thickness(16)
};
popupContent.Children.Add(visualCard);
var popup = new Popup
{
Child = popupContent,
IsHitTestVisible = false,
ShouldConstrainToRootBounds = false,
XamlRoot = element.XamlRoot,
};
object finalContent = rawContent;
if (rawContent is ToolTip toolTipWrapper)
{
finalContent = toolTipWrapper.Content;
}
if (finalContent is string text)
{
var textBlock = new TextBlock
{
Text = text,
FontSize = 12,
MaxWidth = 320,
TextWrapping = TextWrapping.Wrap,
Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]
};
visualCard.Children.Add(textBlock);
}
else if (finalContent != null)
{
var textBlock = new TextBlock
{
Text = finalContent.ToString(),
FontSize = 12,
MaxWidth = 320,
TextWrapping = TextWrapping.Wrap,
Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]
};
visualCard.Children.Add(textBlock);
}
var transform = element.TransformToVisual(null);
var point = transform.TransformPoint(new Point(0, element.ActualHeight));
popup.VerticalOffset = point.Y;
popup.HorizontalOffset = point.X - popupContent.Padding.Left;
popup.IsOpen = true;
App.Current.Resources.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
popupContent.OpacityTransition = new ScalarTransition();
popupContent.Opacity = 1;
});
_activePopups.Add(hashCode, popup);
}
}
}
private static void Element_PointerExited(object sender, PointerRoutedEventArgs e)
{
var element = sender as FrameworkElement;
if (element != null)
{
int hashCode = element.GetHashCode();
if (!_activePopups.ContainsKey(hashCode))
{
return;
}
_activePopups.TryGetValue(hashCode, out var popup);
if (popup != null)
{
if (popup.Child is Grid popupContent)
{
popupContent.Opacity = 0;
}
popup.IsOpen = false;
_activePopups.Remove(hashCode);
}
}
}
private static void Element_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
element.PointerEntered -= Element_PointerEntered;
element.PointerExited -= Element_PointerExited;
element.Unloaded -= Element_Unloaded;
}
}
}
}

View File

@@ -17,7 +17,7 @@
<Grid Style="{StaticResource SettingsGridStyle}"> <Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="SettingsPageBackgroundOverlay" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE790;}"> <dev:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE790;}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind LyricsBackgroundSettings.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}"> <ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind LyricsBackgroundSettings.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
@@ -47,6 +47,43 @@
</dev:SettingsExpander.Items> </dev:SettingsExpander.Items>
</dev:SettingsExpander> </dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAlbumArtLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE93C;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="50"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBlurAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Maximum="200"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander <dev:SettingsExpander
x:Uid="SettingsPageFluidLayer" x:Uid="SettingsPageFluidLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<UserControl <UserControl
x:Class="BetterLyrics.WinUI3.Controls.NowPlayingCanvas" x:Class="BetterLyrics.WinUI3.Controls.LyricsCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml" xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"

View File

@@ -1,55 +1,58 @@
// 2025/6/23 by Zhe Fang // 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Logic; using BetterLyrics.WinUI3.Logic;
using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings; using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Renderer; using BetterLyrics.WinUI3.Renderer;
using BetterLyrics.WinUI3.Services.LastFMService; using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LiveStatesService; using BetterLyrics.WinUI3.Services.GSMTCService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService; using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages; using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml; using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Threading.Tasks;
using Windows.Foundation; using Windows.Foundation;
using Windows.Storage.Streams;
using Windows.UI; using Windows.UI;
using System.Numerics;
namespace BetterLyrics.WinUI3.Controls namespace BetterLyrics.WinUI3.Controls
{ {
public sealed partial class NowPlayingCanvas : UserControl, public sealed partial class LyricsCanvas : UserControl,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<AlbumArtThemeColors>>,
IRecipient<PropertyChangedMessage<TimeSpan>>, IRecipient<PropertyChangedMessage<TimeSpan>>,
IRecipient<PropertyChangedMessage<LyricsData?>>, IRecipient<PropertyChangedMessage<LyricsData?>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>, IRecipient<PropertyChangedMessage<SongInfo>>,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<double>>, IRecipient<PropertyChangedMessage<double>>,
IRecipient<PropertyChangedMessage<bool>>, IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<TextAlignmentType>>, IRecipient<PropertyChangedMessage<TextAlignmentType>>,
IRecipient<PropertyChangedMessage<SongInfo?>> IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<IRandomAccessStream?>>
{ {
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>(); private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>(); private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private readonly ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
private readonly LyricsRenderer _lyricsRenderer = new(); private readonly LyricsRenderer _lyricsRenderer = new();
private readonly FluidBackgroundRenderer _fluidRenderer = new(); private readonly FluidBackgroundRenderer _fluidRenderer = new();
private readonly CoverBackgroundRenderer _coverRenderer = new();
private readonly PureColorBackgroundRenderer _pureColorRenderer = new(); private readonly PureColorBackgroundRenderer _pureColorRenderer = new();
private readonly SnowRenderer _snowRenderer = new(); private readonly SnowRenderer _snowRenderer = new();
private readonly FogRenderer _fogRenderer = new(); private readonly FogRenderer _fogRenderer = new();
private readonly SpectrumRenderer _spectrumRenderer = new(); private readonly SpectrumRenderer _spectrumRenderer = new();
private readonly LyricsSynchronizer _synchronizer = new(); private readonly LyricsSynchronizer _synchronizer = new();
private readonly LyricsLayoutManager _layoutManager = new();
private readonly LyricsAnimator _animator = new(); private readonly LyricsAnimator _animator = new();
private readonly SpectrumAnalyzer _spectrumAnalyzer = new(); private readonly SpectrumAnalyzer _spectrumAnalyzer = new();
@@ -88,10 +91,14 @@ namespace BetterLyrics.WinUI3.Controls
durationSeconds: 0.3f, durationSeconds: 0.3f,
easingType: EasingType.EaseInOutSine easingType: EasingType.EaseInOutSine
); );
private readonly ValueTransition<double> _mouseYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutSine
);
private TimeSpan _songPositionWithOffset;
private TimeSpan _songPosition; // 当前歌曲时刻 private TimeSpan _songPosition; // 当前歌曲时刻
private TimeSpan _totalPlayedTime; // 当前歌曲播放总时长(包括来来回回重复播放的时间)
private bool _isLastFMTracked = false;
private double _renderLyricsStartX = 0; private double _renderLyricsStartX = 0;
private double _renderLyricsStartY = 0; private double _renderLyricsStartY = 0;
@@ -99,14 +106,46 @@ namespace BetterLyrics.WinUI3.Controls
private double _renderLyricsHeight = 0; private double _renderLyricsHeight = 0;
private double _renderLyricsOpacity = 0; private double _renderLyricsOpacity = 0;
private LyricsData? _lyricsData; private LyricsWindowStatus? _lyricsWindowStatus = null;
private AlbumArtThemeColors _albumArtThemeColors = new();
private Point _mousePosition = new(0, 0);
private int _mouseHoverLineIndex = -1;
private bool _isMouseInLyricsArea = false;
private bool _isMousePressing = false;
private bool _isMouseScrolling = false;
private List<RenderLyricsLine>? _renderLyricsLines = null;
private bool _isLayoutChanged = true; private bool _isLayoutChanged = true;
private bool _isMouseScrollingChanged = false;
private int _playingLineIndex; private int _playingLineIndex;
private (int Start, int End) _visibleRange; private (int Start, int End) _visibleRange;
private double _canvasTargetScrollOffset; private double _canvasTargetScrollOffset;
public TimeSpan SongPosition => _songPosition; public TimeSpan SongPosition => _songPosition;
public double CurrentCanvasYScroll => _canvasYScrollTransition.Value;
public double ActualLyricsHeight => LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines);
public int CurrentHoveringLineIndex => _mouseHoverLineIndex;
public LyricsWindowStatus? LyricsWindowStatus
{
get { return (LyricsWindowStatus?)GetValue(LyricsWindowStatusProperty); }
set { SetValue(LyricsWindowStatusProperty, value); }
}
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(LyricsCanvas), new PropertyMetadata(null, OnDependencyPropertyChanged));
public AlbumArtThemeColors AlbumArtThemeColors
{
get { return (AlbumArtThemeColors)GetValue(AlbumArtThemeColorsProperty); }
set { SetValue(AlbumArtThemeColorsProperty, value); }
}
public static readonly DependencyProperty AlbumArtThemeColorsProperty =
DependencyProperty.Register(nameof(AlbumArtThemeColors), typeof(AlbumArtThemeColors), typeof(LyricsCanvas), new PropertyMetadata(new AlbumArtThemeColors(), OnDependencyPropertyChanged));
// 歌词区域起始横 X 坐标 // 歌词区域起始横 X 坐标
public double LyricsStartX public double LyricsStartX
@@ -116,7 +155,7 @@ namespace BetterLyrics.WinUI3.Controls
} }
public static readonly DependencyProperty LyricsStartXProperty = public static readonly DependencyProperty LyricsStartXProperty =
DependencyProperty.Register(nameof(LyricsStartX), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged)); DependencyProperty.Register(nameof(LyricsStartX), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// 歌词区域起始 Y 坐标 // 歌词区域起始 Y 坐标
public double LyricsStartY public double LyricsStartY
@@ -126,7 +165,7 @@ namespace BetterLyrics.WinUI3.Controls
} }
public static readonly DependencyProperty LyricsStartYProperty = public static readonly DependencyProperty LyricsStartYProperty =
DependencyProperty.Register(nameof(LyricsStartY), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged)); DependencyProperty.Register(nameof(LyricsStartY), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// 歌词区域最大宽度 // 歌词区域最大宽度
public double LyricsWidth public double LyricsWidth
@@ -136,7 +175,7 @@ namespace BetterLyrics.WinUI3.Controls
} }
public static readonly DependencyProperty LyricsWidthProperty = public static readonly DependencyProperty LyricsWidthProperty =
DependencyProperty.Register(nameof(LyricsWidth), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged)); DependencyProperty.Register(nameof(LyricsWidth), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// 歌词区域最大高度 // 歌词区域最大高度
public double LyricsHeight public double LyricsHeight
@@ -146,7 +185,7 @@ namespace BetterLyrics.WinUI3.Controls
} }
public static readonly DependencyProperty LyricsHeightProperty = public static readonly DependencyProperty LyricsHeightProperty =
DependencyProperty.Register(nameof(LyricsHeight), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged)); DependencyProperty.Register(nameof(LyricsHeight), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// 歌词区域不透明度 // 歌词区域不透明度
public double LyricsOpacity public double LyricsOpacity
@@ -156,49 +195,139 @@ namespace BetterLyrics.WinUI3.Controls
} }
public static readonly DependencyProperty LyricsOpacityProperty = public static readonly DependencyProperty LyricsOpacityProperty =
DependencyProperty.Register(nameof(LyricsOpacity), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged)); DependencyProperty.Register(nameof(LyricsOpacity), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
public NowPlayingCanvas() /// <summary>
/// 用户操控鼠标已滚动的距离(从 0 开始算)
/// </summary>
public double MouseScrollOffset
{
get { return (double)GetValue(MouseScrollOffsetProperty); }
set { SetValue(MouseScrollOffsetProperty, value); }
}
public static readonly DependencyProperty MouseScrollOffsetProperty =
DependencyProperty.Register(nameof(MouseScrollOffset), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
/// <summary>
/// 用户鼠标当前的位置(相对于歌词区域左上角)
/// </summary>
public Point MousePosition
{
get { return (Point)GetValue(MousePositionProperty); }
set { SetValue(MousePositionProperty, value); }
}
public static readonly DependencyProperty MousePositionProperty =
DependencyProperty.Register(nameof(MousePosition), typeof(Point), typeof(LyricsCanvas), new PropertyMetadata(new Point(0, 0), OnDependencyPropertyChanged));
public bool IsMouseInLyricsArea
{
get { return (bool)GetValue(IsMouseInLyricsAreaProperty); }
set { SetValue(IsMouseInLyricsAreaProperty, value); }
}
public static readonly DependencyProperty IsMouseInLyricsAreaProperty =
DependencyProperty.Register(nameof(IsMouseInLyricsArea), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnDependencyPropertyChanged));
public bool IsMousePressing
{
get { return (bool)GetValue(IsMousePressingProperty); }
set { SetValue(IsMousePressingProperty, value); }
}
public static readonly DependencyProperty IsMousePressingProperty =
DependencyProperty.Register(nameof(IsMousePressing), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnDependencyPropertyChanged));
public bool IsMouseScrolling
{
get { return (bool)GetValue(IsMouseScrollingProperty); }
set { SetValue(IsMouseScrollingProperty, value); }
}
public static readonly DependencyProperty IsMouseScrollingProperty =
DependencyProperty.Register(nameof(IsMouseScrolling), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnDependencyPropertyChanged));
public LyricsCanvas()
{ {
InitializeComponent(); InitializeComponent();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this); WeakReferenceMessenger.Default.RegisterAll(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<AlbumArtThemeColors>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TimeSpan>>(this); UpdateRenderLyricsLines();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsData?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsWindowStatus>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<double>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<bool>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TextAlignmentType>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<SongInfo?>>(this);
} }
private static void OnLayoutPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
if (d is NowPlayingCanvas canvas) if (d is LyricsCanvas canvas)
{ {
if (e.Property == LyricsStartXProperty) if (e.Property == LyricsWindowStatusProperty)
{
canvas._lyricsWindowStatus = (LyricsWindowStatus)e.NewValue;
canvas._isLayoutChanged = true;
}
else if (e.Property == LyricsStartXProperty)
{ {
canvas._renderLyricsStartX = Convert.ToDouble(e.NewValue); canvas._renderLyricsStartX = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
} }
else if (e.Property == LyricsStartYProperty) else if (e.Property == LyricsStartYProperty)
{ {
canvas._renderLyricsStartY = Convert.ToDouble(e.NewValue); canvas._renderLyricsStartY = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
} }
else if (e.Property == LyricsWidthProperty) else if (e.Property == LyricsWidthProperty)
{ {
canvas._renderLyricsWidth = Convert.ToDouble(e.NewValue); canvas._renderLyricsWidth = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
} }
else if (e.Property == LyricsHeightProperty) else if (e.Property == LyricsHeightProperty)
{ {
canvas._renderLyricsHeight = Convert.ToDouble(e.NewValue); canvas._renderLyricsHeight = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
} }
else if (e.Property == LyricsOpacityProperty) else if (e.Property == LyricsOpacityProperty)
{ {
canvas._renderLyricsOpacity = Convert.ToDouble(e.NewValue); canvas._renderLyricsOpacity = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
} }
else if (e.Property == MouseScrollOffsetProperty)
{
canvas._mouseYScrollTransition.StartTransition(Convert.ToDouble(e.NewValue));
}
else if (e.Property == MousePositionProperty)
{
canvas._mousePosition = (Point)e.NewValue;
}
else if (e.Property == IsMouseInLyricsAreaProperty)
{
canvas._isMouseInLyricsArea = (bool)e.NewValue;
}
else if (e.Property == IsMousePressingProperty)
{
canvas._isMousePressing = (bool)e.NewValue;
}
else if (e.Property == IsMouseScrollingProperty)
{
var value = (bool)e.NewValue;
if (canvas._isMouseScrolling != value)
{
canvas._isMouseScrollingChanged = true;
}
canvas._isMouseScrolling = value;
}
else if (e.Property == AlbumArtThemeColorsProperty)
{
var albumArtThemeColors = (AlbumArtThemeColors)e.NewValue;
canvas._immersiveBgColorTransition.StartTransition(albumArtThemeColors.EnvColor);
canvas._accentColor1Transition.StartTransition(albumArtThemeColors.AccentColor1);
canvas._accentColor2Transition.StartTransition(albumArtThemeColors.AccentColor2);
canvas._accentColor3Transition.StartTransition(albumArtThemeColors.AccentColor3);
canvas._accentColor4Transition.StartTransition(albumArtThemeColors.AccentColor4);
canvas._isLayoutChanged = true; canvas._albumArtThemeColors = albumArtThemeColors;
canvas._isLayoutChanged = true;
}
} }
} }
@@ -206,26 +335,22 @@ namespace BetterLyrics.WinUI3.Controls
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args) private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{ {
if (_lyricsWindowStatus == null) return;
var bounds = new Rect(0, 0, sender.Size.Width, sender.Size.Height); var bounds = new Rect(0, 0, sender.Size.Width, sender.Size.Height);
var status = _liveStatesService.LiveStates.LyricsWindowStatus; var albumArtLayout = _lyricsWindowStatus.AlbumArtLayoutSettings;
var albumArtLayout = status.AlbumArtLayoutSettings; var lyricsBg = _lyricsWindowStatus.LyricsBackgroundSettings;
var lyricsBg = status.LyricsBackgroundSettings; var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
var lyricsStyle = status.LyricsStyleSettings; var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
var lyricsEffect = status.LyricsEffectSettings;
var lyricsData = _lyricsData; double songDuration = _gsmtcService.CurrentSongInfo.DurationMs;
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect; bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
double fixedSongPositionMs = _songPosition.TotalMilliseconds + (_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
var lyricsThemeColors = _mediaSessionsService.AlbumArtThemeColors;
Color overlayColor; Color overlayColor;
double finalOpacity; double finalOpacity;
if (status.IsAdaptToEnvironment) if (_lyricsWindowStatus.IsAdaptToEnvironment)
{ {
// 自适应色 // 自适应色
overlayColor = _immersiveBgColorTransition.Value; overlayColor = _immersiveBgColorTransition.Value;
@@ -246,8 +371,8 @@ namespace BetterLyrics.WinUI3.Controls
lyricsBg.IsPureColorOverlayEnabled lyricsBg.IsPureColorOverlayEnabled
); );
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity; _coverRenderer.Draw(sender, args.DrawingSession);
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
_fluidRenderer.Draw(sender, args.DrawingSession); _fluidRenderer.Draw(sender, args.DrawingSession);
_snowRenderer.Draw(sender, args.DrawingSession); _snowRenderer.Draw(sender, args.DrawingSession);
@@ -257,30 +382,35 @@ namespace BetterLyrics.WinUI3.Controls
_lyricsRenderer.Draw( _lyricsRenderer.Draw(
control: sender, control: sender,
ds: args.DrawingSession, ds: args.DrawingSession,
lyricsData: _lyricsData, lines: _renderLyricsLines,
playingLineIndex: _playingLineIndex, playingLineIndex: _playingLineIndex,
mouseHoverLineIndex: _mouseHoverLineIndex,
isMousePressing: _isMousePressing,
startVisibleIndex: _visibleRange.Start, startVisibleIndex: _visibleRange.Start,
endVisibleIndex: _visibleRange.End, endVisibleIndex: _visibleRange.End,
lyricsX: _renderLyricsStartX, lyricsX: _renderLyricsStartX,
lyricsY: _renderLyricsStartY, lyricsY: _renderLyricsStartY,
lyricsWidth: _renderLyricsWidth, lyricsWidth: _renderLyricsWidth,
lyricsHeight: _renderLyricsHeight, lyricsHeight: _renderLyricsHeight,
userScrollOffset: _mouseYScrollTransition.Value,
lyricsOpacity: _renderLyricsOpacity, lyricsOpacity: _renderLyricsOpacity,
windowStatus: status, playingLineTopOffsetFactor: lyricsStyle.PlayingLineTopOffset / 100.0,
strokeColor: lyricsThemeColors.StrokeFontColor, windowStatus: _lyricsWindowStatus,
bgColor: lyricsThemeColors.BgFontColor, strokeColor: _albumArtThemeColors.StrokeFontColor,
fgColor: lyricsThemeColors.FgFontColor, bgColor: _albumArtThemeColors.BgFontColor,
fgColor: _albumArtThemeColors.FgFontColor,
currentProgressMs: _songPositionWithOffset.TotalMilliseconds,
getPlaybackState: (lineIndex) => getPlaybackState: (lineIndex) =>
{ {
if (lyricsData == null) return new LinePlaybackState(); if (_renderLyricsLines == null) return new LinePlaybackState();
var line = lyricsData.LyricsLines.ElementAtOrDefault(lineIndex); var line = _renderLyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return new LinePlaybackState(); if (line == null) return new LinePlaybackState();
var nextLine = lyricsData.LyricsLines.ElementAtOrDefault(lineIndex + 1); var nextLine = _renderLyricsLines.ElementAtOrDefault(lineIndex + 1);
return _synchronizer.GetLinePlayingProgress( return _synchronizer.GetLinePlayingProgress(
fixedSongPositionMs, _songPositionWithOffset.TotalMilliseconds,
line, line,
nextLine, nextLine,
songDuration, songDuration,
@@ -301,30 +431,35 @@ namespace BetterLyrics.WinUI3.Controls
style: lyricsBg.SpectrumStyle, style: lyricsBg.SpectrumStyle,
canvasWidth: sender.Size.Width, canvasWidth: sender.Size.Width,
canvasHeight: sender.Size.Height, canvasHeight: sender.Size.Height,
fillColor: lyricsThemeColors.BgFontColor fillColor: _albumArtThemeColors.BgFontColor
); );
} }
#if DEBUG #if DEBUG && false
args.DrawingSession.DrawText( args.DrawingSession.DrawText(
$"[DEBUG]\n" + $"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
$"Lyrics start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" + $"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
$"Lyrics size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" + $"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" +
$"Playing line (idx): {_playingLineIndex}\n" + $"Playing line (idx): {_playingLineIndex}\n" +
$"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" +
$"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" + $"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" + $"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" +
$"Played: {TimeSpan.FromMilliseconds(fixedSongPositionMs)} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" + $"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_gsmtcService.CurrentSongInfo.DurationMs)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}", $"Y offset: {_canvasYScrollTransition.Value}\n" +
new Vector2(10, 40), Colors.Red); $"User scroll offset: {_mouseYScrollTransition.Value}",
new Vector2(0, 0), Colors.Red);
#endif #endif
} }
private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args) private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
{ {
var lyricsBg = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings; if (_lyricsWindowStatus == null) return;
var lyricsEffect = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
var albumArtThemeColors = _mediaSessionsService.AlbumArtThemeColors; var lyricsBg = _lyricsWindowStatus.LyricsBackgroundSettings;
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
var lyricsData = _gsmtcService.CurrentLyricsData;
TimeSpan elapsedTime = args.Timing.ElapsedTime; TimeSpan elapsedTime = args.Timing.ElapsedTime;
@@ -342,7 +477,7 @@ namespace BetterLyrics.WinUI3.Controls
#region UpdatePlayingLineIndex #region UpdatePlayingLineIndex
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPosition.TotalMilliseconds, _lyricsData); int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, _renderLyricsLines);
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex; bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
_playingLineIndex = newPlayingIndex; _playingLineIndex = newPlayingIndex;
@@ -352,7 +487,7 @@ namespace BetterLyrics.WinUI3.Controls
if (isPlayingLineChanged || _isLayoutChanged) if (isPlayingLineChanged || _isLayoutChanged)
{ {
var targetScroll = _layoutManager.CalculateTargetScrollOffset(_lyricsData, _playingLineIndex); var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _playingLineIndex);
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value; if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType); _canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
@@ -363,50 +498,78 @@ namespace BetterLyrics.WinUI3.Controls
#endregion #endregion
_visibleRange = _layoutManager.CalculateVisibleRange( _mouseYScrollTransition.Update(elapsedTime);
_lyricsData?.LyricsLines,
_canvasYScrollTransition.Value, // 当前滚动位置 _mouseHoverLineIndex = LyricsLayoutManager.FindMouseHoverLineIndex(
_renderLyricsLines,
_isMouseInLyricsArea,
_mousePosition,
_canvasYScrollTransition.Value + _mouseYScrollTransition.Value,
_renderLyricsStartY, _renderLyricsStartY,
_renderLyricsHeight, _renderLyricsHeight,
sender.Size.Height lyricsStyle.PlayingLineTopOffset / 100.0
); );
_animator.UpdateVisibleLines( _visibleRange = LyricsLayoutManager.CalculateVisibleRange(
_lyricsData, _renderLyricsLines,
_visibleRange.Start, _canvasYScrollTransition.Value + _mouseYScrollTransition.Value, // 当前滚动位置
_visibleRange.End, _renderLyricsStartY,
_renderLyricsHeight,
sender.Size.Height,
lyricsStyle.PlayingLineTopOffset / 100.0
);
var maxRange = LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines);
_animator.UpdateLines(
_renderLyricsLines,
_isMouseScrolling ? maxRange.Start : _visibleRange.Start,
_isMouseScrolling ? maxRange.End : _visibleRange.End,
_playingLineIndex, _playingLineIndex,
sender.Size.Height, sender.Size.Height,
_canvasTargetScrollOffset, _canvasTargetScrollOffset,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings, lyricsStyle.PlayingLineTopOffset / 100.0,
_lyricsWindowStatus.LyricsStyleSettings,
_lyricsWindowStatus.LyricsEffectSettings,
_canvasYScrollTransition, _canvasYScrollTransition,
albumArtThemeColors.BgFontColor, _albumArtThemeColors.BgFontColor,
albumArtThemeColors.FgFontColor, _albumArtThemeColors.FgFontColor,
elapsedTime, elapsedTime,
_isMouseScrolling,
_isLayoutChanged, _isLayoutChanged,
isPlayingLineChanged isPlayingLineChanged,
_isMouseScrollingChanged,
_songPositionWithOffset.TotalMilliseconds
); );
_isMouseScrollingChanged = false;
_lyricsRenderer.CalculateLyrics3DMatrix( _lyricsRenderer.CalculateLyrics3DMatrix(
lyricsStyle: lyricsStyle,
lyricsEffect: lyricsEffect, lyricsEffect: lyricsEffect,
lyricsX: _renderLyricsStartX, lyricsX: _renderLyricsStartX,
lyricsY: _renderLyricsStartY, lyricsY: _renderLyricsStartY,
lyricsWidth: _renderLyricsWidth, lyricsWidth: _renderLyricsWidth,
canvasHeight: sender.Size.Height lyricsHeight: _renderLyricsHeight
); );
_isLayoutChanged = false; _isLayoutChanged = false;
if (_fluidRenderer.IsEnabled) _fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
{ _fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity / 100.0;
_fluidRenderer.UpdateColors( _fluidRenderer.UpdateColors(
_accentColor1Transition.Value, _accentColor1Transition.Value,
_accentColor2Transition.Value, _accentColor2Transition.Value,
_accentColor3Transition.Value, _accentColor3Transition.Value,
_accentColor4Transition.Value _accentColor4Transition.Value
); );
_fluidRenderer.Update(elapsedTime); _fluidRenderer.Update(elapsedTime);
}
_coverRenderer.IsEnabled = lyricsBg.IsCoverOverlayEnabled;
_coverRenderer.Opacity = lyricsBg.CoverOverlayOpacity;
_coverRenderer.BlurAmount = lyricsBg.CoverOverlayBlurAmount;
_coverRenderer.Speed = lyricsBg.CoverOverlaySpeed;
_coverRenderer.Update(elapsedTime);
_snowRenderer.IsEnabled = lyricsBg.IsSnowFlakeOverlayEnabled; _snowRenderer.IsEnabled = lyricsBg.IsSnowFlakeOverlayEnabled;
_snowRenderer.Amount = lyricsBg.SnowFlakeOverlayAmount / 100f; _snowRenderer.Amount = lyricsBg.SnowFlakeOverlayAmount / 100f;
@@ -433,19 +596,29 @@ namespace BetterLyrics.WinUI3.Controls
private void Canvas_Unloaded(object sender, RoutedEventArgs e) private void Canvas_Unloaded(object sender, RoutedEventArgs e)
{ {
Canvas.RemoveFromVisualTree();
Canvas = null;
_fluidRenderer.Dispose(); _fluidRenderer.Dispose();
_coverRenderer.Dispose();
_snowRenderer.Dispose(); _snowRenderer.Dispose();
_fogRenderer.Dispose(); _fogRenderer.Dispose();
_spectrumRenderer.Dispose(); _spectrumRenderer.Dispose();
_renderLyricsLines = null;
DisposeAnalyzer(); DisposeAnalyzer();
Canvas.RemoveFromVisualTree();
Canvas = null;
} }
private async void Canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args) private async void Canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{ {
args.TrackAsyncAction(_fluidRenderer.LoadResourcesAsync().AsAsyncAction()); var tasks = new Task[]
{
_fluidRenderer.LoadResourcesAsync(),
ReloadCoverBackgroundResourcesAsync()
};
args.TrackAsyncAction(Task.WhenAll(tasks).AsAsyncAction());
_snowRenderer.LoadResources(); _snowRenderer.LoadResources();
_fogRenderer.LoadResources(); _fogRenderer.LoadResources();
@@ -466,12 +639,12 @@ namespace BetterLyrics.WinUI3.Controls
private void TriggerRelayout() private void TriggerRelayout()
{ {
if (_layoutManager == null || _lyricsData == null || !_isLayoutChanged) return; if (_renderLyricsLines == null || !_isLayoutChanged || _lyricsWindowStatus == null) return;
_layoutManager.MeasureAndArrange( LyricsLayoutManager.MeasureAndArrange(
resourceCreator: Canvas, resourceCreator: Canvas,
lyricsData: _lyricsData, lines: _renderLyricsLines,
status: _liveStatesService.LiveStates.LyricsWindowStatus, status: _lyricsWindowStatus,
appSettings: _settingsService.AppSettings, appSettings: _settingsService.AppSettings,
canvasWidth: Canvas.Size.Width, canvasWidth: Canvas.Size.Width,
canvasHeight: Canvas.Size.Height, canvasHeight: Canvas.Size.Height,
@@ -482,88 +655,62 @@ namespace BetterLyrics.WinUI3.Controls
private void UpdatePlaybackState(TimeSpan elapsedTime) private void UpdatePlaybackState(TimeSpan elapsedTime)
{ {
if (_mediaSessionsService.CurrentIsPlaying) if (_gsmtcService.CurrentIsPlaying)
{ {
_songPosition += elapsedTime; _songPosition += elapsedTime;
_totalPlayedTime += elapsedTime; _songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_gsmtcService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
CheckAndScrobbleLastFM();
}
}
private void CheckAndScrobbleLastFM()
{
bool isEnabled = _mediaSessionsService.CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
if (!isEnabled || _isLastFMTracked) return;
var songInfo = _mediaSessionsService.CurrentSongInfo;
if (songInfo == null || songInfo.Duration <= 0) return;
if (_totalPlayedTime.TotalSeconds >= songInfo.Duration * 0.5)
{
_isLastFMTracked = true;
_lastFMService.TrackAsync(songInfo);
} }
} }
private void ResetPlaybackState() private void ResetPlaybackState()
{ {
_totalPlayedTime = TimeSpan.Zero; _songPosition = TimeSpan.Zero;
_totalPlayedTime = TimeSpan.Zero;
_isLastFMTracked = false;
} }
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries() private void UpdateRenderLyricsLines()
{ {
if (_mediaSessionsService.CurrentSongInfo == null _renderLyricsLines = null;
|| _lyricsData == null var lines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine()
|| _lyricsData.LyricsLines.Count == 0)
{ {
return new Tuple<int, int>(-1, -1); LyricsSyllables = x.LyricsSyllables,
StartMs = x.StartMs,
EndMs = x.EndMs,
PhoneticText = x.PhoneticText,
OriginalText = x.OriginalText,
TranslatedText = x.TranslatedText
}).ToList();
if (lines != null)
{
LyricsLayoutManager.CalculateLanes(lines);
} }
_renderLyricsLines = lines;
return new Tuple<int, int>(0, _lyricsData.LyricsLines.Count - 1);
} }
public void Receive(PropertyChangedMessage<AlbumArtThemeColors> message) private async Task ReloadCoverBackgroundResourcesAsync()
{ {
if (message.Sender is IMediaSessionsService) if (_gsmtcService.AlbumArtBitmapStream is IRandomAccessStream stream)
{ {
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtThemeColors)) stream.Seek(0);
{ CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(Canvas, stream);
var lyricsThemeColors = message.NewValue; _coverRenderer.SetCoverBitmap(bitmap);
_immersiveBgColorTransition.StartTransition(lyricsThemeColors.EnvColor);
_accentColor1Transition.StartTransition(lyricsThemeColors.AccentColor1);
_accentColor2Transition.StartTransition(lyricsThemeColors.AccentColor2);
_accentColor3Transition.StartTransition(lyricsThemeColors.AccentColor3);
_accentColor4Transition.StartTransition(lyricsThemeColors.AccentColor4);
_isLayoutChanged = true;
}
} }
} }
public void Receive(PropertyChangedMessage<TimeSpan> message) public void Receive(PropertyChangedMessage<TimeSpan> message)
{ {
if (message.Sender is IMediaSessionsService) if (message.Sender is IGSMTCService)
{ {
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition)) if (message.PropertyName == nameof(IGSMTCService.CurrentPosition))
{ {
var realPosition = message.NewValue; var realPosition = message.NewValue;
var diff = Math.Abs(_songPosition.TotalMilliseconds - realPosition.TotalMilliseconds); var diff = Math.Abs(_songPosition.TotalMilliseconds - realPosition.TotalMilliseconds);
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0; var timelineSyncThreshold = _gsmtcService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
// 偏差 or seek // 偏差 or seek
if (diff >= timelineSyncThreshold) if (diff >= timelineSyncThreshold)
{ {
_songPosition = realPosition; _songPosition = realPosition;
// 如果跳回了开头,重置 LastFM 统计状态
if (_songPosition.TotalSeconds <= 1)
{
_totalPlayedTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
} }
// 拖动进度条等大跨度 // 拖动进度条等大跨度
@@ -577,30 +724,30 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<LyricsData?> message) public void Receive(PropertyChangedMessage<LyricsData?> message)
{ {
if (message.Sender is IMediaSessionsService) if (message.Sender is IGSMTCService)
{ {
if (message.PropertyName == nameof(IMediaSessionsService.CurrentLyricsData)) if (message.PropertyName == nameof(IGSMTCService.CurrentLyricsData))
{ {
_lyricsData = message.NewValue; UpdateRenderLyricsLines();
_isLayoutChanged = true; _isLayoutChanged = true;
} }
} }
} }
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message) public void Receive(PropertyChangedMessage<SongInfo> message)
{ {
if (message.Sender is LiveStates) if (message.Sender is IGSMTCService)
{ {
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus)) if (message.PropertyName == nameof(IGSMTCService.CurrentSongInfo))
{ {
_isLayoutChanged = true; ResetPlaybackState();
} }
} }
} }
public void Receive(PropertyChangedMessage<int> message) public void Receive(PropertyChangedMessage<int> message)
{ {
if (message.Sender is LyricsStyleSettings) if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{ {
if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsFontSize)) if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsFontSize))
{ {
@@ -618,8 +765,24 @@ namespace BetterLyrics.WinUI3.Controls
{ {
_isLayoutChanged = true; _isLayoutChanged = true;
} }
else if (message.PropertyName == nameof(LyricsStyleSettings.PlayingLineTopOffset))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsOpacity))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.OriginalLyricsOpacity))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.TranslatedLyricsOpacity))
{
_isLayoutChanged = true;
}
} }
else if (message.Sender is LyricsEffectSettings) else if (message.Sender == LyricsWindowStatus?.LyricsEffectSettings)
{ {
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollDuration)) if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollDuration))
{ {
@@ -650,7 +813,7 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<double> message) public void Receive(PropertyChangedMessage<double> message)
{ {
if (message.Sender is LyricsStyleSettings) if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{ {
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsLineSpacingFactor)) if (message.PropertyName == nameof(LyricsStyleSettings.LyricsLineSpacingFactor))
{ {
@@ -661,14 +824,26 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<bool> message) public void Receive(PropertyChangedMessage<bool> message)
{ {
if (message.Sender is LyricsEffectSettings) if (message.Sender == LyricsWindowStatus?.LyricsEffectSettings)
{ {
if (message.PropertyName == nameof(LyricsEffectSettings.IsFanLyricsEnabled)) if (message.PropertyName == nameof(LyricsEffectSettings.IsFanLyricsEnabled))
{ {
_isLayoutChanged = true; _isLayoutChanged = true;
} }
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsBlurEffectEnabled))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsFadeOutEffectEnabled))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsOutOfSightEffectEnabled))
{
_isLayoutChanged = true;
}
} }
else if (message.Sender is LyricsStyleSettings) else if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{ {
if (message.PropertyName == nameof(LyricsStyleSettings.IsDynamicLyricsFontSize)) if (message.PropertyName == nameof(LyricsStyleSettings.IsDynamicLyricsFontSize))
{ {
@@ -679,7 +854,7 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<TextAlignmentType> message) public void Receive(PropertyChangedMessage<TextAlignmentType> message)
{ {
if (message.Sender is LyricsStyleSettings) if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{ {
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsAlignmentType)) if (message.PropertyName == nameof(LyricsStyleSettings.LyricsAlignmentType))
{ {
@@ -688,13 +863,39 @@ namespace BetterLyrics.WinUI3.Controls
} }
} }
public void Receive(PropertyChangedMessage<SongInfo?> message) public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{ {
if (message.Sender is IMediaSessionsService) if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{ {
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo)) if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontWeight))
{ {
ResetPlaybackState(); _isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<IRandomAccessStream?> message)
{
if (message.Sender is IGSMTCService)
{
if (message.PropertyName == nameof(IGSMTCService.AlbumArtBitmapStream))
{
_ = ReloadCoverBackgroundResourcesAsync();
} }
} }
} }

View File

@@ -23,20 +23,93 @@
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Effect" /> Text="Effect" />
<!-- 辉光效果 --> <!-- 模糊效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE9A9;}"> <dev:SettingsCard x:Uid="SettingsPageLyricsBlurEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsBlurEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<!-- 淡出效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsFadeOutEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE89F;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFadeOutEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- 远离视野 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsOutOfSightEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF19D;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsOutOfSightEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- 辉光效果 -->
<dev:SettingsExpander x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE9A9;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageScope">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEffectScopeLongDurationSyllable" />
<ComboBoxItem x:Uid="SettingsPageEffectLineStartToCurrentChar" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLongSyllableDuration">
<local:ExtendedSlider
Default="700"
Maximum="1000"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsGlowEffectLongSyllableDuration, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectAmountAutoAdjust, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectAmountAutoAdjust, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Maximum="16"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsGlowEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 缩放效果 --> <!-- 缩放效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsScaleEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8A3;}"> <dev:SettingsExpander x:Uid="SettingsPageLyricsScaleEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8A3;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectEnabled, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard> <dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageLongSyllableDuration">
<local:ExtendedSlider
Default="700"
Maximum="1000"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScaleEffectLongSyllableDuration, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectAmountAutoAdjust, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectAmountAutoAdjust, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Default="115"
Maximum="200"
Minimum="100"
Unit="%"
Value="{x:Bind LyricsEffectSettings.LyricsScaleEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 浮动动画 --> <!-- 浮动动画 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}"> <dev:SettingsExpander x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" />
</dev:SettingsCard> <dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationAmountAutoAdjust, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationAmountAutoAdjust, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Default="4"
Maximum="16"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsFloatAnimationAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 扇形歌词 --> <!-- 扇形歌词 -->
<dev:SettingsExpander <dev:SettingsExpander

View File

@@ -25,7 +25,7 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Column="0"> <Grid Grid.Column="0">
<ScrollViewer> <ScrollViewer Padding="8,0">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="LyricsSearchControlSongInfoMapping" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="LyricsSearchControlSongInfoMapping" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
@@ -97,27 +97,6 @@
Style="{StaticResource GhostButtonStyle}" /> Style="{StaticResource GhostButtonStyle}" />
</Grid> </Grid>
<RichTextBlock
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap">
<Paragraph>
<Run Text="*" />
<Run x:Uid="ArtistsSplitHint" />
</Paragraph>
</RichTextBlock>
<RichTextBlock
FontSize="12"
FontWeight="Bold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Loaded="ArtistsSplitHintRichTextBlock_Loaded"
TextWrapping="Wrap">
<Paragraph>
<Run Text="; , / " />
</Paragraph>
</RichTextBlock>
</StackPanel> </StackPanel>
</Grid> </Grid>
@@ -161,11 +140,14 @@
<CheckBox x:Uid="LyricsSearchControlMarkAsPureMusic" IsChecked="{x:Bind ViewModel.MappedSongSearchQuery.IsMarkedAsPureMusic, Mode=TwoWay}" /> <CheckBox x:Uid="LyricsSearchControlMarkAsPureMusic" IsChecked="{x:Bind ViewModel.MappedSongSearchQuery.IsMarkedAsPureMusic, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="LyricsSearchControlTargetSearchProvider"> <Button
<Button x:Uid="LyricsSearchControlSearch"
x:Uid="LyricsSearchControlSearch" HorizontalAlignment="Stretch"
Command="{x:Bind ViewModel.SearchCommand}" Command="{x:Bind ViewModel.SearchCommand}"
Style="{StaticResource AccentButtonStyle}" /> Style="{StaticResource AccentButtonStyle}" />
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreCacheWhenSearching, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
</StackPanel> </StackPanel>
@@ -174,7 +156,7 @@
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}"> <ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchResult"> <DataTemplate x:DataType="models:LyricsCacheItem">
<ListViewItem IsEnabled="{x:Bind IsFound}"> <ListViewItem IsEnabled="{x:Bind IsFound}">
<StackPanel Padding="0,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}"> <StackPanel Padding="0,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
<local:PropertyRow <local:PropertyRow
@@ -185,20 +167,13 @@
<!-- Lyrics search result --> <!-- Lyrics search result -->
<StackPanel Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}"> <StackPanel Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind Title, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind Title, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind Artist, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind Album, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind Album, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow <local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
x:Uid="LyricsSearchControlDurauion"
Unit="s"
Value="{x:Bind Duration, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow <local:PropertyRow
x:Uid="LyricsPageMatchPercentage" x:Uid="LyricsPageMatchPercentage"
Unit="%" Unit="%"
Value="{x:Bind MatchPercentage, Mode=OneWay}" /> Value="{x:Bind MatchPercentage, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsPageCachePath"
Link="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel> </StackPanel>
<!-- NOT FOUND --> <!-- NOT FOUND -->
<TextBlock <TextBlock
@@ -248,8 +223,6 @@
<ProgressBar <ProgressBar
VerticalAlignment="Top" VerticalAlignment="Top"
IsIndeterminate="True" IsIndeterminate="True"
ShowError="False"
ShowPaused="False"
Visibility="{x:Bind ViewModel.IsSearching, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" /> Visibility="{x:Bind ViewModel.IsSearching, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</Grid> </Grid>
<Grid Grid.Column="2"> <Grid Grid.Column="2">
@@ -282,18 +255,43 @@
</Pivot.HeaderTemplate> </Pivot.HeaderTemplate>
<Pivot.ItemTemplate> <Pivot.ItemTemplate>
<DataTemplate x:DataType="models:LyricsData"> <DataTemplate x:DataType="models:LyricsData">
<ListView ItemsSource="{x:Bind LyricsLines, Mode=OneWay}" SelectionChanged="ListView_SelectionChanged"> <ListView
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
ItemsSource="{x:Bind LyricsLines, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsLine"> <DataTemplate x:DataType="models:LyricsLine">
<StackPanel Orientation="Horizontal"> <Grid Margin="0,6" ColumnSpacing="6">
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{x:Bind StartMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" /> <Grid.ColumnDefinitions>
<TextBlock <ColumnDefinition Width="Auto" />
Margin="1,0" <ColumnDefinition Width="*" />
Foreground="{ThemeResource SystemFillColorNeutralBrush}" </Grid.ColumnDefinitions>
Text="-" /> <Grid Grid.Column="0">
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{x:Bind EndMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" /> <TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{x:Bind StartMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock Margin="6,0" Text="{x:Bind OriginalText, Mode=OneWay}" /> <Button
</StackPanel> HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="PlayLyricsLineButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE768;}"
Opacity="0"
Style="{StaticResource AccentButtonStyle}">
<Button.OpacityTransition>
<ScalarTransition />
</Button.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
</Grid>
<local:PropertyRow Grid.Column="1" Value="{x:Bind OriginalText, Mode=OneWay}" />
</Grid>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
@@ -325,8 +323,8 @@
</Grid> </Grid>
<Grid Grid.Row="1" ColumnSpacing="6"> <Grid Grid.Row="1" ColumnSpacing="6">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>

View File

@@ -2,8 +2,6 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info. // and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -20,30 +18,10 @@ namespace BetterLyrics.WinUI3.Controls
DataContext = Ioc.Default.GetRequiredService<LyricsSearchControlViewModel>(); DataContext = Ioc.Default.GetRequiredService<LyricsSearchControlViewModel>();
} }
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e) private void PlayLyricsLineButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{ {
ViewModel.SelectedLyricsLine = e.OriginalSource as LyricsLine; var lyricsLine = (LyricsLine)((Button)sender).DataContext;
} ViewModel.PlayLyricsLine(lyricsLine);
private void ArtistsSplitHintRichTextBlock_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (sender is RichTextBlock richTextBlock)
{
TextHighlighter highlighter = new()
{
Background = App.Current.Resources["AccentTextFillColorPrimaryBrush"] as SolidColorBrush,
Ranges =
{
new() { StartIndex = 0, Length = 1 },
new() { StartIndex = 5, Length = 1 },
new() { StartIndex = 10, Length = 1 },
new() { StartIndex = 15, Length = 1 },
new() { StartIndex = 20, Length = 1 },
new() { StartIndex = 25, Length = 1 },
}
};
richTextBlock.TextHighlighters.Add(highlighter);
}
} }
} }
} }

View File

@@ -29,6 +29,26 @@
</ComboBox> </ComboBox>
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsCenterTopOffset" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE78A;}">
<local:ExtendedSlider
Default="50"
Maximum="99"
Minimum="1"
Unit="%"
Value="{x:Bind LyricsStyleSettings.PlayingLineTopOffset, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsLineSpacingFactorSlider"
Default="0.5"
Frequency="0.1"
Maximum="2"
Minimum="0"
Unit="x"
Value="{x:Bind LyricsStyleSettings.LyricsLineSpacingFactor, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander x:Uid="SettingsPageLyricsFontFamily" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D2;}"> <dev:SettingsExpander x:Uid="SettingsPageLyricsFontFamily" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D2;}">
<dev:SettingsExpander.Items> <dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageCJK"> <dev:SettingsCard x:Uid="SettingsPageCJK">
@@ -207,16 +227,35 @@
</dev:SettingsExpander.Items> </dev:SettingsExpander.Items>
</dev:SettingsExpander> </dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}"> <!-- 字体不透明度 -->
<local:ExtendedSlider <dev:SettingsExpander x:Uid="SettingsPageLyricsOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEF20;}">
x:Uid="SettingsPageLyricsLineSpacingFactorSlider" <dev:SettingsExpander.Items>
Default="0.5" <dev:SettingsCard x:Uid="SettingsPagePhoneticText">
Frequency="0.1" <local:ExtendedSlider
Maximum="2" Default="60"
Minimum="0" Frequency="1"
Unit="x" Maximum="100"
Value="{x:Bind LyricsStyleSettings.LyricsLineSpacingFactor, Mode=TwoWay}" /> Minimum="0"
</dev:SettingsCard> Value="{x:Bind LyricsStyleSettings.PhoneticLyricsOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageOriginalText">
<local:ExtendedSlider
Default="30"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.OriginalLyricsOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageTranslatedText">
<local:ExtendedSlider
Default="60"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.TranslatedLyricsOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@@ -11,6 +11,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls" xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="UserControl_Loaded"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid> <Grid>
@@ -21,24 +22,7 @@
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<Grid> <TextBlock x:Uid="SettingsPageRecordedWindowStatus" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="SettingsPageRecordedWindowStatus"
Grid.Column="0"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Button
Grid.Column="2"
Margin="0,30,0,0"
Command="{x:Bind ViewModel.OpenConfigPanelCommand}"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig" />
</Button>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="3"> <StackPanel Orientation="Horizontal" Spacing="3">
@@ -54,6 +38,7 @@
<MenuFlyoutItem x:Uid="SettingsPageDockedMode" Command="{x:Bind ViewModel.CreateDockedLyricsWindowStatusCommand}" /> <MenuFlyoutItem x:Uid="SettingsPageDockedMode" Command="{x:Bind ViewModel.CreateDockedLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageFullscreenMode" Command="{x:Bind ViewModel.CreateFullLyricsWindowStatusCommand}" /> <MenuFlyoutItem x:Uid="SettingsPageFullscreenMode" Command="{x:Bind ViewModel.CreateFullLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageNarrowMode" Command="{x:Bind ViewModel.CreateNarrowLyricsWindowStatusCommand}" /> <MenuFlyoutItem x:Uid="SettingsPageNarrowMode" Command="{x:Bind ViewModel.CreateNarrowLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageTaskbarMode" Command="{x:Bind ViewModel.CreateTaskbarLyricsWindowStatusCommand}" />
</MenuFlyout> </MenuFlyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
@@ -87,7 +72,7 @@
Padding="0,12" Padding="0,12"
CornerRadius="4" CornerRadius="4"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}"> SelectionMode="None">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" /> <controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
@@ -101,10 +86,6 @@
Spacing="6"> Spacing="6">
<StackPanel.ContextFlyout> <StackPanel.ContextFlyout>
<MenuBarItemFlyout> <MenuBarItemFlyout>
<MenuFlyoutItem
x:Uid="LyricsWindowSettingsControlSetDefault"
Click="SetDefaultMenuFlyoutItem_Click"
IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
<MenuFlyoutItem x:Uid="SettingsPageCreateFromCurrent" Click="CopyMenuFlyoutItem_Click" /> <MenuFlyoutItem x:Uid="SettingsPageCreateFromCurrent" Click="CopyMenuFlyoutItem_Click" />
<MenuFlyoutItem x:Uid="LyricsWindowSettingsControlShare" Click="ShareMenuFlyoutItem_Click" /> <MenuFlyoutItem x:Uid="LyricsWindowSettingsControlShare" Click="ShareMenuFlyoutItem_Click" />
<MenuFlyoutItem <MenuFlyoutItem
@@ -113,13 +94,50 @@
IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" /> IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
</MenuBarItemFlyout> </MenuBarItemFlyout>
</StackPanel.ContextFlyout> </StackPanel.ContextFlyout>
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" /> <Grid>
<Border
BorderBrush="{ThemeResource AccentAAFillColorDefaultBrush}"
BorderThickness="4"
CornerRadius="4"
Visibility="{Binding IsOpened, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<uc:DemoWindowGrid Margin="4" LyricsWindowStatus="{Binding}" />
</Grid>
<Grid>
<ToggleButton
Grid.Column="0"
HorizontalAlignment="Stretch"
Click="SetDefaultMenuFlyoutItem_Click"
IsChecked="{Binding IsDefault, Mode=OneWay}">
<TextBlock x:Uid="LyricsWindowSettingsControlSetDefault" />
</ToggleButton>
</Grid>
<Grid ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
HorizontalAlignment="Stretch"
Click="ConfigButton_Click">
<TextBlock x:Uid="LyricsWindowSettingsControlLyricsWindowConfig" />
</Button>
</Grid>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
</StackPanel> </StackPanel>
<dev:SettingsCard x:Uid="SettingsPageMultiNowPlayingWindows">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.MultiNowPlayingWindowMode, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMusicGalleryLyrics">
<Button HorizontalAlignment="Stretch" Click="EmbeddedConfigButton_Click">
<TextBlock x:Uid="LyricsWindowSettingsControlLyricsWindowConfig" />
</Button>
</dev:SettingsCard>
</StackPanel> </StackPanel>
</Grid> </Grid>
@@ -137,8 +155,15 @@
<Grid.TranslationTransition> <Grid.TranslationTransition>
<Vector3Transition /> <Vector3Transition />
</Grid.TranslationTransition> </Grid.TranslationTransition>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Padding="36,0" Style="{StaticResource SettingsGridStyle}"> <Grid
Grid.Row="0"
Padding="36,0"
Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -147,9 +172,9 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig"
Grid.Column="0" Grid.Column="0"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="{x:Bind LyricsWindowStatus.Name, Mode=OneWay}" />
<Button <Button
Grid.Column="2" Grid.Column="2"
Margin="0,30,0,0" Margin="0,30,0,0"
@@ -160,68 +185,61 @@
Style="{StaticResource AccentButtonStyle}" /> Style="{StaticResource AccentButtonStyle}" />
</Grid> </Grid>
<Pivot SelectionChanged="Pivot_SelectionChanged"> <controls:Segmented
x:Name="ConfigSegmented"
HorizontalAlignment="Stretch"
SelectionChanged="ConfigSegmented_SelectionChanged"
Style="{StaticResource PivotSegmentedStyle}">
<PivotItem Tag="General"> <controls:SegmentedItem x:Name="WindowSegmentedItem" Tag="Window">
<PivotItem.Header> <TextBlock
<TextBlock x:Uid="AppSettingsControlGeneral"
x:Uid="AppSettingsControlGeneral" MaxWidth="120"
VerticalAlignment="Center" TextWrapping="Wrap" />
Style="{StaticResource BodyTextBlockStyle}" /> </controls:SegmentedItem>
</PivotItem.Header> <controls:SegmentedItem x:Name="LayoutSegmentedItem" Tag="Layout">
</PivotItem> <TextBlock
x:Uid="SettingsPageLayout"
MaxWidth="120"
TextWrapping="Wrap" />
</controls:SegmentedItem>
<controls:SegmentedItem x:Name="AlbumArtStyleSegmentedItem" Tag="AlbumArtStyle">
<TextBlock
x:Uid="SettingsPageAlbumStyle"
MaxWidth="120"
TextWrapping="Wrap" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="AlbumArtEffect">
<TextBlock
x:Uid="SettingsPageAlbumEffect"
MaxWidth="120"
TextWrapping="Wrap" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="LyricsStyle">
<TextBlock
x:Uid="SettingsPageLyricsStyle"
MaxWidth="120"
TextWrapping="Wrap" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="LyricsEffect">
<TextBlock
x:Uid="SettingsPageLyricsEffect"
MaxWidth="120"
TextWrapping="Wrap" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="LyricsBackground">
<TextBlock
x:Uid="SettingsPageBackgroundOverlay"
MaxWidth="120"
TextWrapping="Wrap" />
</controls:SegmentedItem>
<PivotItem Tag="Layout"> </controls:Segmented>
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLayout"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="AlbumArtStyle">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageAlbumStyle"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsStyle">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLyricsStyle"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsEffect">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLyricsEffect"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsBackground">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageBackgroundOverlay"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
</Pivot>
</StackPanel> </StackPanel>
</Grid> </Grid>
<controls:SwitchPresenter Margin="0,110,0,0" Value="{x:Bind ViewModel.ListViewSelectedItemTag, Mode=OneWay}"> <controls:SwitchPresenter Grid.Row="1" Value="{x:Bind ViewModel.SelectorBarSelectedItemTag, Mode=OneWay}">
<controls:SwitchPresenter.ContentTransitions> <controls:SwitchPresenter.ContentTransitions>
<TransitionCollection> <TransitionCollection>
<PopupThemeTransition /> <PopupThemeTransition />
@@ -229,155 +247,8 @@
</controls:SwitchPresenter.ContentTransitions> </controls:SwitchPresenter.ContentTransitions>
<!-- Window --> <!-- Window -->
<controls:Case Value="General"> <controls:Case Value="Window">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}"> <uc:WindowSettingsControl LyricsWindowStatus="{x:Bind LyricsWindowStatus, Mode=OneWay}" />
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageConfigName" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8AC;}">
<StackPanel
Margin="0,6,0,0"
Orientation="Horizontal"
Spacing="6">
<TextBox Text="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.Name, Mode=TwoWay}" TextWrapping="Wrap" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageWorkArea"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE78B;}"
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageWorkAreaHeight" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<uc:ExtendedSlider
Default="64"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.DockHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockPlacement" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockMonitor" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorDeviceName, Mode=TwoWay}" />
<Button
Command="{x:Bind ViewModel.RefreshMonitorDeviceNamesCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAdaptEnvColor"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE88F;}"
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard
x:Uid="SettingsPageEnvColorSample"
Header="Environment color sample mode"
IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageWindowBounds"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF16B;}"
IsExpanded="True">
<dev:SettingsExpander.Items>
<dev:SettingsCard Header="X">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowX, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard Header="Y">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowY, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageWidth">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowWidth, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageHeight">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowHeight, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAOT"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE718;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTop, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageForceAlwaysOnTop" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTop, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTopPolling, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowInSwitchers" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C4;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsShownInSwitchers, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageClickThrough" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C9;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsClickThrough, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBorderless" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B2;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsBorderless, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB41;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaTop" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaWhole" />
</ComboBox>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</controls:Case> </controls:Case>
<!-- Layout --> <!-- Layout -->
@@ -386,17 +257,17 @@
<Grid Style="{StaticResource SettingsGridStyle}"> <Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="SettingsPageLayout" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsExpander x:Uid="SettingsPageDisplayTypeSwitcher" IsExpanded="True"> <dev:SettingsExpander x:Uid="SettingsPageDisplayTypeSwitcher" IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}"> <ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" /> <ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" /> <ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" /> <ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox> </ComboBox>
<dev:SettingsExpander.Items> <dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageLayoutOrientation"> <dev:SettingsCard x:Uid="SettingsPageLayoutOrientation">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}"> <ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationHorizontal" /> <ComboBoxItem x:Uid="SettingsPageLayoutOrientationHorizontal" />
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationVertical" /> <ComboBoxItem x:Uid="SettingsPageLayoutOrientationVertical" />
</ComboBox> </ComboBox>
@@ -411,22 +282,27 @@
<!-- Album art area style --> <!-- Album art area style -->
<controls:Case Value="AlbumArtStyle"> <controls:Case Value="AlbumArtStyle">
<uc:AlbumArtLayoutSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" /> <uc:AlbumArtAreaStyleSettingsControl AlbumArtLayoutSettings="{x:Bind LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
</controls:Case>
<!-- Album art area effect -->
<controls:Case Value="AlbumArtEffect">
<uc:AlbumArtAreaEffectSettingsControl AlbumArtAreaEffectSettings="{x:Bind LyricsWindowStatus.AlbumArtAreaEffectSettings, Mode=OneWay}" />
</controls:Case> </controls:Case>
<!-- Lyrics style --> <!-- Lyrics style -->
<controls:Case Value="LyricsStyle"> <controls:Case Value="LyricsStyle">
<uc:LyricsStyleSettingsControl LyricsStyleSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" /> <uc:LyricsStyleSettingsControl LyricsStyleSettings="{x:Bind LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" />
</controls:Case> </controls:Case>
<!-- Lyrics effect --> <!-- Lyrics effect -->
<controls:Case Value="LyricsEffect"> <controls:Case Value="LyricsEffect">
<uc:LyricsEffectSettingsControl LyricsEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" /> <uc:LyricsEffectSettingsControl LyricsEffectSettings="{x:Bind LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" />
</controls:Case> </controls:Case>
<!-- Lyrics background --> <!-- Lyrics background -->
<controls:Case Value="LyricsBackground"> <controls:Case Value="LyricsBackground">
<uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" /> <uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" />
</controls:Case> </controls:Case>
</controls:SwitchPresenter> </controls:SwitchPresenter>

View File

@@ -1,15 +1,15 @@
using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization; using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.SettingsService; using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views; using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Input;
using NTextCat.Commons;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -25,7 +25,15 @@ namespace BetterLyrics.WinUI3.Controls
public LyricsWindowSettingsControlViewModel ViewModel => (LyricsWindowSettingsControlViewModel)DataContext; public LyricsWindowSettingsControlViewModel ViewModel => (LyricsWindowSettingsControlViewModel)DataContext;
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>(); private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
public LyricsWindowStatus? LyricsWindowStatus
{
get { return (LyricsWindowStatus?)GetValue(LyricsWindowStatusProperty); }
set { SetValue(LyricsWindowStatusProperty, value); }
}
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(LyricsWindowSettingsControl), new PropertyMetadata(null));
public LyricsWindowSettingsControl() public LyricsWindowSettingsControl()
{ {
@@ -39,10 +47,9 @@ namespace BetterLyrics.WinUI3.Controls
{ {
if (menuFlyoutItem.DataContext is LyricsWindowStatus data) if (menuFlyoutItem.DataContext is LyricsWindowStatus data)
{ {
if (_liveStatesService.LiveStates.LyricsWindowStatus == data) var windows = WindowHook.GetWindows<NowPlayingWindow>();
{ var window = windows.FirstOrDefault(x => x.LyricsWindowStatus == data);
_liveStatesService.LiveStates.LyricsWindowStatus = ViewModel.AppSettings.WindowBoundsRecords.First(); window?.CloseWindow();
}
ViewModel.AppSettings.WindowBoundsRecords.Remove(data); ViewModel.AppSettings.WindowBoundsRecords.Remove(data);
} }
} }
@@ -50,12 +57,11 @@ namespace BetterLyrics.WinUI3.Controls
private void SetDefaultMenuFlyoutItem_Click(object sender, RoutedEventArgs e) private void SetDefaultMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{ {
if (sender is MenuFlyoutItem menuFlyoutItem) if (sender is FrameworkElement element)
{ {
if (menuFlyoutItem.DataContext is LyricsWindowStatus data) if (element.DataContext is LyricsWindowStatus data)
{ {
ViewModel.AppSettings.WindowBoundsRecords.ForEach(x => x.IsDefault = false); data.IsDefault = !data.IsDefault;
data.IsDefault = true;
} }
} }
} }
@@ -73,7 +79,7 @@ namespace BetterLyrics.WinUI3.Controls
StorageFile? file; StorageFile? file;
if (this.Parent is FlyoutPresenter) if (this.Parent is FlyoutPresenter)
{ {
file = await PickerHelper.PickSaveFileAsync<LyricsWindow>(fileTypeChoices); file = await PickerHelper.PickSaveFileAsync<NowPlayingWindow>(fileTypeChoices);
} }
else else
{ {
@@ -115,24 +121,13 @@ namespace BetterLyrics.WinUI3.Controls
} }
} }
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is Pivot pivot)
{
if (pivot.SelectedItem is PivotItem pivotItem)
{
ViewModel?.ListViewSelectedItemTag = pivotItem.Tag;
}
}
}
private async void ImportButton_Click(object sender, RoutedEventArgs e) private async void ImportButton_Click(object sender, RoutedEventArgs e)
{ {
string[] fileTypeFilter = [".json"]; string[] fileTypeFilter = [".json"];
StorageFile? file; StorageFile? file;
if (this.Parent is FlyoutPresenter) if (this.Parent is FlyoutPresenter)
{ {
file = await PickerHelper.PickSingleFileAsync<LyricsWindow>(fileTypeFilter); file = await PickerHelper.PickSingleFileAsync<NowPlayingWindow>(fileTypeFilter);
} }
else else
{ {
@@ -154,5 +149,31 @@ namespace BetterLyrics.WinUI3.Controls
{ {
ViewModel.DisplayPanelHeight = e.NewSize.Height; ViewModel.DisplayPanelHeight = e.NewSize.Height;
} }
private void ConfigButton_Click(object sender, RoutedEventArgs e)
{
WindowSegmentedItem.IsEnabled = LayoutSegmentedItem.IsEnabled = true;
ConfigSegmented.SelectedItem = WindowSegmentedItem;
LyricsWindowStatus = (LyricsWindowStatus)((Button)sender).DataContext;
ViewModel.OpenConfigPanel();
}
private void EmbeddedConfigButton_Click(object sender, RoutedEventArgs e)
{
WindowSegmentedItem.IsEnabled = LayoutSegmentedItem.IsEnabled = false;
ConfigSegmented.SelectedItem = AlbumArtStyleSegmentedItem;
LyricsWindowStatus = _settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus;
ViewModel.OpenConfigPanel();
}
private void ConfigSegmented_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.SelectorBarSelectedItemTag = (string)((SegmentedItem)((Segmented)sender).SelectedItem).Tag;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
ViewModel.CloseConfigPanelCommand.Execute(null);
}
} }
} }

View File

@@ -44,8 +44,9 @@
<ListView <ListView
Margin="48,56" Margin="48,56"
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}"> SelectionMode="None">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" /> <controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
@@ -54,12 +55,14 @@
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid <Grid
Margin="0,10" Margin="16"
Padding="5"
AllowFocusOnInteraction="True"
CornerRadius="4" CornerRadius="4"
Tapped="Grid_Tapped"> Tapped="Grid_Tapped">
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" /> <Border
BorderBrush="{ThemeResource AccentAAFillColorDefaultBrush}"
BorderThickness="4"
Visibility="{Binding IsOpened, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<uc:DemoWindowGrid Margin="4" LyricsWindowStatus="{Binding}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Hooks; using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views; using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
@@ -16,6 +17,8 @@ namespace BetterLyrics.WinUI3.Controls
{ {
public LyricsWindowSwitchControlViewModel ViewModel => (LyricsWindowSwitchControlViewModel)DataContext; public LyricsWindowSwitchControlViewModel ViewModel => (LyricsWindowSwitchControlViewModel)DataContext;
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public LyricsWindowSwitchControl() public LyricsWindowSwitchControl()
{ {
InitializeComponent(); InitializeComponent();
@@ -37,7 +40,7 @@ namespace BetterLyrics.WinUI3.Controls
var lyricsWindowSwitchWindow = WindowHook.GetWindow<LyricsWindowSwitchWindow>(); var lyricsWindowSwitchWindow = WindowHook.GetWindow<LyricsWindowSwitchWindow>();
lyricsWindowSwitchWindow?.ViewModel.RootGridOpacity = 0; lyricsWindowSwitchWindow?.ViewModel.RootGridOpacity = 0;
await Task.Delay(300); await Task.Delay(300);
WindowHook.HideWindow<LyricsWindowSwitchWindow>(); lyricsWindowSwitchWindow?.HideWindow();
} }
private void ShadowRect_Loaded(object sender, RoutedEventArgs e) private void ShadowRect_Loaded(object sender, RoutedEventArgs e)

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl <UserControl
x:Class="BetterLyrics.WinUI3.Controls.MediaSettingsControl" x:Class="BetterLyrics.WinUI3.Controls.MediaSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@@ -6,9 +5,11 @@
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI" xmlns:dev="using:DevWinUI"
xmlns:enums="using:BetterLyrics.WinUI3.Enums"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls" xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d"> mc:Ignorable="d">
@@ -49,42 +50,136 @@
ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}"
SelectionMode="None"> SelectionMode="None">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="models:MediaFolder">
<dev:SettingsExpander> <dev:SettingsExpander IsExpanded="True">
<dev:SettingsExpander.HeaderIcon>
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="{x:Bind SourceType, Converter={StaticResource FileSourceTypeToIconConverter}, Mode=OneWay}" />
</dev:SettingsExpander.HeaderIcon>
<dev:SettingsExpander.Header> <dev:SettingsExpander.Header>
<HyperlinkButton <TextBlock IsTextSelectionEnabled="True" Text="{x:Bind Name, Mode=OneWay}" />
Click="LocalFolderHyperlinkButton_Click"
Content="{Binding Path, Mode=OneWay}"
Tag="{Binding Path, Mode=OneWay}" />
</dev:SettingsExpander.Header> </dev:SettingsExpander.Header>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" /> <dev:SettingsExpander.Description>
<TextBlock IsTextSelectionEnabled="True" Text="{x:Bind ConnectionSummary, Mode=OneWay}" />
</dev:SettingsExpander.Description>
<ToggleSwitch IsOn="{x:Bind IsEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items> <dev:SettingsExpander.Items>
<dev:SettingsCard> <dev:SettingsCard x:Uid="MediaSettingsControlNameSetting">
<dev:SettingsCard.Header> <TextBox VerticalAlignment="Center" Text="{x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<HyperlinkButton
x:Uid="SettingsPageRemovePath"
Click="SettingsPageRemovePathButton_Click"
Tag="{Binding}" />
</dev:SettingsCard.Header>
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMusicLibRealTimeWatch"> <dev:SettingsCard x:Uid="MediaSettingsControlLastSyncTime" Description="{x:Bind LastSyncTime.ToString(), Mode=OneWay, TargetNullValue=N/A}">
<ToggleSwitch IsOn="{Binding IsRealTimeWatchEnabled, Mode=TwoWay}" /> <Button
x:Uid="MediaSettingsControlSyncNow"
Click="SyncNowButton_Click"
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="MusicSettingsControlAutoSyncInterval">
<ComboBox IsEnabled="{x:Bind IsEnabled, Mode=OneWay}" SelectedIndex="{x:Bind ScanInterval, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MusicSettingsControlAutoSyncIntervalDisabled" />
<ComboBoxItem x:Uid="MusicSettingsControlAutoSyncIntervalEveryFifteenMin" />
<ComboBoxItem x:Uid="MusicSettingsControlAutoSyncIntervalEveryHour" />
<ComboBoxItem x:Uid="MusicSettingsControlAutoSyncIntervalEverySixHrs" />
<ComboBoxItem x:Uid="MusicSettingsControlAutoSyncIntervalEveryDay" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard>
<Button x:Uid="SettingsPageRemovePath" Click="SettingsPageRemovePathButton_Click" />
</dev:SettingsCard> </dev:SettingsCard>
</dev:SettingsExpander.Items> </dev:SettingsExpander.Items>
<dev:SettingsExpander.ItemsHeader>
<StackPanel>
<!-- Index info -->
<InfoBar
IsClosable="False"
IsOpen="True"
Message="{x:Bind StatusText, Mode=OneWay}"
Severity="{x:Bind StatusSeverity, Mode=OneWay}" />
<ProgressBar
Background="Transparent"
Visibility="{x:Bind IsProcessing, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"
Value="{x:Bind IndexingProgress, Mode=OneWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind IndexingProgress, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="IsIndeterminate" Value="True" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind IndexingProgress, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="IsIndeterminate" Value="False" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ProgressBar>
</StackPanel>
</dev:SettingsExpander.ItemsHeader>
</dev:SettingsExpander> </dev:SettingsExpander>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
<dev:SettingsCard x:Uid="SettingsPageAddFolder" Style="{StaticResource DefaultSettingsExpanderItemStyle}"> <StackPanel
<Button Margin="0,6,0,0"
x:Uid="SettingsPageAddFolderButton" HorizontalAlignment="Right"
Command="{x:Bind ViewModel.SelectAndAddFolderCommand}" Orientation="Horizontal"
CommandParameter="{Binding ElementName=RootGrid}" /> Spacing="6">
</dev:SettingsCard> <Button Command="{x:Bind ViewModel.OpenMusicGalleryWindowCommand}">
<TextBlock x:Uid="SystemTrayMusicGallery" />
</Button>
<DropDownButton x:Uid="SettingsPageAddFolderButton">
<DropDownButton.Flyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="SettingsPageLocalFolder"
Command="{x:Bind ViewModel.AddMediaSourceCommand}"
CommandParameter="Local">
<MenuFlyoutItem.Icon>
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE8B7;" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator />
<MenuFlyoutItem
Command="{x:Bind ViewModel.AddMediaSourceCommand}"
CommandParameter="SMB"
Text="SMB">
<MenuFlyoutItem.Icon>
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE839;" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem
Command="{x:Bind ViewModel.AddMediaSourceCommand}"
CommandParameter="FTP"
Text="FTP">
<MenuFlyoutItem.Icon>
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE838;" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem
Command="{x:Bind ViewModel.AddMediaSourceCommand}"
CommandParameter="WebDAV"
Text="WebDAV">
<MenuFlyoutItem.Icon>
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE774;" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
</StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System; using System;
using System.Threading.Tasks;
using Windows.System; using Windows.System;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
@@ -22,18 +23,14 @@ namespace BetterLyrics.WinUI3.Controls
private void SettingsPageRemovePathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) private void SettingsPageRemovePathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{ {
ViewModel.RemoveFolderAsync((LocalMediaFolder)(sender as HyperlinkButton)!.Tag); var folder = (MediaFolder)((FrameworkElement)sender).DataContext;
ViewModel.RemoveFolder(folder);
} }
private async void LocalFolderHyperlinkButton_Click(object sender, RoutedEventArgs e) private void SyncNowButton_Click(object sender, RoutedEventArgs e)
{ {
if (sender is HyperlinkButton button && button.Tag is string uriStr) var folder = (MediaFolder)((FrameworkElement)sender).DataContext;
{ ViewModel.SyncFolder(folder);
if (Uri.TryCreate(uriStr, UriKind.Absolute, out var uri))
{
await Launcher.LaunchUriAsync(uri);
}
}
} }
} }
} }

View File

@@ -1,206 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<Page <UserControl
x:Class="BetterLyrics.WinUI3.Views.LyricsPage" x:Class="BetterLyrics.WinUI3.Controls.NowPlayingBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
xmlns:const="using:BetterLyrics.WinUI3.Constants"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI" xmlns:dev="using:DevWinUI"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Views" xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:renderer="using:BetterLyrics.WinUI3.Renderer"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="Page_Loaded"
Unloaded="Page_Unloaded"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid <Grid x:Name="RootGrid">
x:Name="RootGrid"
RightTapped="RootGrid_RightTapped"
SizeChanged="RootGrid_SizeChanged">
<!-- Win2D drawing area -->
<uc:NowPlayingCanvas x:Name="NowPlayingCanvas" />
<Grid x:Name="TrackSummaryGridContainer" Loaded="TrackSummaryGridContainer_Loaded">
<Grid.RowDefinitions>
<RowDefinition x:Name="TrackSummaryRowDef" Height="0" />
<RowDefinition x:Name="MiddleGapRowDef" Height="0" />
<RowDefinition x:Name="LyricsRowDef" Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftGapDef" Width="0" />
<ColumnDefinition x:Name="TrackSummaryColDef" Width="*" />
<ColumnDefinition x:Name="MiddleGapColDef" Width="0" />
<ColumnDefinition x:Name="LyricsColDef" Width="*" />
<ColumnDefinition x:Name="RightGapDef" Width="0" />
</Grid.ColumnDefinitions>
<!-- Album art and song info area -->
<Grid
x:Name="TrackSummaryGrid"
Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition x:Name="TrackSummaryGridRow0" Height="0" />
<RowDefinition x:Name="TrackSummaryGridRow1" Height="*" />
<RowDefinition x:Name="TrackSummaryGridRow2" Height="0" />
<RowDefinition x:Name="TrackSummaryGridRow3" Height="Auto" />
<RowDefinition x:Name="TrackSummaryGridRow4" Height="0" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="TrackSummaryGridCol0" Width="Auto" />
<ColumnDefinition x:Name="TrackSummaryGridCol1" Width="0" />
<ColumnDefinition x:Name="TrackSummaryGridCol2" Width="*" />
</Grid.ColumnDefinitions>
<Grid.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Grid.OpacityTransition>
<!-- Album art -->
<Grid
x:Name="AlbumArtGrid"
Margin="-32"
Padding="32"
HorizontalAlignment="{Binding ElementName=SongInfoStackPanel, Path=HorizontalAlignment, Mode=OneWay}">
<Grid.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Grid.OpacityTransition>
<Grid
x:Name="ShadowCastGrid"
CornerRadius="{x:Bind AlbumArtCornerRadius, Mode=OneWay}"
SizeChanged="ShadowCastGrid_SizeChanged">
<Image x:Name="LastAlbumArtImage" Stretch="Uniform">
<Image.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Image.OpacityTransition>
</Image>
<Image x:Name="AlbumArtImage" Stretch="Uniform">
<Image.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Image.OpacityTransition>
</Image>
</Grid>
<Border
x:Name="ShadowRect"
CornerRadius="{x:Bind AlbumArtCornerRadius, Mode=OneWay}"
Loaded="ShadowRect_Loaded"
Translation="0,0,0">
<Border.Shadow>
<ThemeShadow x:Name="Shadow" />
</Border.Shadow>
</Border>
</Grid>
<!-- Song info -->
<StackPanel x:Name="SongInfoStackPanel" HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<StackPanel.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</StackPanel.OpacityTransition>
<dev:OpacityMaskView HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<dev:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</dev:OpacityMaskView.OpacityMask>
<dev:AutoScrollView
x:Name="TitleAutoScrollHoverEffectView"
IsPlaying="False"
PointerCanceled="TitleAutoScrollHoverEffectView_PointerCanceled"
PointerEntered="TitleAutoScrollHoverEffectView_PointerEntered"
PointerExited="TitleAutoScrollHoverEffectView_PointerExited"
ScrollingPixelsPreSecond="20">
<TextBlock
x:Name="TitleTextBlock"
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
TextTrimming="None"
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</dev:AutoScrollView>
</dev:OpacityMaskView>
<dev:OpacityMaskView HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<dev:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</dev:OpacityMaskView.OpacityMask>
<dev:AutoScrollView
x:Name="ArtistsAutoScrollHoverEffectView"
IsPlaying="False"
PointerCanceled="ArtistsAutoScrollHoverEffectView_PointerCanceled"
PointerEntered="ArtistsAutoScrollHoverEffectView_PointerEntered"
PointerExited="ArtistsAutoScrollHoverEffectView_PointerExited"
ScrollingPixelsPreSecond="20">
<TextBlock
x:Name="ArtistsTextBlock"
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
Opacity="0.5"
TextTrimming="None"
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowArtists, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</dev:AutoScrollView>
</dev:OpacityMaskView>
<dev:OpacityMaskView HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<dev:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</dev:OpacityMaskView.OpacityMask>
<dev:AutoScrollView
x:Name="AlbumAutoScrollHoverEffectView"
IsPlaying="False"
PointerCanceled="AlbumAutoScrollHoverEffectView_PointerCanceled"
PointerEntered="AlbumAutoScrollHoverEffectView_PointerEntered"
PointerExited="AlbumAutoScrollHoverEffectView_PointerExited"
ScrollingPixelsPreSecond="20">
<TextBlock
x:Name="AlbumTextBlock"
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
Opacity="0.5"
TextTrimming="None"
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowAlbum, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</dev:AutoScrollView>
</dev:OpacityMaskView>
</StackPanel>
</Grid>
<Grid x:Name="LyricsPlaceholder" SizeChanged="LyricsPlaceholder_SizeChanged">
<ScrollViewer
x:Name="LyricsScrollViewer"
PointerWheelChanged="LyricsScrollViewer_PointerWheelChanged"
Visibility="Collapsed">
<Grid />
</ScrollViewer>
</Grid>
</Grid>
<!-- Bottom command area -->
<Grid <Grid
x:Name="BottomCommandGrid" x:Name="BottomCommandGrid"
Margin="12" Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
VerticalAlignment="Bottom"
Background="Transparent"
Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}" Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}"
PointerEntered="BottomCommandGrid_PointerEntered" PointerEntered="BottomCommandGrid_PointerEntered"
PointerExited="BottomCommandGrid_PointerExited"> PointerExited="BottomCommandGrid_PointerExited">
@@ -209,22 +23,232 @@
</Grid.OpacityTransition> </Grid.OpacityTransition>
<Grid x:Name="BottomCommandContent"> <Grid x:Name="BottomCommandContent">
<Grid Padding="3" HorizontalAlignment="Left">
<Grid HorizontalAlignment="Left">
<StackPanel <StackPanel
x:Name="BottomLeftCommandStackPanel" x:Name="BottomLeftCommandStackPanel"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="3"> Spacing="3">
<StackPanel <StackPanel
Margin="0,0,0,2" x:Name="SongInfoStackPanel"
Margin="8"
Padding="8"
VerticalAlignment="Center" VerticalAlignment="Center"
CornerRadius="4"
Opacity="{x:Bind ShowSongInfo, Mode=OneWay, Converter={StaticResource BoolToOpacityConverter}}"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="2"> Spacing="12"
Tapped="SongInfoStackPanel_Tapped"
Visibility="{x:Bind ShowSongInfo, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Background" Value="Transparent" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerPressed">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerReleased">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Grid VerticalAlignment="Center" CornerRadius="4">
<local:ImageSwitcher
Width="36"
Height="36"
Source="{x:Bind ViewModel.GSMTCService.AlbumArtBitmapImage, Mode=OneWay}" />
</Grid>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, Mode=OneWay}" />
<TextBlock
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Artist, Mode=OneWay}" />
</StackPanel>
</StackPanel>
<StackPanel
x:Name="TimeStackPanel"
Margin="8"
Padding="8"
VerticalAlignment="Center"
Opacity="{x:Bind ShowTime, Mode=OneWay, Converter={StaticResource BoolToOpacityConverter}}"
Orientation="Horizontal"
Spacing="2"
Tapped="TimeStackPanel_Tapped"
Visibility="{x:Bind ShowTime, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Background" Value="Transparent" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerPressed">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerReleased">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{Binding ElementName=TimelineSlider, Path=Value, Converter={StaticResource SecondsToFormattedTimeConverter}}" /> <TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{Binding ElementName=TimelineSlider, Path=Value, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" /> <TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
<TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" /> <TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
</StackPanel> </StackPanel>
</StackPanel>
</Grid>
<Grid HorizontalAlignment="Center">
<StackPanel
x:Name="BottomCenterCommandStackPanel"
Padding="16"
Orientation="Horizontal"
Spacing="12">
<!-- Playback order -->
<Button
Grid.Column="2"
Click="PlaybackOrderButton_Click"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ShowPlaybackOrderButton, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip>
</ToolTipService.ToolTip>
<Button.Content>
<Grid>
<!-- Repeat all -->
<FontIcon
x:Name="PlaybackRepeatAll"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8EE;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Repeat one -->
<FontIcon
x:Name="PlaybackRepeatOne"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8ED;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Shuffle -->
<FontIcon
x:Name="PlaybackShuffle"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8B1;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
</Button.Content>
</Button>
<!-- 上一曲目 -->
<Button
Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE622;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
Command="{x:Bind ViewModel.PauseSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8AE;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.PlaySongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5B0;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.NextSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
<!-- 播放队列按钮 -->
<Button
Click="PlayingQueueButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8FD;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPagePlayingQueue" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
<Grid HorizontalAlignment="Right">
<StackPanel
x:Name="BottomRightCommandStackPanel"
Padding="16"
Orientation="Horizontal"
Spacing="3">
<!-- Stop media session -->
<Button
Command="{x:Bind ViewModel.StopTrackCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE71A;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ShowStopButton, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
</ToolTipService.ToolTip>
</Button>
<!-- Volume --> <!-- Volume -->
<Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}"> <Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}">
<Grid> <Grid>
@@ -325,7 +349,7 @@
<Button.ContextFlyout> <Button.ContextFlyout>
<Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False"> <Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False">
<uc:ExtendedSlider <local:ExtendedSlider
Frequency="10" Frequency="10"
IsSliderEnabled="False" IsSliderEnabled="False"
Maximum="100" Maximum="100"
@@ -338,131 +362,54 @@
</Button.ContextFlyout> </Button.ContextFlyout>
</Button> </Button>
</StackPanel> <!-- More -->
</Grid> <Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE712;}" Style="{StaticResource GhostButtonStyle}">
<Button.Flyout>
<Grid Padding="3" HorizontalAlignment="Center"> <MenuFlyout>
<StackPanel <!-- Lyrics search window -->
x:Name="BottomCenterCommandStackPanel" <MenuFlyoutItem
Orientation="Horizontal" x:Uid="LyricsPageLyricsSearch"
Spacing="3"> Command="{x:Bind ViewModel.OpenLyricsSearchWindowCommand}"
<Button Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Command="{x:Bind ViewModel.PreviousSongCommand}" Glyph=&#xE721;}" />
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, <!-- Lyrics window manager shortcut settings -->
Glyph=&#xE622;}" <MenuFlyoutItem
Style="{StaticResource GhostButtonStyle}" /> x:Uid="LyricsPageLyricsSettings"
<Button Click="LyricsSettingsShortcutMenuFlyoutItem_Click"
Command="{x:Bind ViewModel.PauseSongCommand}" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE61F;}">
Glyph=&#xF8AE;}" <MenuFlyoutItem.ContextFlyout>
Style="{StaticResource GhostButtonStyle}"> <Flyout
<interactivity:Interaction.Behaviors> x:Name="LyricsSettingsFlyout"
<interactivity:DataTriggerBehavior Closed="LyricsSettingsFlyout_Closed"
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}" FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
ComparisonCondition="Equal" Placement="Right"
Value="True"> ShouldConstrainToRootBounds="False" />
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" /> </MenuFlyoutItem.ContextFlyout>
</interactivity:DataTriggerBehavior> </MenuFlyoutItem>
<interactivity:DataTriggerBehavior <!-- Playback shortcut settings -->
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}" <MenuFlyoutItem
ComparisonCondition="Equal" x:Uid="LyricsPagePlaybackSource"
Value="False"> Click="PlaybackSettingsShortcutMenuFlyoutItem_Click"
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" /> Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
</interactivity:DataTriggerBehavior> Glyph=&#xEA69;}">
</interactivity:Interaction.Behaviors> <MenuFlyoutItem.ContextFlyout>
</Button> <Flyout
<Button x:Name="PlaybackSettingsFlyout"
Command="{x:Bind ViewModel.PlaySongCommand}" Closed="PlaybackSettingsFlyout_Closed"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
Glyph=&#xF5B0;}" Placement="Right"
Style="{StaticResource GhostButtonStyle}"> ShouldConstrainToRootBounds="False" />
<interactivity:Interaction.Behaviors> </MenuFlyoutItem.ContextFlyout>
<interactivity:DataTriggerBehavior </MenuFlyoutItem>
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}" <!-- Settings -->
ComparisonCondition="Equal" <MenuFlyoutItem
Value="True"> x:Uid="LyricsPageSettings"
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" /> Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
</interactivity:DataTriggerBehavior> Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
<interactivity:DataTriggerBehavior Glyph=&#xE713;}" />
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}" </MenuFlyout>
ComparisonCondition="Equal" </Button.Flyout>
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.NextSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</Grid>
<Grid Padding="3" HorizontalAlignment="Right">
<StackPanel
x:Name="BottomRightCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<Button
Click="LyricsSearchShortcutButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE721;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="LyricsPageLyricsSearchButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
<!-- Lyrics window manager shortcut settings -->
<Button
Click="LyricsSettingsShortcutButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE61F;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="LyricsPageLyricsSettingsButtonToolTip" />
</ToolTipService.ToolTip>
<Button.ContextFlyout>
<Flyout
x:Name="LyricsSettingsFlyout"
Closed="LyricsSettingsFlyout_Closed"
FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
Placement="Right"
ShouldConstrainToRootBounds="False" />
</Button.ContextFlyout>
</Button>
<!-- Playback shortcut settings -->
<Button
Click="PlaybackSettingsShortcutButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEA69;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="LyricsPagePlaybackSourceButtonToolTip" />
</ToolTipService.ToolTip>
<Button.ContextFlyout>
<Flyout
x:Name="PlaybackSettingsFlyout"
Closed="PlaybackSettingsFlyout_Closed"
FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
Placement="Right"
ShouldConstrainToRootBounds="False" />
</Button.ContextFlyout>
</Button>
<!-- Settings -->
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button> </Button>
</StackPanel> </StackPanel>
@@ -470,20 +417,22 @@
<Slider <Slider
x:Name="TimelineSlider" x:Name="TimelineSlider"
Margin="0,-32,0,0" Margin="0,-14,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}" Maximum="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
Minimum="0" Minimum="0"
Style="{StaticResource GhostSliderStyle}" Style="{StaticResource GhostSliderStyle}"
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" /> ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
Value="{x:Bind ViewModel.GSMTCService.CurrentPosition.TotalSeconds, Mode=OneWay}" />
<Grid <Grid
x:Name="TimelineSliderLyricsLineInfo" x:Name="TimelineSliderLyricsLineInfo"
Margin="0,-48,0,0" Margin="0,-32,0,0"
Padding="8,4" Padding="8,4"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Top" VerticalAlignment="Top"
Background="{ThemeResource SolidBackgroundFillColorQuarternaryBrush}" Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
CornerRadius="6" CornerRadius="6"
Opacity="{x:Bind ViewModel.TimelineSliderThumbOpacity, Mode=OneWay}"> Opacity="{x:Bind ViewModel.TimelineSliderThumbOpacity, Mode=OneWay}">
<Grid.OpacityTransition> <Grid.OpacityTransition>
@@ -503,7 +452,7 @@
</Grid> </Grid>
<Grid <Grid
Height="32" Height="32"
Margin="0,-32,0,0" Margin="0,-12,0,0"
VerticalAlignment="Top" VerticalAlignment="Top"
Background="Transparent" Background="Transparent"
PointerEntered="TimelineSliderOverlay_PointerEntered" PointerEntered="TimelineSliderOverlay_PointerEntered"
@@ -543,7 +492,8 @@
<Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False"> <Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False">
<Flyout.FlyoutPresenterStyle> <Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter"> <Style TargetType="FlyoutPresenter">
<Setter Property="MinWidth" Value="500" /> <Setter Property="Padding" Value="0" />
<Setter Property="MinWidth" Value="400" />
<Setter Property="MinHeight" Value="100" /> <Setter Property="MinHeight" Value="100" />
<Setter Property="CornerRadius" Value="12" /> <Setter Property="CornerRadius" Value="12" />
</Style> </Style>
@@ -553,7 +503,58 @@
</Grid.ContextFlyout> </Grid.ContextFlyout>
</Grid> </Grid>
<uc:SystemTray /> <VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PlaybackOrderState">
<VisualState x:Name="RepeatAll">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="1" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="0" />
<Setter Target="PlaybackShuffle.Opacity" Value="0" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Visible" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="RepeatOne">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="1" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="0" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="1" />
<Setter Target="PlaybackShuffle.Opacity" Value="0" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Visible" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Shuffle">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="2" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="0" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="0" />
<Setter Target="PlaybackShuffle.Opacity" Value="1" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid> </Grid>
</Page>
</UserControl>

View File

@@ -0,0 +1,309 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.GSMTCService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Numerics;
using BetterLyrics.WinUI3.Extensions;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class NowPlayingBar : UserControl
{
public NowPlayingBarViewModel ViewModel => (NowPlayingBarViewModel)DataContext;
public event EventHandler? SongInfoTapped;
public event EventHandler? TimeTapped;
public event EventHandler? PlayQueueButtonClick;
public bool ShowTime
{
get { return (bool)GetValue(ShowTimeProperty); }
set { SetValue(ShowTimeProperty, value); }
}
public static readonly DependencyProperty ShowTimeProperty =
DependencyProperty.Register(nameof(ShowTime), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowSongInfo
{
get { return (bool)GetValue(ShowSongInfoProperty); }
set { SetValue(ShowSongInfoProperty, value); }
}
public static readonly DependencyProperty ShowSongInfoProperty =
DependencyProperty.Register(nameof(ShowSongInfo), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowPlayingQueueButton
{
get { return (bool)GetValue(ShowPlayingQueueButtonProperty); }
set { SetValue(ShowPlayingQueueButtonProperty, value); }
}
public static readonly DependencyProperty ShowPlayingQueueButtonProperty =
DependencyProperty.Register(nameof(ShowPlayingQueueButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowPlaybackOrderButton
{
get { return (bool)GetValue(ShowPlaybackOrderButtonProperty); }
set { SetValue(ShowPlaybackOrderButtonProperty, value); }
}
public static readonly DependencyProperty ShowStopButtonProperty =
DependencyProperty.Register(nameof(ShowStopButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowStopButton
{
get { return (bool)GetValue(ShowStopButtonProperty); }
set { SetValue(ShowStopButtonProperty, value); }
}
public static readonly DependencyProperty ShowPlaybackOrderButtonProperty =
DependencyProperty.Register(nameof(ShowPlaybackOrderButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public PlaybackOrder PlaybackOrder
{
get { return (PlaybackOrder)GetValue(PlaybackOrderProperty); }
set { SetValue(PlaybackOrderProperty, value); }
}
public static readonly DependencyProperty PlaybackOrderProperty =
DependencyProperty.Register(nameof(PlaybackOrder), typeof(PlaybackOrder), typeof(NowPlayingBar), new PropertyMetadata(PlaybackOrder.RepeatAll));
public bool IsCompactMode
{
get { return (bool)GetValue(IsCompactModeProperty); }
set { SetValue(IsCompactModeProperty, value); }
}
public static readonly DependencyProperty IsCompactModeProperty =
DependencyProperty.Register(nameof(IsCompactMode), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false, OnDependencyPropertyChanged));
public bool IsAutoHideEnabled
{
get { return (bool)GetValue(IsAutoHideEnabledProperty); }
set { SetValue(IsAutoHideEnabledProperty, value); }
}
public static readonly DependencyProperty IsAutoHideEnabledProperty =
DependencyProperty.Register(nameof(IsAutoHideEnabled), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false, OnDependencyPropertyChanged));
private bool _isPointerInBottomCommandGrid = false;
public NowPlayingBar()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<NowPlayingBarViewModel>();
}
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is NowPlayingBar self)
{
if (e.Property == IsCompactModeProperty)
{
self.OnIsCompactModeChanged();
}
else if (e.Property == IsAutoHideEnabledProperty)
{
self.OnIsAutoHideEnabledChanged();
}
}
}
private void OnIsAutoHideEnabledChanged()
{
if (IsAutoHideEnabled)
{
if (!_isPointerInBottomCommandGrid)
{
ViewModel.BottomCommandGridOpacity = 0;
}
}
else
{
ViewModel.BottomCommandGridOpacity = 1;
}
}
private void OnIsCompactModeChanged()
{
if (IsCompactMode)
{
if (BottomCommandGrid.Children.Count != 0)
{
BottomCommandGrid.Children.Remove(BottomCommandContent);
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
}
else
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.Hide();
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
BottomCommandGrid.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
}
}
private void PlaybackSettingsShortcutMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
PlaybackSettingsFlyout.Content = new PlaybackSettingsControl
{
MaxHeight = 500,
MaxWidth = 850,
};
PlaybackSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void VolumeButton_Click(object sender, RoutedEventArgs e)
{
VolumeFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void PlaybackSettingsFlyout_Closed(object sender, object e)
{
PlaybackSettingsFlyout.Content = null;
}
private void LyricsSettingsFlyout_Closed(object sender, object e)
{
LyricsSettingsFlyout.Content = null;
}
private void LyricsSettingsShortcutMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
LyricsSettingsFlyout.Content = new LyricsWindowSettingsControl
{
MaxHeight = 500,
MaxWidth = 850,
};
LyricsSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void TimelineSliderOverlay_PointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
var grid = (Grid)sender;
var pos = e.GetCurrentPoint(grid).Position;
var ratio = pos.X / grid.ActualWidth;
ViewModel.GSMTCService.ChangePosition(TimelineSlider.Maximum * ratio);
}
private void TimelineSliderOverlay_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
float targetX;
var grid = (Grid)sender;
var pos = e.GetCurrentPoint(grid).Position;
var ratio = pos.X / grid.ActualWidth;
ViewModel.TimelineSliderThumbSeconds = TimelineSlider.Maximum * ratio;
if (pos.X + TimelineSliderLyricsLineInfo.ActualWidth > grid.ActualWidth)
{
targetX = (float)(grid.ActualWidth - TimelineSliderLyricsLineInfo.ActualWidth);
}
else
{
targetX = (float)pos.X;
}
TimelineSliderLyricsLineInfo.Translation = new Vector3(targetX, 0, 0);
}
private void TimelineSliderOverlay_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ViewModel.TimelineSliderThumbOpacity = 1f;
}
private void TimelineSliderOverlay_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ViewModel.TimelineSliderThumbOpacity = 0f;
}
private void ExtendedSlider_ValueChangedByUser(object sender, Events.ExtendedSliderValueChangedByUserEventArgs e)
{
SystemVolumeHook.MasterVolume = ViewModel.Volume;
}
private void LyricsSearchShortcutButton_Click(object sender, RoutedEventArgs e)
{
WindowHook.OpenOrShowWindow<LyricsSearchWindow>();
}
private void SongInfoStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
{
SongInfoTapped?.Invoke(sender, EventArgs.Empty);
}
private void TimeStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
{
TimeTapped?.Invoke(sender, EventArgs.Empty);
}
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
_isPointerInBottomCommandGrid = true;
if (IsAutoHideEnabled && BottomCommandGrid.Children.Count != 0)
{
ViewModel.BottomCommandGridOpacity = 1f;
}
e.Handled = true;
}
private void BottomCommandGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
_isPointerInBottomCommandGrid = false;
if (IsAutoHideEnabled && BottomCommandGrid.Children.Count != 0)
{
ViewModel.BottomCommandGridOpacity = 0f;
}
e.Handled = true;
}
private void BottomCommandFlyoutTrigger_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
ViewModel.BottomCommandFlyoutTriggerOpacity = 1f;
}
}
private void BottomCommandFlyoutTrigger_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
ViewModel.BottomCommandFlyoutTriggerOpacity = 0f;
}
}
private void BottomCommandFlyoutTrigger_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger);
}
}
private void PlayingQueueButton_Click(object sender, RoutedEventArgs e)
{
PlayQueueButtonClick?.Invoke(sender, EventArgs.Empty);
}
private void PlaybackOrderButton_Click(object sender, RoutedEventArgs e)
{
PlaybackOrder = PlaybackOrder.GetNext();
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.PatronControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Margin="12,8">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind PatronName, Mode=OneWay}" />
<TextBlock Foreground="{ThemeResource TextFillColorTertiaryBrush}" Text="{x:Bind Date, Mode=OneWay}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,46 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class PatronControl : UserControl
{
public string PatronName
{
get { return (string)GetValue(PatronNameProperty); }
set { SetValue(PatronNameProperty, value); }
}
public static readonly DependencyProperty PatronNameProperty =
DependencyProperty.Register(nameof(PatronName), typeof(string), typeof(PatronControl), new PropertyMetadata(""));
public string Date
{
get { return (string)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register(nameof(Date), typeof(string), typeof(PatronControl), new PropertyMetadata(""));
public PatronControl()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.PlayQueue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="12,12,12,0">
<TextBlock
x:Uid="MusicGalleryPagePlayingQueue"
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" />
</Grid>
<Grid Grid.Row="1" Margin="12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=OneWay, Converter={StaticResource IndexToDisplayConverter}}" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
<TextBlock Text="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}" />
</StackPanel>
<!-- Scroll to playing item -->
<Button
Grid.Column="1"
HorizontalAlignment="Right"
Click="ScrollToPlayingItemButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE7B7;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPageScrollToPlayingItem" />
</ToolTipService.ToolTip>
</Button>
<!-- Empty play queue -->
<Button
Grid.Column="2"
HorizontalAlignment="Right"
Click="EmptyPlayingQueueButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE738;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPageEmptyPlayingQueue" />
</ToolTipService.ToolTip>
</Button>
</Grid>
<NavigationViewItemSeparator Grid.Row="2" />
<ListView
x:Name="PlayingQueueListView"
Grid.Row="3"
ItemsSource="{x:Bind ViewModel.SMTCService.TrackPlayingQueue, Mode=OneWay}"
SelectedIndex="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Padding="0,6">
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
<StackPanel Margin="0,0,36,0">
<TextBlock Text="{Binding Track.Title}" TextWrapping="Wrap" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Track.Artist}"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
<Grid HorizontalAlignment="Right">
<Button
VerticalAlignment="Center"
Click="RemoveFromPlayingQueueButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE738;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPageRemoveFromPlayingQueue" />
</ToolTipService.ToolTip>
</Button>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="3">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<Image MaxWidth="100" Source="/Assets/EmptyBox.png" />
<TextBlock
x:Uid="MusicGalleryPagePlayingQueueEmpty"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,103 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class PlayQueue : UserControl, IRecipient<PropertyChangedMessage<int>>
{
public PlayQueueViewModel ViewModel => (PlayQueueViewModel)DataContext;
public PlayQueue()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<PlayQueueViewModel>();
WeakReferenceMessenger.Default.RegisterAll(this);
}
private void ScrollToPlayingItem()
{
if (PlayingQueueListView == null) return;
var targetItem = ViewModel.SMTCService.TrackPlayingQueue
.ElementAtOrDefault(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
if (targetItem == null) return;
PlayingQueueListView.ScrollIntoView(targetItem);
}
private void ScrollToPlayingItemButton_Click(object sender, RoutedEventArgs e)
{
ScrollToPlayingItem();
}
private async void PlayingQueueListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
{
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
await ViewModel.SMTCService.PlayTrackAsync(item);
}
private async void RemoveFromPlayingQueueButton_Click(object sender, RoutedEventArgs e)
{
bool playNext = false;
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
int index = ViewModel.SMTCService.TrackPlayingQueue.IndexOf(item);
if (item == PlayingQueueListView.SelectedItem)
{
playNext = true;
}
ViewModel.SMTCService.TrackPlayingQueue.Remove(item);
if (playNext)
{
if (ViewModel.SMTCService.TrackPlayingQueue.Count == 0)
{
index = -1;
}
else if (index >= ViewModel.SMTCService.TrackPlayingQueue.Count)
{
index = ViewModel.SMTCService.TrackPlayingQueue.Count - 1;
}
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = index;
await ViewModel.SMTCService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
}
}
private async void EmptyPlayingQueueButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.SMTCService.TrackPlayingQueue.Clear();
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = -1;
await ViewModel.SMTCService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is MusicGallerySettings)
{
if (message.PropertyName == nameof(MusicGallerySettings.PlayQueueIndex))
{
ScrollToPlayingItem();
}
}
}
}
}

View File

@@ -22,8 +22,9 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Column="0" RowSpacing="18"> <Grid Grid.Column="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@@ -42,51 +43,53 @@
</interactivity:DataTriggerBehavior> </interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
<!-- 播放源列表 --> <InfoBar
<ListView x:Uid="SettingsPageMusicGalleryOpened"
x:Name="MediaSourceProvidersListView"
Grid.Row="0" Grid.Row="0"
AllowDrop="True" IsClosable="False"
CanDragItems="True" IsOpen="{x:Bind ViewModel.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened, Mode=OneWay}"
CanReorderItems="True" Severity="Informational" />
DragItemsCompleted="MediaSourceProvidersListView_DragItemsCompleted"
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<StackPanel Orientation="Horizontal" Spacing="6">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding Provider, Mode=OneWay}" />
</ToolTipService.ToolTip>
<FontIcon
FontFamily="Segoe UI Symbol"
FontSize="12"
Glyph="&#x283F;" />
<Grid
Width="16"
Height="16"
CornerRadius="4">
<ImageIcon Source="{Binding LogoPath}" />
</Grid>
<TextBlock
MaxWidth="200"
Text="{Binding DisplayName, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ScrollViewer Grid.Row="1" Style="{StaticResource SettingsScrollViewerStyle}"> <!-- 播放源列表 -->
<Grid Grid.Row="1" Margin="36,0,36,4">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageConfigPlaybackSource" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<ComboBox
x:Name="MediaSourceProvidersListView"
HorizontalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<Grid Padding="2,4" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToolTipService.ToolTip>
<ToolTip Content="{Binding Provider, Mode=OneWay}" />
</ToolTipService.ToolTip>
<Grid
Grid.Column="0"
Width="16"
Height="16"
CornerRadius="4">
<ImageIcon Source="{Binding LogoPath}" />
</Grid>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding DisplayName, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
<!-- 播放源配置 -->
<ScrollViewer Grid.Row="2" Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}"> <Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}"> <StackPanel Spacing="{StaticResource SettingsCardSpacing}">
@@ -269,6 +272,7 @@
</Grid> </Grid>
<!-- 播放源空白占位 -->
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -294,6 +298,8 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" /> Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel> </StackPanel>
<!-- 右侧杂项设置 -->
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}"> <ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}"> <Grid Style="{StaticResource SettingsGridStyle}">
@@ -308,8 +314,8 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"> HorizontalContentAlignment="Left">
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.GSMTCService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel> </StackPanel>
</Expander> </Expander>
@@ -319,13 +325,10 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"> HorizontalContentAlignment="Left">
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Artist, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow <local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DurationMs, TargetNullValue=N/A, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
x:Uid="LyricsSearchControlDurauion"
Unit="s"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Duration, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel> </StackPanel>
</Expander> </Expander>
@@ -335,30 +338,29 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"> HorizontalContentAlignment="Left">
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Artist, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" /> <local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow <local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
x:Uid="LyricsSearchControlDurauion" <local:PropertyRow x:Uid="LyricsPageLanguageCode" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsData.LanguageCode, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
Unit="s"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Duration, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow <local:PropertyRow
x:Uid="LyricsPageLyricsProviderPrefix" x:Uid="LyricsPageLyricsProviderPrefix"
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}" Link="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Reference, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay}" ToolTipService.ToolTip="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" /> Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" /> <local:PropertyRow x:Uid="LyricsPageTransliterationProviderPrefix" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.TransliterationProvider, Mode=OneWay, Converter={StaticResource TransliterationSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.TranslationProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow <local:PropertyRow
x:Uid="LyricsPageMatchPercentage" x:Uid="LyricsPageMatchPercentage"
Unit="%" Unit="%"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" /> Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsPageCachePath"
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel> </StackPanel>
</Expander> </Expander>
<dev:SettingsCard x:Uid="SettingsPageListenNewSession">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect"> <dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
@@ -386,16 +388,23 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
</dev:SettingsCard> </dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}"> <dev:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="12"> <Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox <TextBox
x:Uid="LibreTranslateServerTextBox" x:Uid="LibreTranslateServerTextBox"
Grid.Column="0"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}" /> Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button <Button
x:Uid="SettingsPageServerTestButton" x:Uid="SettingsPageServerTestButton"
Grid.Column="1"
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}" Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" /> IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel> </Grid>
</dev:SettingsCard> </dev:SettingsCard>
</dev:SettingsExpander.Items> </dev:SettingsExpander.Items>
</dev:SettingsExpander> </dev:SettingsExpander>
@@ -413,9 +422,33 @@
</dev:SettingsCard> </dev:SettingsCard>
</dev:SettingsExpander.Items> </dev:SettingsExpander.Items>
</dev:SettingsExpander> </dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageJapanese"> <dev:SettingsExpander x:Uid="SettingsPageJapanese" IsExpanded="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=TwoWay}" />
</dev:SettingsCard> <dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageCutletDockerServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=OneWay}">
<dev:SettingsCard.Description>
<HyperlinkButton Content="https://github.com/jayfunc/cutlet-docker" NavigateUri="https://github.com/jayfunc/cutlet-docker" />
</dev:SettingsCard.Description>
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
x:Uid="CutletServerTextBox"
Grid.Column="0"
IsEnabled="{x:Bind ViewModel.IsCutletDockerServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.CutletDockerServer, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
x:Uid="SettingsPageServerTestButton"
Grid.Column="1"
Command="{x:Bind ViewModel.CutletDockerServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsCutletDockerServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</Grid>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 中文简体繁体偏好 --> <!-- 中文简体繁体偏好 -->
<TextBlock x:Uid="SettingsPageChineseLyrics" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock x:Uid="SettingsPageChineseLyrics" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
@@ -424,9 +457,9 @@
</dev:SettingsCard> </dev:SettingsCard>
<!-- Last.fm --> <!-- Last.fm -->
<TextBlock x:Uid="SettingsPageLastFM" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" /> <TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="Last.fm" />
<dev:SettingsExpander <dev:SettingsExpander
x:Uid="SettingsPageLastFMManager" Header="Last.fm"
HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/LastFM.png}" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/LastFM.png}"
IsExpanded="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}"> IsExpanded="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
@@ -514,6 +547,7 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.RemoteServerConfigControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<ScrollViewer>
<StackPanel Width="400" Spacing="16">
<ProgressBar
x:Name="ProgressBar"
IsIndeterminate="True"
Visibility="Collapsed" />
<InfoBar
x:Name="ErrorInfoBar"
IsClosable="True"
IsOpen="False"
Severity="Error" />
<TextBox
x:Name="NameBox"
x:Uid="RemoteServerConfigControlName"
TextWrapping="Wrap" />
<StackPanel x:Name="RemoteFieldsPanel" Spacing="16">
<Grid ColumnDefinitions="*, Auto" ColumnSpacing="12">
<TextBox
x:Name="HostBox"
x:Uid="RemoteServerConfigControlServerAddress"
Grid.Column="0"
InputScope="Url"
PlaceholderText="192.168.1.x"
TextWrapping="Wrap" />
<NumberBox
x:Name="PortBox"
x:Uid="RemoteServerConfigControlPort"
Grid.Column="1"
MinWidth="100"
LargeChange="10"
SmallChange="1"
SpinButtonPlacementMode="Inline"
ToolTipService.ToolTip="80"
Value="80" />
</Grid>
</StackPanel>
<Grid ColumnDefinitions="*, Auto" ColumnSpacing="8">
<TextBox
x:Name="PathBox"
x:Uid="RemoteServerConfigControlPath"
Grid.Column="0"
TextChanged="PathBox_TextChanged"
TextWrapping="Wrap" />
<Button
x:Name="BrowseButton"
x:Uid="RemoteServerConfigControlBrowse"
Grid.Column="1"
VerticalAlignment="Bottom"
Click="BrowseButton_Click"
Visibility="Collapsed" />
</Grid>
<InfoBar
x:Name="PathWarningBar"
IsClosable="False"
IsOpen="False"
Severity="Warning" />
<StackPanel x:Name="AuthFieldsPanel" Spacing="16">
<Grid ColumnDefinitions="*, *" ColumnSpacing="12">
<TextBox
x:Name="UserBox"
x:Uid="RemoteServerConfigControlUsername"
Grid.Column="0"
TextWrapping="Wrap" />
<PasswordBox
x:Name="PwdBox"
x:Uid="RemoteServerConfigControlPassword"
Grid.Column="1"
PasswordRevealMode="Peek" />
</Grid>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,195 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class RemoteServerConfigControl : UserControl
{
private readonly FileSourceType _fileSourceType;
private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public RemoteServerConfigControl(FileSourceType fileSourceType)
{
this.InitializeComponent();
_fileSourceType = fileSourceType;
SetupDefaults();
CheckPathForWarning();
}
private void SetupDefaults()
{
if (_fileSourceType == FileSourceType.Local)
{
RemoteFieldsPanel.Visibility = Visibility.Collapsed;
AuthFieldsPanel.Visibility = Visibility.Collapsed;
BrowseButton.Visibility = Visibility.Visible;
PathBox.PlaceholderText = @"D:\Music";
}
else
{
BrowseButton.Visibility = Visibility.Collapsed;
RemoteFieldsPanel.Visibility = Visibility.Visible;
AuthFieldsPanel.Visibility = Visibility.Visible;
switch (_fileSourceType)
{
case FileSourceType.SMB:
PortBox.Value = 445;
PathBox.PlaceholderText = "SharedMusic";
break;
case FileSourceType.FTP:
PortBox.Value = 21;
PathBox.PlaceholderText = "/pub/music";
break;
case FileSourceType.WebDAV:
PortBox.Value = 80;
PathBox.PlaceholderText = "/dav/music";
break;
}
}
}
private string GetScheme()
{
string scheme = string.Empty;
switch (_fileSourceType)
{
case FileSourceType.SMB:
scheme = "smb";
break;
case FileSourceType.FTP:
scheme = "ftp";
break;
case FileSourceType.WebDAV:
scheme = "https";
break;
}
return scheme;
}
public MediaFolder GetConfig()
{
string finalName = HostBox.Text.Trim();
if (_fileSourceType == FileSourceType.Local)
{
if (string.IsNullOrWhiteSpace(PathBox.Text))
throw new ArgumentException(_localizationService.GetLocalizedString("RemoteServerConfigControlPathRequired"));
if (!string.IsNullOrWhiteSpace(NameBox.Text))
finalName = NameBox.Text.Trim();
else
finalName = PathBox.Text.TrimEnd(System.IO.Path.DirectorySeparatorChar);
return new MediaFolder
{
Name = finalName,
SourceType = FileSourceType.Local,
UriScheme = "file",
UriPath = PathBox.Text.Trim(),
};
}
if (string.IsNullOrWhiteSpace(HostBox.Text))
throw new ArgumentException(_localizationService.GetLocalizedString("RemoteServerConfigControlServerAddressRequired"));
if (!string.IsNullOrWhiteSpace(NameBox.Text))
{
finalName = NameBox.Text.Trim();
}
else
{
finalName = $"{_fileSourceType} - {HostBox.Text}";
}
string scheme = GetScheme();
var folder = new MediaFolder
{
Name = finalName,
SourceType = _fileSourceType,
UriScheme = scheme,
UriHost = HostBox.Text.Trim(), // ȥ<><C8A5><EFBFBD><EFBFBD>β<EFBFBD>ո<EFBFBD>
UriPort = (int)PortBox.Value,
UriPath = PathBox.Text.Trim(),
UserName = UserBox.Text.Trim(),
Password = PwdBox.Password,
};
return folder;
}
public void ShowError(string? message)
{
ErrorInfoBar.Message = message;
ErrorInfoBar.IsOpen = !string.IsNullOrWhiteSpace(message);
}
public void SetProgressBarVisibility(Visibility visibility)
{
ProgressBar.Visibility = visibility;
}
private void CheckPathForWarning()
{
string? path = PathBox.Text?.Trim();
bool isSymbolRoot = string.IsNullOrEmpty(path) ||
path == "/" ||
path == "\\";
bool isDriveRoot = false;
if (!string.IsNullOrEmpty(path))
{
var normalized = path.TrimEnd('\\', '/');
isDriveRoot = normalized.EndsWith(":") && normalized.Length == 2;
}
bool isRoot = isSymbolRoot || isDriveRoot;
if (isRoot)
{
PathWarningBar.Message = _localizationService.GetLocalizedString("FileSystemServiceRootDirectoryWarning");
PathWarningBar.IsOpen = true;
}
else
{
PathWarningBar.IsOpen = false;
}
}
private void PathBox_TextChanged(object sender, TextChangedEventArgs e)
{
CheckPathForWarning();
}
private async void BrowseButton_Click(object sender, RoutedEventArgs e)
{
try
{
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
if (folder != null)
{
PathBox.Text = folder.Path;
}
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.ShadowImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:const="using:BetterLyrics.WinUI3.Constants"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<UserControl.OpacityTransition>
<ScalarTransition />
</UserControl.OpacityTransition>
<Grid Margin="-32" Padding="32">
<Grid
x:Name="ShadowCastGrid"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SizeChanged="ShadowCastGrid_SizeChanged">
<Image Source="{x:Bind Source, Mode=OneWay}" Stretch="Uniform" />
</Grid>
<Border
x:Name="ShadowRect"
Loaded="ShadowRect_Loaded"
SizeChanged="ShadowRect_SizeChanged"
Translation="0,0,0">
<Border.Shadow>
<ThemeShadow x:Name="Shadow" />
</Border.Shadow>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,93 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class ShadowImage : UserControl
{
public int CornerRadiusAmount
{
get { return (int)GetValue(CornerRadiusAmountProperty); }
set { SetValue(CornerRadiusAmountProperty, value); }
}
public static readonly DependencyProperty CornerRadiusAmountProperty =
DependencyProperty.Register(nameof(CornerRadiusAmount), typeof(int), typeof(ShadowImage), new PropertyMetadata(0, OnDependencyPropertyChanged));
public int ShadowAmount
{
get { return (int)GetValue(ShadowAmountProperty); }
set { SetValue(ShadowAmountProperty, value); }
}
public static readonly DependencyProperty ShadowAmountProperty =
DependencyProperty.Register(nameof(ShadowAmount), typeof(int), typeof(ShadowImage), new PropertyMetadata(0, OnDependencyPropertyChanged));
public ImageSource? Source
{
get { return (ImageSource?)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(ImageSource), typeof(ShadowImage), new PropertyMetadata(null));
public ShadowImage()
{
InitializeComponent();
}
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ShadowImage shadowImage)
{
if (e.Property == CornerRadiusAmountProperty)
{
shadowImage.UpdateShadowCastGridCornerRadius();
shadowImage.UpdateShadowRectCornerRadius();
}
else if (e.Property == ShadowAmountProperty)
{
shadowImage.UpdateShadowRectShadow();
}
}
}
private void UpdateShadowRectShadow()
{
ShadowRect.Translation = new(0, 0, ShadowAmount);
}
private void UpdateShadowCastGridCornerRadius()
{
var minSize = Math.Min(ShadowCastGrid.ActualHeight, ShadowCastGrid.ActualWidth);
ShadowCastGrid.CornerRadius = new(CornerRadiusAmount / 100.0 * (minSize / 2));
}
private void UpdateShadowRectCornerRadius()
{
var minSize = Math.Min(ShadowRect.ActualHeight, ShadowRect.ActualWidth);
ShadowRect.CornerRadius = new(CornerRadiusAmount / 100.0 * (minSize / 2));
}
private void ShadowCastGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateShadowCastGridCornerRadius();
}
private void ShadowRect_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateShadowRectCornerRadius();
}
private void ShadowRect_Loaded(object sender, RoutedEventArgs e)
{
Shadow.Receivers.Add(ShadowCastGrid);
}
}
}

View File

@@ -23,7 +23,11 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12, FontSize=12,
Glyph=&#xE894;}" Glyph=&#xE894;}"
Style="{StaticResource GhostButtonStyle}" /> Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageClear" />
</ToolTipService.ToolTip>
</Button>
<Button <Button
Margin="3,0,0,0" Margin="3,0,0,0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@@ -31,7 +35,11 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12, FontSize=12,
Glyph=&#xE721;}" Glyph=&#xE721;}"
Style="{StaticResource GhostButtonStyle}" /> Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageCheckShortcut" />
</ToolTipService.ToolTip>
</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -1,6 +1,6 @@
using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks; using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Services.ResourceService; using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Input; using Microsoft.UI.Input;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
@@ -16,7 +16,7 @@ namespace BetterLyrics.WinUI3.Controls
{ {
public sealed partial class ShortcutTextBox : UserControl public sealed partial class ShortcutTextBox : UserControl
{ {
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public ShortcutTextBox() public ShortcutTextBox()
{ {

View File

@@ -0,0 +1,410 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.StatsDashboardControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:BetterLyrics.WinUI3.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.WinUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:statsmodels="using:BetterLyrics.WinUI3.Models.Stats"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<UserControl.Resources>
<Style x:Key="StatsCardStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="16" />
<Setter Property="Margin" Value="0,0,12,12" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar
Grid.Row="0"
Background="Transparent"
IsIndeterminate="{x:Bind ViewModel.IsLoading, Mode=OneWay}"
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<Grid Grid.Row="1" Visibility="{x:Bind ViewModel.GSMTCService.IsScrobbled, Mode=OneWay, Converter={StaticResource BoolNegationToVisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<InfoBar
x:Uid="StatsDashboardControlRecording"
Grid.Row="0"
IsClosable="False"
IsOpen="True"
Message="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, Mode=OneWay}" />
<ProgressBar
Grid.Row="1"
Background="Transparent"
Maximum="{x:Bind ViewModel.GSMTCService.TargetScrobbledDuration.TotalSeconds, Mode=OneWay}"
ShowPaused="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
Value="{x:Bind ViewModel.GSMTCService.ScrobbledDuration.TotalSeconds, Mode=OneWay}" />
</Grid>
<controls:WrapPanel
Grid.Row="2"
Margin="36,36,36,12"
HorizontalSpacing="12"
Orientation="Horizontal"
VerticalSpacing="12">
<ComboBox
x:Uid="StatsDashboardControlTimeRange"
Header="Time Range"
SelectedIndex="{x:Bind ViewModel.SelectedTimeRange, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="StatsDashboardControlToday" />
<ComboBoxItem x:Uid="StatsDashboardControlThisWeek" />
<ComboBoxItem x:Uid="StatsDashboardControlThisMonth" />
<ComboBoxItem x:Uid="StatsDashboardControlThisQuarter" />
<ComboBoxItem x:Uid="StatsDashboardControlThisYear" />
<ComboBoxItem x:Uid="StatsDashboardControlCustom" />
</ComboBox>
<CalendarDatePicker
x:Uid="StatsDashboardControlStart"
Date="{x:Bind ViewModel.CustomStartDate, Mode=TwoWay}"
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
<TimePicker
VerticalAlignment="Bottom"
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
Time="{x:Bind ViewModel.CustomStartTime, Mode=TwoWay}" />
<CalendarDatePicker
x:Uid="StatsDashboardControlEnd"
Date="{x:Bind ViewModel.CustomEndDate, Mode=TwoWay}"
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
<TimePicker
VerticalAlignment="Bottom"
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
Time="{x:Bind ViewModel.CustomEndTime, Mode=TwoWay}" />
<Button
VerticalAlignment="Bottom"
Command="{x:Bind ViewModel.RefreshDataCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE72C;}" />
</controls:WrapPanel>
<ScrollViewer Grid.Row="3" Padding="36,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
<StackPanel>
<StackPanel
Opacity="0.8"
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="14" Glyph="&#xE916;" />
<TextBlock x:Uid="StatsDashboardControlTotalDuration" Style="{ThemeResource CaptionTextBlockStyle}" />
</StackPanel>
<StackPanel
Margin="0,8,0,0"
Orientation="Horizontal"
Spacing="4">
<TextBlock
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.TotalDuration.TotalHours, Mode=OneWay, Converter={StaticResource DoubleToDecimalConverter}}" />
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Bottom"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Opacity="0.8"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="Hrs" />
</StackPanel>
</StackPanel>
</Border>
<Border Grid.Column="1" Style="{StaticResource StatsCardStyle}">
<StackPanel>
<StackPanel
Opacity="0.8"
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="14" Glyph="&#xE8D6;" />
<TextBlock x:Uid="StatsDashboardControlTracksPlayed" Style="{ThemeResource CaptionTextBlockStyle}" />
</StackPanel>
<TextBlock
Margin="0,8,0,0"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.TotalTracksPlayed, Mode=OneWay}" />
</StackPanel>
</Border>
<Border
Grid.Column="2"
Margin="0,0,0,12"
Style="{StaticResource StatsCardStyle}">
<StackPanel>
<StackPanel
Opacity="0.8"
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="14" Glyph="&#xEC4A;" />
<TextBlock x:Uid="StatsDashboardControlTopSource" Style="{ThemeResource CaptionTextBlockStyle}" />
</StackPanel>
<TextBlock
Margin="0,8,0,0"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.TopPlayerName, Mode=OneWay}" />
</StackPanel>
</Border>
</Grid>
<!-- Activity by hour -->
<Border
Grid.Row="1"
Margin="0,0,0,12"
Style="{StaticResource StatsCardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Margin="0,0,0,12"
Orientation="Horizontal"
Spacing="8">
<TextBlock x:Uid="StatsDashboardControlActivityByHour" Style="{ThemeResource SubtitleTextBlockStyle}" />
</StackPanel>
<Grid Grid.Row="1" Margin="0,0,0,16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock
x:Uid="StatsDashboardControlMostActive"
FontSize="12"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.PeakHourText, Mode=OneWay}" />
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock
x:Uid="StatsDashboardControlLeastActive"
FontSize="12"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.QuietHourText, Mode=OneWay}" />
</StackPanel>
</Grid>
<lvc:CartesianChart
Grid.Row="2"
Height="180"
Margin="0,8,0,0"
Background="Transparent"
TooltipPosition="Top">
<lvc:CartesianChart.XAxes>
<lvc:AxesCollection>
<lvc:XamlAxis Labels="{x:Bind ViewModel.HourlyXAxisLabels, Mode=OneWay}" TextSize="{StaticResource BodyTextBlockFontSize}" />
</lvc:AxesCollection>
</lvc:CartesianChart.XAxes>
<lvc:CartesianChart.YAxes>
<lvc:AxesCollection>
<lvc:XamlAxis
x:Uid="StatsDashboardControlTrackCountAxis"
NameTextSize="{StaticResource BodyTextBlockFontSize}"
ShowSeparatorLines="False"
TextSize="{StaticResource BodyTextBlockFontSize}" />
</lvc:AxesCollection>
</lvc:CartesianChart.YAxes>
<lvc:CartesianChart.Series>
<lvc:SeriesCollection>
<lvc:XamlColumnSeries
x:Name="HourlySeries"
Rx="4"
Ry="4"
Values="{x:Bind ViewModel.HourlySeriesValues, Mode=OneWay}" />
</lvc:SeriesCollection>
</lvc:CartesianChart.Series>
</lvc:CartesianChart>
</Grid>
</Border>
<!-- Top artists and sources -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Top artists -->
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
<StackPanel>
<TextBlock
x:Uid="StatsDashboardControlTopArtists"
Margin="0,0,0,12"
Style="{ThemeResource SubtitleTextBlockStyle}" />
<ItemsControl ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="statsmodels:ArtistPlayCount">
<Grid Margin="0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Spacing="10">
<PersonPicture
Width="32"
Height="32"
DisplayName="{x:Bind Artist}" />
<TextBlock
VerticalAlignment="Center"
Style="{ThemeResource BodyStrongTextBlockStyle}"
Text="{x:Bind Artist}" />
</StackPanel>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontWeight="SemiBold">
<Run Text="{x:Bind PlayCount}" />
<Run
x:Uid="StatsDashboardControlTrackCountText"
FontSize="10"
FontWeight="Normal"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<!-- Top tracks -->
<Border
Grid.Column="1"
Margin="0,0,0,12"
Style="{StaticResource StatsCardStyle}">
<StackPanel>
<TextBlock
x:Uid="StatsDashboardControlTopSongs"
Margin="0,0,0,12"
Style="{ThemeResource SubtitleTextBlockStyle}" />
<ItemsControl ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="statsmodels:SongPlayCount">
<Grid Margin="0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid
Width="40"
Height="40"
Margin="0,0,12,0"
Background="{ThemeResource LayerFillColorAltBrush}"
CornerRadius="4">
<FontIcon
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xE8D6;" />
</Grid>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
<TextBlock
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind Artist}" />
</StackPanel>
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
FontWeight="SemiBold">
<Run Text="{x:Bind PlayCount}" />
<Run
x:Uid="StatsDashboardControlTrackCountText"
FontSize="10"
FontWeight="Normal"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</Grid>
<!-- 播放源分布 -->
<Border
Grid.Row="3"
Margin="0,0,0,20"
Style="{StaticResource StatsCardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
x:Uid="StatsDashboardControlSources"
Margin="0,0,0,12"
Style="{ThemeResource SubtitleTextBlockStyle}" />
<lvc:PieChart
Grid.Row="1"
MinHeight="250"
Background="Transparent"
LegendPosition="Bottom"
LegendTextSize="{StaticResource BodyTextBlockFontSize}"
Series="{x:Bind ViewModel.SourceSeries, Mode=OneWay}"
TooltipPosition="Center" />
</Grid>
</Border>
</Grid>
</ScrollViewer>
<!--<Button
Grid.Row="2"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Command="{x:Bind ViewModel.GenerateTestDataCommand}"
Content="Generate test data" />-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using BetterLyrics.WinUI3.Enums;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class StatsDashboardControl : UserControl
{
public StatsDashboardControlViewModel ViewModel => (StatsDashboardControlViewModel)DataContext;
public StatsDashboardControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<StatsDashboardControlViewModel>();
}
}

View File

@@ -14,9 +14,8 @@
x:Name="TrayIcon" x:Name="TrayIcon"
x:FieldModifier="public" x:FieldModifier="public"
ContextMenuMode="SecondWindow" ContextMenuMode="SecondWindow"
DoubleClickCommand="{x:Bind ViewModel.OpenLyricsCommand}"
IconSource="ms-appx:///Assets/Logo.ico" IconSource="ms-appx:///Assets/Logo.ico"
LeftClickCommand="{x:Bind ViewModel.OpenLyricsCommand}" LeftClickCommand="{x:Bind ViewModel.OpenLyricsWindowSwitchCommand}"
NoLeftClickDelay="True" NoLeftClickDelay="True"
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}"> ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
<tb:TaskbarIcon.ContextFlyout> <tb:TaskbarIcon.ContextFlyout>
@@ -39,11 +38,6 @@
Command="{x:Bind ViewModel.OpenLyricsSearchWindowCommand}" Command="{x:Bind ViewModel.OpenLyricsSearchWindowCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE721;}" /> Glyph=&#xE721;}" />
<MenuFlyoutItem
x:Uid="SystemTrayLyrics"
Command="{x:Bind ViewModel.OpenLyricsCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE90B;}" />
<MenuFlyoutItem <MenuFlyoutItem
x:Uid="SystemTrayMusicGallery" x:Uid="SystemTrayMusicGallery"
Command="{x:Bind ViewModel.OpenMusicGalleryCommand}" Command="{x:Bind ViewModel.OpenMusicGalleryCommand}"

View File

@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.WindowSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="AppSettingsControlGeneral" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageConfigName" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8AC;}">
<TextBox Text="{x:Bind LyricsWindowStatus.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" />
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageWorkArea"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE78B;}"
IsExpanded="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageWorkAreaHeight" IsEnabled="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<local:ExtendedSlider
Default="64"
Maximum="{x:Bind LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
Unit="px"
Value="{x:Bind LyricsWindowStatus.DockHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockPlacement" IsEnabled="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockMonitor" IsEnabled="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind LyricsWindowStatus.MonitorDeviceName, Mode=TwoWay}" />
<Button
Click="RefreshMonitorButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAdaptEnvColor"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE88F;}"
IsExpanded="{x:Bind LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAdaptToEnvironment, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard
x:Uid="SettingsPageEnvColorSample"
Header="Environment color sample mode"
IsEnabled="{x:Bind LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPagePinToTaskbar"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7C4;}"
IsExpanded="{x:Bind LyricsWindowStatus.IsPinToTaskbar, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsPinToTaskbar, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageTaskbarPlacement">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.TaskbarPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLeft" />
<ComboBoxItem x:Uid="SettingsPageRight" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageAlwaysHideUnlockButton" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE785;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysHideUnlockButton, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageAOT"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE718;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysOnTop, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageForceAlwaysOnTop" IsEnabled="{x:Bind LyricsWindowStatus.IsAlwaysOnTop, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysOnTopPolling, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.AutoShowOrHideWindow, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowInSwitchers" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C4;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsShownInSwitchers, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB41;}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaTop" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaWhole" />
</ComboBox>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,43 @@
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.ObjectModel;
using System.Linq;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class WindowSettingsControl : UserControl
{
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(AlbumArtAreaEffectSettings), typeof(WindowSettingsControl), new PropertyMetadata(default));
public LyricsWindowStatus LyricsWindowStatus
{
get => (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty);
set => SetValue(LyricsWindowStatusProperty, value);
}
public ObservableCollection<string> MonitorDeviceNames { get; set; } = [];
public WindowSettingsControl()
{
InitializeComponent();
MonitorDeviceNames = [.. MonitorHook.GetAllMonitorDeviceNames()];
}
private void RefreshMonitorDeviceNames()
{
MonitorDeviceNames = [.. MonitorHook.GetAllMonitorDeviceNames()];
LyricsWindowStatus.MonitorDeviceName = MonitorDeviceNames.FirstOrDefault() ?? "";
}
private void RefreshMonitorButton_Click(object sender, RoutedEventArgs e)
{
RefreshMonitorDeviceNames();
}
}

View File

@@ -1,5 +1,5 @@
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.ResourceService; using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
using System; using System;
@@ -8,7 +8,7 @@ namespace BetterLyrics.WinUI3.Converter
{ {
public partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter public partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
{ {
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
@@ -16,8 +16,8 @@ namespace BetterLyrics.WinUI3.Converter
{ {
return provider switch return provider switch
{ {
AlbumArtSearchProvider.Local => _resourceService.GetLocalizedString("AlbumArtSearchLocalProvider"), AlbumArtSearchProvider.Local => _localizationService.GetLocalizedString("AlbumArtSearchLocalProvider"),
AlbumArtSearchProvider.SMTC => _resourceService.GetLocalizedString("AlbumArtSearchSMTCProvider"), AlbumArtSearchProvider.SMTC => _localizationService.GetLocalizedString("AlbumArtSearchSMTCProvider"),
AlbumArtSearchProvider.iTunes => "iTunes", AlbumArtSearchProvider.iTunes => "iTunes",
_ => throw new Exception($"Unknown AlbumArtSearchProvider: {provider}"), _ => throw new Exception($"Unknown AlbumArtSearchProvider: {provider}"),
}; };

View File

@@ -17,20 +17,16 @@ namespace BetterLyrics.WinUI3.Converter
using (var ms = new MemoryStream(byteArray)) using (var ms = new MemoryStream(byteArray))
{ {
var stream = ms.AsRandomAccessStream(); var stream = ms.AsRandomAccessStream();
var bitmapImage = new BitmapImage(); var bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream); bitmapImage.SetSource(stream);
return bitmapImage; return bitmapImage;
} }
} }
catch catch { }
{
return PathHelper.AlbumArtPlaceholderPath;
}
} }
return PathHelper.AlbumArtPlaceholderPath; return new BitmapImage(new Uri(PathHelper.AlbumArtPlaceholderPath));
} }
public object ConvertBack(object value, Type targetType, object parameter, string language) public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -11,7 +11,8 @@ namespace BetterLyrics.WinUI3.Converter
{ {
if (value is string langCode) if (value is string langCode)
{ {
return LanguageHelper.SupportedDisplayLanguages.FindIndex(x => x.LanguageCode == langCode); var found = LanguageHelper.SupportedDisplayLanguages.FindIndex(x => x.LanguageCode == langCode);
return found == -1 ? 0 : found;
} }
return 0; return 0;
} }

View File

@@ -0,0 +1,33 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Converter
{
public partial class DoubleToDecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null) return string.Empty;
if (double.TryParse(value.ToString(), out double number))
{
int decimalPlaces = 2;
if (parameter != null && int.TryParse(parameter.ToString(), out int parsedParams))
{
decimalPlaces = parsedParams;
}
return number.ToString($"F{decimalPlaces}");
}
return value.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,27 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class FileSourceTypeToIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is FileSourceType type)
{
return type switch
{
FileSourceType.Local => "\uE8B7", // Folder
FileSourceType.SMB => "\uE839", // Network
FileSourceType.FTP => "\uE838", // Globe
FileSourceType.WebDAV => "\uE753", // Cloud
_ => "\uE8B7"
};
}
return "\uE8B7";
}
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}
}

View File

@@ -11,7 +11,11 @@ namespace BetterLyrics.WinUI3.Converter
{ {
if (value is string langCode) if (value is string langCode)
{ {
if (PhoneticHelper.IsPhoneticCode(langCode)) if (langCode == "N/A")
{
return langCode;
}
else if (PhoneticHelper.IsPhoneticCode(langCode))
{ {
return PhoneticHelper.GetDisplayName(langCode); return PhoneticHelper.GetDisplayName(langCode);
} }

View File

@@ -1,7 +1,7 @@
// 2025/6/23 by Zhe Fang // 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.ResourceService; using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
using System; using System;
@@ -10,7 +10,7 @@ namespace BetterLyrics.WinUI3.Converter
{ {
public partial class LyricsSearchProviderToDisplayNameConverter : IValueConverter public partial class LyricsSearchProviderToDisplayNameConverter : IValueConverter
{ {
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
@@ -24,10 +24,10 @@ namespace BetterLyrics.WinUI3.Converter
LyricsSearchProvider.Kugou => "酷狗音乐", LyricsSearchProvider.Kugou => "酷狗音乐",
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db", LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
LyricsSearchProvider.AppleMusic => "Apple Music", LyricsSearchProvider.AppleMusic => "Apple Music",
LyricsSearchProvider.LocalLrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderLocalLrcFile"), LyricsSearchProvider.LocalLrcFile => _localizationService.GetLocalizedString("LyricsSearchProviderLocalLrcFile"),
LyricsSearchProvider.LocalMusicFile => _resourceService.GetLocalizedString("LyricsSearchProviderLocalMusicFile"), LyricsSearchProvider.LocalMusicFile => _localizationService.GetLocalizedString("LyricsSearchProviderLocalMusicFile"),
LyricsSearchProvider.LocalEslrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderEslrcFile"), LyricsSearchProvider.LocalEslrcFile => _localizationService.GetLocalizedString("LyricsSearchProviderEslrcFile"),
LyricsSearchProvider.LocalTtmlFile => _resourceService.GetLocalizedString("LyricsSearchProviderTtmlFile"), LyricsSearchProvider.LocalTtmlFile => _localizationService.GetLocalizedString("LyricsSearchProviderTtmlFile"),
_ => "N/A", _ => "N/A",
}; };
} }

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang // 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
@@ -10,13 +11,13 @@ namespace BetterLyrics.WinUI3.Converter
{ {
public partial class MatchedLocalFilesPathToVisibilityConverter : IValueConverter public partial class MatchedLocalFilesPathToVisibilityConverter : IValueConverter
{ {
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
if (value is string path) if (value is string path)
{ {
if (path == _resourceService.GetLocalizedString("MainPageNoLocalFilesMatched")) if (path == _localizationService.GetLocalizedString("MainPageNoLocalFilesMatched"))
{ {
return Visibility.Collapsed; return Visibility.Collapsed;
} }

View File

@@ -3,18 +3,37 @@ using System;
namespace BetterLyrics.WinUI3.Converter namespace BetterLyrics.WinUI3.Converter
{ {
public class MillisecondsToFormattedTimeConverter : IValueConverter public partial class MillisecondsToFormattedTimeConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
if (value is int milliseconds) double? milliseconds = null;
if (value is int iVal) milliseconds = iVal;
else if (value is double dVal) milliseconds = dVal;
else if (value is long lVal) milliseconds = lVal;
if (milliseconds.HasValue)
{ {
return TimeSpan.FromMilliseconds(milliseconds).ToString(@"mm\:ss\.fff"); var ts = TimeSpan.FromMilliseconds(milliseconds.Value);
}
else if (value is double doubleMilliseconds) string? format = parameter?.ToString();
{
return TimeSpan.FromMilliseconds(doubleMilliseconds).ToString(@"mm\:ss\.fff"); if (string.IsNullOrEmpty(format))
{
format = @"mm\:ss\.fff";
}
try
{
return ts.ToString(format);
}
catch (FormatException)
{
return ts.ToString();
}
} }
return value?.ToString() ?? ""; return value?.ToString() ?? "";
} }

View File

@@ -3,7 +3,7 @@ using System;
namespace BetterLyrics.WinUI3.Converter namespace BetterLyrics.WinUI3.Converter
{ {
public class MillisecondsToSecondsConverter : IValueConverter public partial class MillisecondsToSecondsConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {

View File

@@ -0,0 +1,31 @@
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace BetterLyrics.WinUI3.Converter
{
public partial class PathToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string targetPath = PathHelper.AlbumArtPlaceholderPath;
if (value is string path)
{
if (File.Exists(path))
{
targetPath = path;
}
}
return new BitmapImage(new Uri(targetPath));
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,7 +1,7 @@
// 2025/6/23 by Zhe Fang // 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.ResourceService; using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
using System; using System;
@@ -10,7 +10,7 @@ namespace BetterLyrics.WinUI3.Converter
{ {
public partial class TranslationSearchProviderToDisplayNameConverter : IValueConverter public partial class TranslationSearchProviderToDisplayNameConverter : IValueConverter
{ {
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
@@ -24,10 +24,10 @@ namespace BetterLyrics.WinUI3.Converter
TranslationSearchProvider.Kugou => "酷狗音乐", TranslationSearchProvider.Kugou => "酷狗音乐",
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db", TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
TranslationSearchProvider.AppleMusic => "Apple Music", TranslationSearchProvider.AppleMusic => "Apple Music",
TranslationSearchProvider.LocalLrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderLocalLrcFile"), TranslationSearchProvider.LocalLrcFile => _localizationService.GetLocalizedString("LyricsSearchProviderLocalLrcFile"),
TranslationSearchProvider.LocalMusicFile => _resourceService.GetLocalizedString("LyricsSearchProviderLocalMusicFile"), TranslationSearchProvider.LocalMusicFile => _localizationService.GetLocalizedString("LyricsSearchProviderLocalMusicFile"),
TranslationSearchProvider.LocalEslrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderEslrcFile"), TranslationSearchProvider.LocalEslrcFile => _localizationService.GetLocalizedString("LyricsSearchProviderEslrcFile"),
TranslationSearchProvider.LocalTtmlFile => _resourceService.GetLocalizedString("LyricsSearchProviderTtmlFile"), TranslationSearchProvider.LocalTtmlFile => _localizationService.GetLocalizedString("LyricsSearchProviderTtmlFile"),
TranslationSearchProvider.LibreTranslate => "LibreTranslate", TranslationSearchProvider.LibreTranslate => "LibreTranslate",
_ => "N/A", _ => "N/A",
}; };

View File

@@ -0,0 +1,42 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class TransliterationSearchProviderToDisplayNameConverter : IValueConverter
{
private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TransliterationSearchProvider provider)
{
return provider switch
{
TransliterationSearchProvider.LrcLib => "LrcLib",
TransliterationSearchProvider.QQ => "QQ 音乐",
TransliterationSearchProvider.Netease => "网易云音乐",
TransliterationSearchProvider.Kugou => "酷狗音乐",
TransliterationSearchProvider.AmllTtmlDb => "amll-ttml-db",
TransliterationSearchProvider.AppleMusic => "Apple Music",
TransliterationSearchProvider.LocalLrcFile => _localizationService.GetLocalizedString("LyricsSearchProviderLocalLrcFile"),
TransliterationSearchProvider.LocalMusicFile => _localizationService.GetLocalizedString("LyricsSearchProviderLocalMusicFile"),
TransliterationSearchProvider.LocalEslrcFile => _localizationService.GetLocalizedString("LyricsSearchProviderEslrcFile"),
TransliterationSearchProvider.LocalTtmlFile => _localizationService.GetLocalizedString("LyricsSearchProviderTtmlFile"),
TransliterationSearchProvider.BetterLyrics => "BetterLyrics",
TransliterationSearchProvider.CutletDocker => "cutlet-docker",
_ => "N/A",
};
}
return "N/A";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,17 +1,18 @@
using ATL; using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Extensions;
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
using System; using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Converter namespace BetterLyrics.WinUI3.Converter
{ {
public partial class TrackToLyricsConverter : IValueConverter public partial class UriStringToDecodedAbsoluteUri : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
if (value is Track track) if (value is string uriString)
{ {
return track.GetRawLyrics(); return uriString.ToDecodedAbsoluteUri();
} }
return ""; return "";
} }

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Enums
{
public enum AutoScanInterval
{
Disabled,
Every15Minutes,
EveryHour,
Every6Hours,
Daily
}
}

View File

@@ -0,0 +1,10 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum FileSourceType
{
Local,
SMB,
FTP,
WebDAV
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum ImageSwitchType
{
Crossfade,
Slide
}
}

View File

@@ -1,8 +0,0 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum LineMaskType
{
Glow,
Highlight,
}
}

View File

@@ -1,11 +0,0 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
public enum LineRenderingType
{
CurrentChar,
LineStartToCurrentChar,
CurrentLine
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsEffectScope
{
LongDurationSyllable,
LineStartToCurrentChar,
}
}

View File

@@ -3,8 +3,6 @@
public enum ShortcutID public enum ShortcutID
{ {
LyricsWindowShowOrHide, LyricsWindowShowOrHide,
Borderless,
ClickThrough,
LyricsWindowSwitch, LyricsWindowSwitch,
PlayOrPauseSong, PlayOrPauseSong,
NextSong, NextSong,

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Enums
{
public enum StatsRange
{
Today,
ThisWeek,
ThisMonth,
ThisQuarter,
ThisYear,
Custom
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum TaskbarPlacement
{
Left,
Right,
}
}

View File

@@ -0,0 +1,18 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum TransliterationSearchProvider
{
QQ,
Kugou,
Netease,
LrcLib,
AmllTtmlDb,
AppleMusic,
LocalMusicFile,
LocalLrcFile,
LocalEslrcFile,
LocalTtmlFile,
BetterLyrics,
CutletDocker
}
}

View File

@@ -0,0 +1,16 @@
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
namespace BetterLyrics.WinUI3.Events
{
public class InteractiveAreaEventArgs : EventArgs
{
public IList<FrameworkElement> Elements { get; set; }
public InteractiveAreaEventArgs(IList<FrameworkElement> elements)
{
Elements = elements;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Events
{
public class TaskbarFreeBoundsChangedEventArgs : EventArgs
{
public Rect TaskbarFreeBounds { get; }
public TaskbarFreeBoundsChangedEventArgs(Rect taskbarBounds)
{
TaskbarFreeBounds = taskbarBounds;
}
}
}

View File

@@ -0,0 +1,123 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BetterLyrics.WinUI3.Extensions
{
public static class LyricsDataExtensions
{
extension(LyricsData lyricsData)
{
public static LyricsData GetLoadingPlaceholder()
{
return new LyricsData()
{
LyricsLines = [
new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = "● ● ●",
},
],
LanguageCode = "N/A",
};
}
public void SetTranslatedText(LyricsData translationData, int toleranceMs = 50)
{
foreach (var line in lyricsData.LyricsLines)
{
// 在翻译歌词中查找与当前行开始时间最接近且在容忍范围内的行
var transLine = translationData.LyricsLines
.FirstOrDefault(t => Math.Abs(t.StartMs - line.StartMs) <= toleranceMs);
if (transLine != null)
{
// 此处 transLine.OriginalText 指翻译中的“原文”属性
line.TranslatedText = transLine.OriginalText;
}
else
{
// 没有匹配的翻译
line.TranslatedText = "";
}
}
}
public void SetPhoneticText(LyricsData phoneticData, int toleranceMs = 50)
{
foreach (var line in lyricsData.LyricsLines)
{
// 在音译歌词中查找与当前行开始时间最接近且在容忍范围内的行
var transLine = phoneticData.LyricsLines
.FirstOrDefault(t => Math.Abs(t.StartMs - line.StartMs) <= toleranceMs);
if (transLine != null)
{
// 此处 transLine.OriginalText 指音译中的“原文”属性
line.PhoneticText = transLine.OriginalText;
}
else
{
// 没有匹配的音译
line.PhoneticText = "";
}
}
}
public void SetTranslation(string translation)
{
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
foreach (var line in lyricsData.LyricsLines)
{
if (i >= translationArr.Count)
{
line.TranslatedText = ""; // No translation available, keep empty
}
else
{
line.TranslatedText = translationArr[i];
}
i++;
}
}
public void SetTransliteration(string transliteration)
{
List<string> transliterationArr = transliteration.Split(StringHelper.NewLine).ToList();
int i = 0;
foreach (var line in lyricsData.LyricsLines)
{
if (i >= transliterationArr.Count)
{
line.PhoneticText = ""; // No transliteration available, keep empty
}
else
{
line.PhoneticText = transliterationArr[i];
}
i++;
}
}
public LyricsLine? GetLyricsLine(double sec)
{
for (int i = 0; i < lyricsData.LyricsLines.Count; i++)
{
var line = lyricsData.LyricsLines[i];
if (line.StartMs > sec * 1000)
{
return lyricsData.LyricsLines.ElementAtOrDefault(i - 1);
}
}
return null;
}
}
}
}

View File

@@ -8,21 +8,6 @@ namespace BetterLyrics.WinUI3.Extensions
{ {
extension(LyricsSearchProvider provider) extension(LyricsSearchProvider provider)
{ {
public string GetCacheDirectory() => provider switch
{
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
LyricsSearchProvider.LocalMusicFile => PathHelper.LocalMusicCacheDirectory,
LyricsSearchProvider.LocalLrcFile => PathHelper.LocalLrcCacheDirectory,
LyricsSearchProvider.LocalEslrcFile => PathHelper.LocalEslrcCacheDirectory,
LyricsSearchProvider.LocalTtmlFile => PathHelper.LocalTtmlCacheDirectory,
_ => throw new ArgumentOutOfRangeException(nameof(provider)),
};
public LyricsFormat GetLyricsFormat() => provider switch public LyricsFormat GetLyricsFormat() => provider switch
{ {
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc, LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
@@ -59,6 +44,21 @@ namespace BetterLyrics.WinUI3.Extensions
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile, LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
_ => null, _ => null,
}; };
public TransliterationSearchProvider? ToTransliterationSearchProvider() => provider switch
{
LyricsSearchProvider.LrcLib => TransliterationSearchProvider.LrcLib,
LyricsSearchProvider.QQ => TransliterationSearchProvider.QQ,
LyricsSearchProvider.Kugou => TransliterationSearchProvider.Kugou,
LyricsSearchProvider.Netease => TransliterationSearchProvider.Netease,
LyricsSearchProvider.AmllTtmlDb => TransliterationSearchProvider.AmllTtmlDb,
LyricsSearchProvider.AppleMusic => TransliterationSearchProvider.AppleMusic,
LyricsSearchProvider.LocalMusicFile => TransliterationSearchProvider.LocalMusicFile,
LyricsSearchProvider.LocalLrcFile => TransliterationSearchProvider.LocalLrcFile,
LyricsSearchProvider.LocalEslrcFile => TransliterationSearchProvider.LocalEslrcFile,
LyricsSearchProvider.LocalTtmlFile => TransliterationSearchProvider.LocalTtmlFile,
_ => null,
};
} }
} }
} }

View File

@@ -1,27 +1,30 @@
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings; using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.ResourceService; using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Windows.Foundation; using Windows.Foundation;
namespace BetterLyrics.WinUI3.Extensions namespace BetterLyrics.WinUI3.Extensions
{ {
public static class LyricsWindowStatusExtensions public static class LyricsWindowStatusExtensions
{ {
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private static readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public static LyricsWindowStatus DesktopMode() public static LyricsWindowStatus DesktopMode(Window? window = null)
{ {
return new LyricsWindowStatus window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{ {
Name = _resourceService.GetLocalizedString("DesktopMode"), Name = _localizationService.GetLocalizedString("DesktopMode"),
LyricsDisplayType = LyricsDisplayType.LyricsOnly, LyricsDisplayType = LyricsDisplayType.LyricsOnly,
WindowBounds = new Rect(100, 100, 600, 250), WindowBounds = new Rect(100, 100, 600, 250),
IsLocked = true,
IsAlwaysOnTop = true, IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true, IsAlwaysOnTopPolling = true,
IsBorderless = true,
IsClickThrough = true,
IsAdaptToEnvironment = true, IsAdaptToEnvironment = true,
IsShownInSwitchers = false, IsShownInSwitchers = false,
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge, EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
@@ -36,20 +39,19 @@ namespace BetterLyrics.WinUI3.Extensions
}; };
} }
public static LyricsWindowStatus DockedMode() public static LyricsWindowStatus DockedMode(Window? window = null)
{ {
var status = new LyricsWindowStatus window ??= WindowHook.GetWindow<SystemTrayWindow>();
var status = new LyricsWindowStatus(window)
{ {
Name = _resourceService.GetLocalizedString("DockedMode"), Name = _localizationService.GetLocalizedString("DockedMode"),
IsWorkArea = true, IsWorkArea = true,
IsAlwaysOnTop = true, IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true, IsAlwaysOnTopPolling = true,
IsBorderless = true,
IsAdaptToEnvironment = true, IsAdaptToEnvironment = true,
IsShownInSwitchers = false, IsShownInSwitchers = false,
LyricsDisplayType = LyricsDisplayType.LyricsOnly, LyricsDisplayType = LyricsDisplayType.LyricsOnly,
EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow, EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow,
TitleBarArea = TitleBarArea.None,
LyricsStyleSettings = new LyricsStyleSettings LyricsStyleSettings = new LyricsStyleSettings
{ {
LyricsAlignmentType = TextAlignmentType.Center, LyricsAlignmentType = TextAlignmentType.Center,
@@ -64,19 +66,18 @@ namespace BetterLyrics.WinUI3.Extensions
return status; return status;
} }
public static LyricsWindowStatus FullscreenMode() public static LyricsWindowStatus FullscreenMode(Window? window = null)
{ {
var status = new LyricsWindowStatus window ??= WindowHook.GetWindow<SystemTrayWindow>();
var status = new LyricsWindowStatus(window)
{ {
Name = _resourceService.GetLocalizedString("FullscreenMode"), Name = _localizationService.GetLocalizedString("FullscreenMode"),
IsBorderless = true,
IsAlwaysOnTop = true,
TitleBarArea = TitleBarArea.None,
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical, LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
LyricsStyleSettings = new LyricsStyleSettings LyricsStyleSettings = new LyricsStyleSettings
{ {
LyricsAlignmentType = TextAlignmentType.Center, LyricsAlignmentType = TextAlignmentType.Center,
}, },
IsFullscreen = true,
}; };
status.WindowBounds = new Rect( status.WindowBounds = new Rect(
status.MonitorBounds.X, status.MonitorBounds.X,
@@ -87,22 +88,49 @@ namespace BetterLyrics.WinUI3.Extensions
return status; return status;
} }
public static LyricsWindowStatus StandardMode() public static LyricsWindowStatus StandardMode(Window? window = null)
{ {
return new LyricsWindowStatus window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{ {
Name = _resourceService.GetLocalizedString("StandardMode"), Name = _localizationService.GetLocalizedString("StandardMode"),
}; };
} }
public static LyricsWindowStatus NarrowMode() public static LyricsWindowStatus NarrowMode(Window? window = null)
{ {
return new LyricsWindowStatus window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{ {
Name = _resourceService.GetLocalizedString("NarrowMode"), Name = _localizationService.GetLocalizedString("NarrowMode"),
WindowBounds = new Rect(100, 100, 400, 800), WindowBounds = new Rect(100, 100, 400, 800),
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical, LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
}; };
} }
public static LyricsWindowStatus TaskbarMode(Window? window = null)
{
window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{
Name = _localizationService.GetLocalizedString("TaskbarMode"),
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
IsPinToTaskbar = true,
IsLocked = true,
IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true,
IsAdaptToEnvironment = true,
IsShownInSwitchers = false,
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
LyricsStyleSettings = new()
{
LyricsAlignmentType = TextAlignmentType.Center,
},
LyricsBackgroundSettings = new LyricsBackgroundSettings
{
IsFluidOverlayEnabled = false,
}
};
}
} }
} }

View File

@@ -8,6 +8,12 @@ namespace BetterLyrics.WinUI3.Extensions
extension(Point point) extension(Point point)
{ {
public PointInt32 ToPointInt32() => new((int)point.X, (int)point.Y); public PointInt32 ToPointInt32() => new((int)point.X, (int)point.Y);
public Point AddX(double deltaX) => new(point.X + deltaX, point.Y);
public Point AddY(double deltaY) => new(point.X, point.Y + deltaY);
public Point WithX(double x) => new(x, point.Y);
public Point WithY(double y) => new(point.X, y);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using Windows.Foundation; using Vanara.PInvoke;
using Windows.Foundation;
using Windows.Graphics; using Windows.Graphics;
namespace BetterLyrics.WinUI3.Extensions namespace BetterLyrics.WinUI3.Extensions
@@ -77,5 +78,15 @@ namespace BetterLyrics.WinUI3.Extensions
); );
} }
} }
extension(RECT rect)
{
public Rect ToRect() => new(
rect.Left,
rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top
);
}
} }
} }

View File

@@ -0,0 +1,18 @@
using System.Drawing;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Extensions
{
public static class RectangleExtensions
{
extension(Rectangle rect)
{
public Rect ToRect() => new(
rect.Left,
rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top
);
}
}
}

View File

@@ -1,14 +1,18 @@
using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models;
using System;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
namespace BetterLyrics.WinUI3.Extensions namespace BetterLyrics.WinUI3.Extensions
{ {
public static class SongInfoExtensions public static class SongInfoExtensions
{ {
public static SongInfo Placeholder => new SongInfo public static SongInfo Placeholder => new()
{ {
Title = "N/A", Title = "N/A",
Album = "N/A", Album = "N/A",
Artists = ["N/A"], Artist = "N/A",
}; };
extension(SongInfo songInfo) extension(SongInfo songInfo)
@@ -19,9 +23,9 @@ namespace BetterLyrics.WinUI3.Extensions
return songInfo; return songInfo;
} }
public SongInfo WithArtist(string[] value) public SongInfo WithArtist(string value)
{ {
songInfo.Artists = value; songInfo.Artist = value;
return songInfo; return songInfo;
} }
@@ -30,6 +34,39 @@ namespace BetterLyrics.WinUI3.Extensions
songInfo.Album = value; songInfo.Album = value;
return songInfo; return songInfo;
} }
public PlayHistoryItem? ToPlayHistoryItem(double actualPlayedMs)
{
if (songInfo == null) return null;
return new PlayHistoryItem
{
Title = songInfo.Title,
Artist = songInfo.Artist,
Album = songInfo.Album,
PlayerId = songInfo.PlayerId ?? "N/A",
TotalDurationMs = songInfo.DurationMs,
DurationPlayedMs = actualPlayedMs,
StartedAt = DateTime.FromBinary(songInfo.StartedAt)
};
}
public string GetCacheKey()
{
string title = songInfo.Title?.Trim() ?? "";
string album = songInfo.Album?.Trim() ?? "";
string artists = songInfo.Artist?.Trim() ?? "";
long seconds = (long)Math.Round(songInfo.Duration);
string durationPart = seconds.ToString(CultureInfo.InvariantCulture);
string rawKey = $"{title}|{artists}|{album}|{durationPart}";
using var sha256 = SHA256.Create();
byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(rawKey));
return Convert.ToHexString(bytes);
}
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using System;
using System.Linq; using System.Linq;
namespace BetterLyrics.WinUI3.Extensions namespace BetterLyrics.WinUI3.Extensions
@@ -75,6 +76,18 @@ namespace BetterLyrics.WinUI3.Extensions
return null; return null;
} }
} }
public string ToDecodedAbsoluteUri()
{
if (string.IsNullOrEmpty(str)) return "";
try
{
var u = new Uri(str);
return u.IsFile ? u.LocalPath : System.Net.WebUtility.UrlDecode(u.AbsoluteUri);
}
catch { return str; }
}
} }
} }
} }

View File

@@ -1,24 +0,0 @@
using ATL;
using System.IO;
namespace BetterLyrics.WinUI3.Extensions
{
public static class TrackExtensions
{
extension(Track track)
{
public string GetParentFolderName() => Directory.GetParent(track.Path)?.Name ?? "";
public string GetParentFolderPath() => Directory.GetParent(track.Path)?.FullName ?? "";
public string GetRawLyrics()
{
if (track.Path is string path)
{
return TagLib.File.Create(path).Tag.Lyrics;
}
return "";
}
}
}
}

View File

@@ -1,6 +1,6 @@
using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.ResourceService; using BetterLyrics.WinUI3.Services.LocalizationService;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
@@ -9,16 +9,24 @@ namespace BetterLyrics.WinUI3.Extensions
{ {
public static class WindowExtensions public static class WindowExtensions
{ {
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>(); private static readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
extension(Window window) extension(Window window)
{ {
public void Init( public void Init(
string titleKey, string titleKey = "",
string title = "",
TitleBarHeightOption titleBarHeightOption = TitleBarHeightOption.Standard, TitleBarHeightOption titleBarHeightOption = TitleBarHeightOption.Standard,
BackdropType backdropType = BackdropType.DesktopAcrylic) BackdropType backdropType = BackdropType.DesktopAcrylic)
{ {
window.Title = _resourceService.GetLocalizedString(titleKey); if (titleKey != "")
{
window.Title = _localizationService.GetLocalizedString(titleKey);
}
if (title != "")
{
window.Title = title;
}
window.AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode; window.AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode;
window.AppWindow.SetIcons(); window.AppWindow.SetIcons();

View File

@@ -4,6 +4,7 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Hooks; using BetterLyrics.WinUI3.Hooks;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing.Imaging; using System.Drawing.Imaging;
@@ -15,6 +16,16 @@ namespace BetterLyrics.WinUI3.Helper
{ {
public static class ColorHelper public static class ColorHelper
{ {
public static Color GetSystemAccentColor()
{
if (Application.Current.Resources.TryGetValue("SystemAccentColor", out var resource) &&
resource is Color uiColor)
{
return uiColor;
}
return Color.FromArgb(255, 0, 120, 215);
}
public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor) public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor)
{ {
// 计算亮度YIQ公式 // 计算亮度YIQ公式

View File

@@ -5,8 +5,11 @@ using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization; using BetterLyrics.WinUI3.Serialization;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Ude; using Ude;
namespace BetterLyrics.WinUI3.Helper namespace BetterLyrics.WinUI3.Helper
@@ -27,6 +30,18 @@ namespace BetterLyrics.WinUI3.Helper
return Encoding.GetEncoding(encoding); return Encoding.GetEncoding(encoding);
} }
public static async Task CopyFileAsync(string sourcePath, string destinationPath)
{
var dir = Path.GetDirectoryName(destinationPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await sourceStream.CopyToAsync(destinationStream);
}
}
public static string SanitizeFileName(string fileName, char replacement = '_') public static string SanitizeFileName(string fileName, char replacement = '_')
{ {
var invalidChars = Path.GetInvalidFileNameChars(); var invalidChars = Path.GetInvalidFileNameChars();
@@ -38,22 +53,6 @@ namespace BetterLyrics.WinUI3.Helper
return sb.ToString(); return sb.ToString();
} }
public static LyricsSearchResult? ReadLyricsCache(SongInfo songInfo, LyricsSearchProvider lyricsSearchProvider)
{
var cacheFilePath = Path.Combine(
lyricsSearchProvider.GetCacheDirectory(),
SanitizeFileName($"{songInfo.ToFileName()}.json"));
if (File.Exists(cacheFilePath))
{
var json = File.ReadAllText(cacheFilePath);
var data = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LyricsSearchResult);
data?.SelfPath = cacheFilePath;
return data;
}
return null;
}
public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath) public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
{ {
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}")); var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
@@ -64,19 +63,9 @@ namespace BetterLyrics.WinUI3.Helper
return null; return null;
} }
public static void WriteLyricsCache(SongInfo songInfo, LyricsSearchResult lyricsSearchResult)
{
var cacheFilePath = Path.Combine(
lyricsSearchResult.Provider.GetCacheDirectory(),
SanitizeFileName($"{songInfo.ToFileName()}.json"));
lyricsSearchResult.SelfPath = cacheFilePath;
var json = System.Text.Json.JsonSerializer.Serialize(lyricsSearchResult, SourceGenerationContext.Default.LyricsSearchResult);
File.WriteAllText(cacheFilePath, json);
}
public static void WriteAlbumArtCache(SongInfo songInfo, byte[] img, string format, string cacheFolderPath) public static void WriteAlbumArtCache(SongInfo songInfo, byte[] img, string format, string cacheFolderPath)
{ {
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.DisplayArtists} - {songInfo.Album}{format}")); var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.Artist} - {songInfo.Album}{format}"));
File.WriteAllBytes(cacheFilePath, img); File.WriteAllBytes(cacheFilePath, img);
} }
@@ -86,5 +75,15 @@ namespace BetterLyrics.WinUI3.Helper
".wav", ".aiff", ".aif", ".pcm", ".cda", ".dsf", ".dff", ".au", ".snd", ".wav", ".aiff", ".aif", ".pcm", ".cda", ".dsf", ".dff", ".au", ".snd",
".mid", ".midi", ".mod", ".xm", ".it", ".s3m" ".mid", ".midi", ".mod", ".xm", ".it", ".s3m"
}; };
public static readonly string[] LyricExtensions =
Enum.GetValues(typeof(LyricsSearchProvider)).Cast<LyricsSearchProvider>()
.Where(x => x.IsLocal())
.Select(x => x.GetLyricsFormat())
.Where(x => x != LyricsFormat.NotSpecified)
.Select(x => x.ToFileExtension())
.ToArray();
public static readonly HashSet<string> AllSupportedExtensions = new(MusicExtensions.Union(LyricExtensions));
} }
} }

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using BetterLyrics.WinUI3.Models;
public static class FolderTreeBuilder
{
public static ObservableCollection<FolderNode> Build(List<ExtendedTrack> tracks, List<MediaFolder> folderConfigs)
{
var rootNodes = new ObservableCollection<FolderNode>();
// 按 MediaFolderId 分组
var folderGroups = tracks.GroupBy(t => t.MediaFolderId);
foreach (var group in folderGroups)
{
var config = folderConfigs.FirstOrDefault(f => f.Id == group.Key);
if (config == null) continue;
string baseUri = config.GetStandardUri().AbsoluteUri.TrimEnd('/');
var rootNode = new FolderNode
{
SourceType = config.SourceType,
FolderName = config.Name ?? config.ConnectionSummary, // 显示用户自定义的名字
MediaFolderId = group.Key,
FolderPath = baseUri,
IsExpanded = true
};
foreach (var track in group)
{
try
{
if (!track.Uri.StartsWith(baseUri)) continue; // 防御性编程
string relativePart = track.Uri.Substring(baseUri.Length);
var segments = relativePart
.Split('/', StringSplitOptions.RemoveEmptyEntries)
.Select(s => System.Net.WebUtility.UrlDecode(s))
.ToArray();
if (segments.Length > 1) // 长度大于1说明在子文件夹里
{
var folderSegments = segments.Take(segments.Length - 1).ToArray();
CreateFolderStructure(rootNode, folderSegments, baseUri);
}
}
catch { }
}
rootNodes.Add(rootNode);
}
return rootNodes;
}
private static void CreateFolderStructure(FolderNode parent, string[] segments, string rootBaseUri)
{
var current = parent;
string currentFullPath = parent.FolderPath;
foreach (var segmentName in segments)
{
var existingChild = current.SubFolders.FirstOrDefault(f => f.FolderName == segmentName);
currentFullPath += "/" + System.Net.WebUtility.UrlEncode(segmentName);
if (existingChild == null)
{
var newFolder = new FolderNode
{
FolderName = segmentName,
FolderPath = currentFullPath, // 存完整的 URI
MediaFolderId = parent.MediaFolderId
};
current.SubFolders.Add(newFolder);
current = newFolder;
}
else
{
current = existingChild;
currentFullPath = existingChild.FolderPath;
}
}
}
}

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