Compare commits

..

460 Commits
1.6.5 ... 1.7.4

Author SHA1 Message Date
Lightczx
f6ec0f5d7e 1.7.4 package 2023-09-12 15:03:14 +08:00
Masterain
7e73710103 New translations SH.resx (English) 2023-09-12 14:50:41 +08:00
Masterain
4ba9f32ac3 New translations SH.resx (English) 2023-09-12 14:50:41 +08:00
Masterain
6cb2818161 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:41 +08:00
Masterain
34d291001b New translations SH.resx (Korean) 2023-09-12 14:50:40 +08:00
Masterain
157f64fb47 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
6b9479d27e New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
ea6cbb8d6b New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
9f94f36cf8 New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
c50dd0737c New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:40 +08:00
Masterain
6f63ed55d7 New translations SH.resx (Korean) 2023-09-12 14:50:40 +08:00
Masterain
4cc16153c2 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
a214dd331a New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
e6c83390eb New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
aa21e3e5e9 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
58c992e278 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
bd19bfdd52 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
7a9c1a7b3c New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
c5a24ae0d7 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:40 +08:00
Masterain
6ee2899692 New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
9b3ea4496a New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:40 +08:00
Masterain
96ac468a24 New translations SH.resx (Korean) 2023-09-12 14:50:40 +08:00
Masterain
0a26297446 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
de2cafd9e5 New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
3f8674ad9a New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:40 +08:00
Masterain
8e1ea87993 New translations SH.resx (Korean) 2023-09-12 14:50:40 +08:00
Masterain
cdbe013310 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
a972ba0035 New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
a285dbb9b9 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
39182ce44c New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
95ad0ca574 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:40 +08:00
Masterain
3ec905f5ca New translations SH.resx (Korean) 2023-09-12 14:50:40 +08:00
Masterain
4306461ba9 New translations SH.resx (Japanese) 2023-09-12 14:50:40 +08:00
Masterain
133e767be3 New translations SH.resx (English) 2023-09-12 14:50:40 +08:00
Masterain
949cf59177 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:40 +08:00
Masterain
42e693ba8a New translations SH.resx (Korean) 2023-09-12 14:50:40 +08:00
Masterain
09d00f25f6 New translations SH.resx (Japanese) 2023-09-12 14:50:39 +08:00
Masterain
4f9828f358 New translations SH.resx (English) 2023-09-12 14:50:39 +08:00
Masterain
4f7cf08285 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:39 +08:00
Masterain
46a36a4d17 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:39 +08:00
Masterain
ef1c31732c New translations SH.resx (Korean) 2023-09-12 14:50:39 +08:00
Masterain
26a392e376 New translations SH.resx (Japanese) 2023-09-12 14:50:39 +08:00
Masterain
2731abc815 New translations SH.resx (English) 2023-09-12 14:50:39 +08:00
Masterain
c8fb7fe82a New translations SH.resx (English) 2023-09-12 14:50:39 +08:00
Masterain
d4f6cf8e02 New translations SH.resx (Chinese Traditional) 2023-09-12 14:50:39 +08:00
Masterain
a3c4965932 New translations SH.resx (Korean) 2023-09-12 14:50:39 +08:00
Masterain
e30a010931 New translations SH.resx (Japanese) 2023-09-12 14:50:39 +08:00
Masterain
8af117ea11 New translations SH.resx (English) 2023-09-12 14:50:39 +08:00
Lightczx
5f6d279dba Update SignInClient.cs 2023-09-12 14:50:39 +08:00
Lightczx
aed1171933 fix signin challenge source 2023-09-12 14:50:39 +08:00
DismissedLight
dbc81f0a93 UIIF definition 2023-09-12 14:50:39 +08:00
DismissedLight
1c7b926d76 drop AdaptiveGridView 2023-09-12 14:50:39 +08:00
Lightczx
ab2d38b142 overwrite icon when creating shortcut 2023-09-12 14:50:39 +08:00
DismissedLight
87f7e22022 fix cultivate project selection on other page 2023-09-12 14:50:39 +08:00
DismissedLight
7af57118bc improve db layer & homecard experience 2023-09-12 14:50:39 +08:00
DismissedLight
5d03f5d0b5 fix #917 2023-09-12 14:50:39 +08:00
DismissedLight
84f629c113 batch add for avatarinfo cultivation 2023-09-12 14:50:39 +08:00
DismissedLight
ecd535f9a0 boost up gacha statistics view speed 2x 2023-09-12 14:50:39 +08:00
Lightczx
d3ea7cf8ac fix bilibili server convert 2023-09-12 14:50:39 +08:00
DismissedLight
12afac8d1a fix #907 2023-09-12 14:50:39 +08:00
DismissedLight
5763f56ab2 fix #887 2023-09-12 14:50:39 +08:00
DismissedLight
a8b679acbf fix #904 2023-09-12 14:50:39 +08:00
Lightczx
25987ba5b3 add ability to create desktop shortcut 2023-09-12 14:50:39 +08:00
Lightczx
b0626a0d60 Add ability to switch powershell instance 2023-09-12 14:50:39 +08:00
Lightczx
fd16521c94 update metadata models 2023-09-12 14:50:38 +08:00
DismissedLight
eb077cc8d6 fix banner typo 2023-09-12 14:50:38 +08:00
DismissedLight
28813444bf Merge pull request #921 from Masterain98/main 2023-09-11 11:16:44 +08:00
Masterain
c24cca58bf Update issue templates 2023-09-10 20:11:53 -07:00
DismissedLight
4da0258f7b update banner image 2023-09-05 21:24:20 +08:00
DismissedLight
0c98d4c2a1 Merge branch 'develop' 2023-09-04 23:39:37 +08:00
DismissedLight
85e718947c 1.7.3 hotfix package 2023-09-04 23:38:49 +08:00
DismissedLight
328bca597f 1.7.3 hotfix package 2023-09-04 23:38:17 +08:00
DismissedLight
94b957ed20 fix a issue where taskbaricon launchgame will open mainwindow 2023-09-04 23:38:17 +08:00
DismissedLight
490df104f0 elevation override 2023-09-04 23:38:17 +08:00
Lightczx
b8a7ecdd40 add reorderable collection 2023-09-04 23:38:17 +08:00
Lightczx
14f1b43e08 fix #891 2023-09-04 23:38:17 +08:00
dependabot[bot]
c93215320c Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).

- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.7.1...v17.7.2)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 23:38:17 +08:00
Lightczx
da6534d36c fix #881 v2.2 UIGF always throw on first item 2023-09-04 23:38:16 +08:00
DismissedLight
b9306bc213 add id for uigf import in-compat 2023-09-04 23:38:16 +08:00
DismissedLight
4ad031015b migrate to was 1.4 2023-09-04 23:38:16 +08:00
DismissedLight
2080df1dc0 fix passport client oversea creation 2023-09-04 23:38:16 +08:00
DismissedLight
cc41037a2b fix a issue where taskbaricon launchgame will open mainwindow 2023-09-04 23:27:01 +08:00
DismissedLight
2316a5beaf elevation override 2023-09-04 22:34:09 +08:00
Lightczx
73d173613a add reorderable collection 2023-09-04 17:15:34 +08:00
Lightczx
ad10a758bd fix #891 2023-09-04 15:46:58 +08:00
DismissedLight
bf66cf96d8 Merge pull request #894 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-390ba13de5
Bump the packages group in /src/Snap.Hutao with 1 update
2023-09-04 15:21:50 +08:00
dependabot[bot]
2a439664c4 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).

- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.7.1...v17.7.2)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 07:17:44 +00:00
Lightczx
04e6f36111 fix #881 v2.2 UIGF always throw on first item 2023-09-04 11:07:30 +08:00
DismissedLight
f254f2d437 add id for uigf import in-compat 2023-09-03 23:33:15 +08:00
DismissedLight
1c9cbc2082 migrate to was 1.4 2023-09-03 13:26:33 +08:00
DismissedLight
2cc8435115 1.7.2 hotfix package 2023-09-03 13:24:48 +08:00
DismissedLight
167dc906d4 fix passport client oversea creation 2023-09-03 10:00:51 +08:00
DismissedLight
945a53a737 1.7.2 hotfix package 2023-09-02 22:27:11 +08:00
DismissedLight
38747298b5 fix hutao cloud fetch merge and remove archive crash 2023-09-02 22:12:37 +08:00
DismissedLight
1594edc16c 1.7.1 hotfix pacakge 2023-09-02 20:42:25 +08:00
DismissedLight
7a5dec4291 fix #882 gachalog import & aggressive merge refresh 2023-09-02 20:24:33 +08:00
DismissedLight
300e99a9ae add options to disable metadata initialization 2023-09-02 15:22:14 +08:00
DismissedLight
97bd2b5af9 list item alternating 2023-09-02 13:24:14 +08:00
DismissedLight
a464cc1f33 1.7.0 package 2023-09-01 20:07:05 +08:00
DismissedLight
8a447813d7 Merge pull request #877 from DGP-Studio/develop
1.7.0 Stable
2023-09-01 19:52:57 +08:00
Masterain
ec0cbc614d Merge pull request #874 from DGP-Studio/l10n_develop
New Crowdin updates
2023-09-01 02:54:34 -07:00
Masterain
4c5e6984d7 New translations SH.resx (Japanese) 2023-09-01 02:53:41 -07:00
Masterain
c5bbbbdfe3 New translations SH.resx (English) 2023-09-01 02:15:39 -07:00
Lightczx
9f9f2a9d14 Update GachaLog.png 2023-09-01 13:50:27 +08:00
Masterain
a7640fdcb3 New translations SH.resx (English) 2023-08-31 16:46:17 -07:00
DismissedLight
2bec008381 fix announcement padding 2023-08-31 23:50:36 +08:00
DismissedLight
3ada42c927 announcement 2023-08-31 23:47:10 +08:00
Lightczx
bbed07b2ea add announcement 2023-08-31 17:29:50 +08:00
Lightczx
d90ce0afe0 improve achievement & gachalog large dataset load speed 2023-08-31 10:23:25 +08:00
Lightczx
4e0d83726e segment abstraction 2023-08-30 15:51:59 +08:00
Masterain
2b53ffd4d2 Merge pull request #867 from DGP-Studio/l10n_develop
New Crowdin updates
2023-08-30 00:47:50 -07:00
Masterain
84c1fbd13c New translations SH.resx (English) 2023-08-30 00:06:31 -07:00
Masterain
4077c85be4 New translations SH.resx (Chinese Traditional) 2023-08-30 00:06:30 -07:00
Lightczx
948987e31a fix gachalog and cultivation view dispose pipeline 2023-08-30 15:05:51 +08:00
Lightczx
686d2bd3b8 attempt to fix cultivation selection 2023-08-30 14:34:44 +08:00
Masterain
552fb131ed New translations SH.resx (Japanese) 2023-08-29 21:46:19 -07:00
Masterain
cb1fb96cf8 New translations SH.resx (English) 2023-08-29 21:46:18 -07:00
Masterain
7e37d0a008 New translations SH.resx (Chinese Traditional) 2023-08-29 21:46:17 -07:00
Masterain
3abe1d25cd New translations SH.resx (Korean) 2023-08-29 21:46:16 -07:00
Lightczx
00c13ec333 fix gacha statistic pull prediction view 2023-08-30 12:37:46 +08:00
Masterain
c3246da5dd New translations SH.resx (English) 2023-08-29 10:36:54 -07:00
Masterain
79ade22f94 New translations SH.resx (Chinese Traditional) 2023-08-29 08:15:48 -07:00
DismissedLight
9d196c7c14 dailynote text no wrap 2023-08-29 22:47:09 +08:00
Masterain
3949e32324 New translations SH.resx (Japanese) 2023-08-29 07:13:32 -07:00
Masterain
c04b473494 New translations SH.resx (English) 2023-08-29 07:13:31 -07:00
Masterain
a8437b90fc New translations SH.resx (Chinese Traditional) 2023-08-29 07:13:29 -07:00
Masterain
c667623c06 New translations SH.resx (Korean) 2023-08-29 07:13:28 -07:00
DismissedLight
e62a386ac8 fix minor bugs 2023-08-29 21:23:26 +08:00
Masterain
7407bd44ce New translations SH.resx (English) 2023-08-29 04:05:20 -07:00
Masterain
aaea81022f New translations SH.resx (Chinese Traditional) 2023-08-29 04:05:19 -07:00
Masterain
d99b7b46c7 New translations SH.resx (Korean) 2023-08-29 04:05:17 -07:00
Lightczx
a000b3a10e fix guide language text 2023-08-29 17:07:58 +08:00
Lightczx
d06cd53ac4 correct reward time 2023-08-29 17:06:09 +08:00
Masterain
0b8c3fd225 Merge pull request #862 from DGP-Studio/l10n_develop
New Crowdin updates
2023-08-29 02:05:29 -07:00
Masterain
6c267b51d3 New translations SH.resx (English) 2023-08-29 02:02:10 -07:00
Masterain
0140c85d07 New translations SH.resx (Chinese Traditional) 2023-08-29 02:02:09 -07:00
Masterain
9129ff88fd New translations SH.resx (English) 2023-08-29 01:33:17 -07:00
Masterain
a123325c71 New translations SH.resx (Chinese Traditional) 2023-08-29 01:33:16 -07:00
Masterain
cb9e4c5c04 New translations SH.resx (Korean) 2023-08-29 01:33:14 -07:00
Masterain
24a186e254 New translations SH.resx (Japanese) 2023-08-29 01:33:13 -07:00
Lightczx
604c51e064 fix wiki avatar & weapon select item in view 2023-08-29 15:31:20 +08:00
Lightczx
088f915a54 1.7.0 RC 3 2023-08-29 15:12:14 +08:00
Lightczx
5fdbcfc2e8 update dependencies 2023-08-29 11:06:03 +08:00
Lightczx
bcaeb86fe7 Merge branch 'feature/guide' into develop 2023-08-29 10:46:53 +08:00
Lightczx
c83db8695f guide complete 2023-08-29 10:45:53 +08:00
DismissedLight
c2319cac68 add missing localizible string 2023-08-29 09:10:17 +08:00
DismissedLight
cd3ce6d338 fix #815 2023-08-29 09:10:17 +08:00
Masterain
0bdc5d6c54 New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
f007f9b193 New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
90ea0f7276 New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
af5c4c258d New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
eaa50df837 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:17 +08:00
Masterain
04ea760fba New translations SH.resx (Korean) 2023-08-29 09:10:17 +08:00
Masterain
57c0016839 New translations SH.resx (Japanese) 2023-08-29 09:10:17 +08:00
Masterain
3f019e0d23 New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
09e94f5ccc New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
e5f295fae1 New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
ae8da285ab New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:17 +08:00
Masterain
50b244f76c New translations SH.resx (Korean) 2023-08-29 09:10:17 +08:00
Masterain
171add072c New translations SH.resx (Japanese) 2023-08-29 09:10:17 +08:00
Masterain
16ea8ea5fb New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
18bce39e2a New translations SH.resx (English) 2023-08-29 09:10:17 +08:00
Masterain
d8712c45b4 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:17 +08:00
Masterain
325800602c New translations SH.resx (Korean) 2023-08-29 09:10:17 +08:00
Masterain
71bbf20c12 New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
a55ef96a79 New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
f27308c12d New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
a8877d93b8 New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
4605df8f03 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:16 +08:00
Masterain
3a26c7a3ed New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
e648080d8b New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
60585f90ed New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
f104823d04 New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
29c6e20738 New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
f922bd6c2f New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:16 +08:00
Masterain
76b2921847 New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
6a41ace38d New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
40cd084304 New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
f84e5f2f1b New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:16 +08:00
Masterain
b9d7987605 New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
ddf272d8f4 New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
7c7b959916 New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
260aa8797f New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:16 +08:00
Masterain
e551072e52 New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
f3a271dd48 New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
f8d804eb4e New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
4271bdb23b New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:16 +08:00
Masterain
0f96a6d8da New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
f9185b519e New translations SH.resx (Japanese) 2023-08-29 09:10:16 +08:00
Masterain
59f5095432 New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
19d4b8f25d New translations SH.resx (English) 2023-08-29 09:10:16 +08:00
Masterain
0026add54b New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:16 +08:00
Masterain
0c6789c71d New translations SH.resx (Korean) 2023-08-29 09:10:16 +08:00
Masterain
1d45c75584 New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
b4d58405e0 New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
85ee37926e New translations SH.resx (English) 2023-08-29 09:10:15 +08:00
Masterain
3b411c6949 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:15 +08:00
Masterain
361cd850da New translations SH.resx (English) 2023-08-29 09:10:15 +08:00
Masterain
3ea59a0352 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:15 +08:00
Masterain
9abe39e806 New translations SH.resx (Korean) 2023-08-29 09:10:15 +08:00
Masterain
47fcd6f851 New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
8f83853a8f New translations SH.resx (English) 2023-08-29 09:10:15 +08:00
Masterain
f5300745b0 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:15 +08:00
Masterain
43f65db1b6 New translations SH.resx (Korean) 2023-08-29 09:10:15 +08:00
Masterain
d2d1e73891 New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
4bf1d8513d New translations SH.resx (English) 2023-08-29 09:10:15 +08:00
Masterain
6006e79923 New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:15 +08:00
Masterain
2cd5d0c622 New translations SH.resx (Korean) 2023-08-29 09:10:15 +08:00
Masterain
f2d6a2627c New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
7b6c340348 New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
5e5f5398fb New translations SH.resx (English) 2023-08-29 09:10:15 +08:00
Masterain
bb042a1498 New translations SH.resx (English) 2023-08-29 09:10:15 +08:00
Masterain
9db1fc360b New translations SH.resx (Chinese Traditional) 2023-08-29 09:10:15 +08:00
Masterain
4a3676d549 New translations SH.resx (Korean) 2023-08-29 09:10:15 +08:00
Masterain
fdbf7ecf90 New translations SH.resx (Japanese) 2023-08-29 09:10:15 +08:00
Masterain
a9712fa6df New translations SH.resx (English) 2023-08-28 09:34:09 -07:00
Masterain
a3e105db7b New translations SH.resx (English) 2023-08-28 07:58:06 -07:00
Masterain
94fe2016f8 New translations SH.resx (Chinese Traditional) 2023-08-28 07:58:05 -07:00
Masterain
baab021a42 New translations SH.resx (Korean) 2023-08-28 07:58:04 -07:00
Masterain
5c3365a7c6 New translations SH.resx (Japanese) 2023-08-28 07:58:03 -07:00
DismissedLight
a5a85ac0fc add missing localizible string 2023-08-28 22:40:46 +08:00
DismissedLight
967f01108c fix #815 2023-08-28 22:30:09 +08:00
Lightczx
605a48f8f2 guide view 2023-08-28 17:25:25 +08:00
Masterain
058484459b Merge pull request #845 from DGP-Studio/l10n_develop
New Crowdin updates
2023-08-28 01:06:11 -07:00
Lightczx
32a780f460 remove redundant icons 2023-08-28 11:17:25 +08:00
Lightczx
d50117af3d fix cultivation icons 2023-08-28 09:32:55 +08:00
Masterain
a8406cf5a4 New translations SH.resx (English) 2023-08-27 13:44:13 -07:00
Masterain
67052b5f24 New translations SH.resx (English) 2023-08-27 12:39:33 -07:00
Masterain
beff98c8c5 New translations SH.resx (English) 2023-08-27 09:23:50 -07:00
DismissedLight
7756e348a8 add new buff & time info for spiral abyss 2023-08-27 23:26:47 +08:00
Masterain
b3fabf92e8 New translations SH.resx (English) 2023-08-27 08:14:34 -07:00
Masterain
3ced5ee962 New translations SH.resx (Chinese Traditional) 2023-08-27 08:14:33 -07:00
Masterain
fc3424e59e New translations SH.resx (Korean) 2023-08-27 08:14:32 -07:00
Masterain
5e7e310bf5 New translations SH.resx (Japanese) 2023-08-27 08:14:31 -07:00
DismissedLight
92afa4b23c add ability to customize geetest url 2023-08-27 22:20:28 +08:00
DismissedLight
82b00ba458 Merge pull request #858 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-b2d8e37c27
Bump the packages group in /src/Snap.Hutao with 2 updates
2023-08-27 20:15:35 +08:00
dependabot[bot]
3f798b86a4 Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) and [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).


Updates `Microsoft.CodeAnalysis.CSharp` from 4.6.0 to 4.7.0
- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/commits)

Updates `Microsoft.NET.Test.Sdk` from 17.7.0 to 17.7.1
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.7.0...v17.7.1)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-27 12:14:34 +00:00
DismissedLight
49fa5846f8 Merge pull request #857 from DGP-Studio/develop
1.7.0 RC 2
2023-08-27 20:14:10 +08:00
DismissedLight
1e73c1ba96 fix #855 2023-08-27 20:10:31 +08:00
DismissedLight
f37ed74238 fix response message on -100 2023-08-27 19:38:08 +08:00
Masterain
b57faac904 New translations SH.resx (English) 2023-08-27 04:21:41 -07:00
Masterain
4f7be56376 New translations SH.resx (English) 2023-08-27 03:10:55 -07:00
Masterain
81574f678c New translations SH.resx (English) 2023-08-27 02:10:59 -07:00
Masterain
188fd831e4 New translations SH.resx (Chinese Traditional) 2023-08-27 02:10:58 -07:00
Masterain
8ff369bd11 New translations SH.resx (Korean) 2023-08-27 02:10:57 -07:00
Masterain
51871f9795 New translations SH.resx (Japanese) 2023-08-27 02:10:56 -07:00
DismissedLight
8ca6251d33 add refresh time for avatar property 2023-08-27 17:02:23 +08:00
DismissedLight
00f083608e refine navigation icons 2023-08-27 15:57:39 +08:00
DismissedLight
14c75d7c04 fix dailynote activation exiting 2023-08-26 21:50:50 +08:00
DismissedLight
eb557afd18 add color for enhanced count 2023-08-26 19:55:02 +08:00
Masterain
2ac428d848 New translations SH.resx (English) 2023-08-26 02:32:02 -07:00
DismissedLight
1d8ee4cee5 remove redundant attribute 2023-08-26 17:08:18 +08:00
Masterain
335dbc9dc7 New translations SH.resx (English) 2023-08-26 01:28:59 -07:00
Masterain
c87555e86f New translations SH.resx (Chinese Traditional) 2023-08-26 01:28:58 -07:00
Masterain
809ea5768c New translations SH.resx (Korean) 2023-08-26 01:28:57 -07:00
Masterain
7b035053a1 New translations SH.resx (Japanese) 2023-08-26 01:28:56 -07:00
DismissedLight
d872639c19 Merge branch 'main' into develop 2023-08-26 16:16:34 +08:00
DismissedLight
e969267c1e port back signin reward feature 2023-08-26 16:13:49 +08:00
Masterain
a874ddc078 Update azure-pipelines.yml 2023-08-25 22:37:52 -07:00
DismissedLight
853473ebf7 fix avatar property page issue 2023-08-26 11:05:03 +08:00
Masterain
08a1363a13 New translations SH.resx (Korean) 2023-08-25 19:47:31 -07:00
Masterain
d8fdcbcf2f New translations SH.resx (Japanese) 2023-08-25 19:47:30 -07:00
DismissedLight
0cdcb1c444 refine user panel UI 2023-08-26 10:36:05 +08:00
Masterain
548ecea28b New translations SH.resx (English) 2023-08-25 09:58:38 -07:00
Masterain
f14933a797 New translations SH.resx (Chinese Traditional) 2023-08-25 09:58:37 -07:00
Masterain
8b53bb6a89 New translations SH.resx (Korean) 2023-08-25 09:58:36 -07:00
Masterain
99a91166ee New translations SH.resx (Japanese) 2023-08-25 09:58:35 -07:00
DismissedLight
0de4aff03a fix dailynote refresh time notify 2023-08-26 00:14:18 +08:00
Masterain
7ef88d390c New translations SH.resx (English) 2023-08-25 08:50:03 -07:00
Masterain
fe8648e241 New translations SH.resx (English) 2023-08-25 03:28:44 -07:00
Masterain
5f000629e8 New translations SH.resx (English) 2023-08-25 02:31:35 -07:00
Masterain
604708298a New translations SH.resx (Chinese Traditional) 2023-08-25 02:31:34 -07:00
Masterain
cc529df60c New translations SH.resx (Korean) 2023-08-25 02:31:32 -07:00
Masterain
195ddb20a0 New translations SH.resx (Japanese) 2023-08-25 02:31:31 -07:00
Lightczx
020c8c38a8 add signin client back 2023-08-25 17:27:57 +08:00
Lightczx
800ecb07d7 change how we format reliquary sub property 2023-08-25 15:15:21 +08:00
Lightczx
486e7fffd2 add enhanced count for composed reliquary sub affix 2023-08-25 14:35:09 +08:00
Masterain
119bacea7d New translations SH.resx (English) 2023-08-24 20:49:48 -07:00
Masterain
900730935e New translations SH.resx (Chinese Traditional) 2023-08-24 20:49:47 -07:00
Masterain
cd37fc761f New translations SH.resx (Korean) 2023-08-24 20:49:46 -07:00
Masterain
79d8a63016 New translations SH.resx (Japanese) 2023-08-24 20:49:44 -07:00
Lightczx
0fd018f42e UIGF import denial on invalid itemid 2023-08-25 11:47:53 +08:00
Lightczx
c1cc4961a0 try fix dailynote notification user sync 2023-08-25 11:36:07 +08:00
Masterain
243217d1fa New translations SH.resx (English) 2023-08-24 08:04:53 -07:00
Masterain
c4e3e993fd New translations SH.resx (Chinese Traditional) 2023-08-24 08:04:52 -07:00
Masterain
4e4770dc23 New translations SH.resx (Korean) 2023-08-24 08:04:51 -07:00
Masterain
a74d9e5c4c New translations SH.resx (Japanese) 2023-08-24 08:04:49 -07:00
DismissedLight
b7642847cd fix refresh time update 2023-08-24 22:55:28 +08:00
DismissedLight
5f37b6bd6d add setting for dailynote auto refresh 2023-08-24 22:46:36 +08:00
Masterain
9158aca48d New translations SH.resx (English) 2023-08-24 06:52:10 -07:00
Masterain
591be8473c New translations SH.resx (Chinese Traditional) 2023-08-24 06:52:09 -07:00
Masterain
409ac022ea New translations SH.resx (Korean) 2023-08-24 06:52:07 -07:00
Masterain
ff6f3c50c4 New translations SH.resx (Japanese) 2023-08-24 06:52:06 -07:00
DismissedLight
b3de8b11a1 add dailynote refresh time hint 2023-08-24 21:22:52 +08:00
Lightczx
5a952d8b41 refreshtime for dailynote 2023-08-24 17:30:38 +08:00
Lightczx
39db32a48b fix #656 listviewbase selected item sync 2023-08-24 16:19:17 +08:00
Lightczx
67d17cf23f fix #596 incorrect announcement time 2023-08-24 15:16:57 +08:00
Lightczx
2df292f1b8 style tag zero width negative look ahead 2023-08-24 14:06:47 +08:00
Lightczx
b09a16079c source generation code style 2023-08-24 11:13:07 +08:00
Lightczx
8bd831a16d code style 2023-08-24 10:56:36 +08:00
Masterain
097d5ab9ef New translations SH.resx (English) 2023-08-23 13:28:33 -07:00
Masterain
73071e6e81 New translations SH.resx (English) 2023-08-23 08:10:59 -07:00
Masterain
7da2b21db7 New translations SH.resx (Chinese Traditional) 2023-08-23 08:10:57 -07:00
Masterain
7b78b501a7 New translations SH.resx (Korean) 2023-08-23 08:10:56 -07:00
Masterain
55d5847980 New translations SH.resx (Japanese) 2023-08-23 08:10:55 -07:00
DismissedLight
9259f173b5 refine dailynote UI & fix transformer not obtained desc 2023-08-23 22:54:54 +08:00
DismissedLight
b6080c45c2 fix #823 2023-08-23 20:21:32 +08:00
DismissedLight
704113cacd geetest verify 2023-08-22 23:32:03 +08:00
DismissedLight
f4f3242546 fix game resouce latest package no path 2023-08-21 23:16:23 +08:00
DismissedLight
23feb78f05 fix user collection initialization race condition 2023-08-21 22:51:33 +08:00
Lightczx
e347694a0b Update dependabot.yml 2023-08-21 15:52:20 +08:00
Masterain
3748f721bf New translations SH.resx (Japanese) 2023-08-21 00:36:52 -07:00
Masterain
96f458b42e New translations SH.resx (English) 2023-08-20 09:41:01 -07:00
Masterain
2b313e6c0a New translations SH.resx (Chinese Traditional) 2023-08-20 08:07:44 -07:00
Masterain
ed90664170 New translations SH.resx (English) 2023-08-20 07:06:38 -07:00
Masterain
eb75ce75ce New translations SH.resx (Chinese Traditional) 2023-08-20 07:06:37 -07:00
Masterain
4188dd1f9e New translations SH.resx (Korean) 2023-08-20 07:06:36 -07:00
Masterain
08b82dda07 New translations SH.resx (Japanese) 2023-08-20 07:06:35 -07:00
DismissedLight
f2fea5a0e6 fix game package convert 2023-08-20 21:50:39 +08:00
DismissedLight
368d0bfbd7 fix avatarproperty ordering 2023-08-19 20:56:27 +08:00
DismissedLight
baee72a2fa fix gachastatistics pullprediction infobar spam 2023-08-19 20:33:32 +08:00
DismissedLight
6f1c7b250e fix achievement archive creation 2023-08-19 20:23:30 +08:00
DismissedLight
75938c3ede fix gachalog initialization & improve loading speed 2023-08-19 20:07:02 +08:00
DismissedLight
79cf2839e6 fix gachalog import crash 2023-08-19 18:21:24 +08:00
DismissedLight
cb06949e60 fix gachalog refresh crash 2023-08-19 18:16:42 +08:00
DismissedLight
282eb228d9 improve cachedimage performance 2023-08-19 17:15:46 +08:00
DismissedLight
674002ee8d fix #841 2023-08-19 16:40:22 +08:00
Masterain
faa00d7c5f New translations SH.resx (English) 2023-08-18 02:47:04 -07:00
Masterain
638c9ff732 New translations SH.resx (Chinese Traditional) 2023-08-18 02:47:03 -07:00
Masterain
dabe80900d New translations SH.resx (Korean) 2023-08-18 02:47:01 -07:00
Masterain
a8b7a7f850 New translations SH.resx (Japanese) 2023-08-18 02:47:00 -07:00
Lightczx
7e5a536119 remove incorrect i18n resource 2023-08-18 16:43:41 +08:00
Lightczx
0ea85ae26b Prepare Announcement Regex 2023-08-18 16:41:11 +08:00
Masterain
d3beccc304 New translations SH.resx (English) 2023-08-18 01:41:10 -07:00
Masterain
facfec4831 New translations SH.resx (Chinese Traditional) 2023-08-18 01:41:09 -07:00
Masterain
d6eea91f2c New translations SH.resx (Korean) 2023-08-18 01:41:08 -07:00
Masterain
a5f3821d43 New translations SH.resx (Japanese) 2023-08-18 01:41:07 -07:00
Masterain
6d5485d4ca New translations SH.resx (Japanese) 2023-08-17 22:19:21 -07:00
Lightczx
1e92edca63 Improve AnnouncementContentViewer DarkMode speed 2023-08-18 11:48:06 +08:00
Masterain
055e040238 Update azure-pipelines.yml 2023-08-17 00:42:49 -07:00
Masterain
96f6921120 Update azure-pipelines.yml 2023-08-17 00:25:16 -07:00
Masterain
ddbb2611d7 New translations SH.resx (English) 2023-08-16 23:45:40 -07:00
Masterain
617961d0d2 New translations SH.resx (English) 2023-08-16 21:27:16 -07:00
Masterain
cf1c3eb056 New translations SH.resx (Chinese Traditional) 2023-08-16 21:27:15 -07:00
Masterain
882d9fc655 New translations SH.resx (Korean) 2023-08-16 21:27:14 -07:00
Masterain
6bfad4571a New translations SH.resx (Japanese) 2023-08-16 21:27:13 -07:00
DismissedLight
bc3aea5515 Merge pull request #836 from DGP-Studio/develop
Update readme.md
2023-08-17 00:35:15 +08:00
DismissedLight
c1c7cda390 update readme.md 2023-08-17 00:33:50 +08:00
DismissedLight
0bd8c01fdd refactor done 2023-08-17 00:05:04 +08:00
Lightczx
c4e4ffebd6 refactor viewmodel 2023-08-16 17:19:43 +08:00
Lightczx
d08d2b406f refactor welcome view model 2023-08-15 17:26:38 +08:00
Lightczx
4c50479e96 refactor SettingViewModel 2023-08-14 17:30:08 +08:00
Lightczx
f1cea8e5da Update azure-pipelines.yml 2023-08-14 17:09:03 +08:00
DismissedLight
3225f8105a Merge pull request #831 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/main-dependencies-ddb7c159a1
Bump the main-dependencies group in /src/Snap.Hutao with 3 updates
2023-08-14 16:07:37 +08:00
dependabot[bot]
565817c14a Bump the main-dependencies group in /src/Snap.Hutao with 3 updates
Bumps the main-dependencies group in /src/Snap.Hutao with 3 updates: [Microsoft.EntityFrameworkCore.Sqlite](https://github.com/dotnet/efcore), [Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/efcore) and [Microsoft.VisualStudio.Threading.Analyzers](https://github.com/microsoft/vs-threading).


Updates `Microsoft.EntityFrameworkCore.Sqlite` from 7.0.9 to 7.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.9...v7.0.10)

Updates `Microsoft.EntityFrameworkCore.Tools` from 7.0.9 to 7.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.9...v7.0.10)

Updates `Microsoft.VisualStudio.Threading.Analyzers` from 17.6.40 to 17.7.30
- [Release notes](https://github.com/microsoft/vs-threading/releases)
- [Commits](https://github.com/microsoft/vs-threading/commits)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: main-dependencies
- dependency-name: Microsoft.EntityFrameworkCore.Tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: main-dependencies
- dependency-name: Microsoft.VisualStudio.Threading.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: main-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 07:35:33 +00:00
DismissedLight
ade6613fe1 refactor dialogs 2023-08-12 16:24:12 +08:00
Lightczx
71fcbc367c refactor string cultureinfo 2023-08-11 16:12:11 +08:00
DismissedLight
a23043fb6d update controls style 2023-08-10 23:38:35 +08:00
Lightczx
d9169df3b8 refactor wiki viewmodel 2023-08-10 17:25:37 +08:00
DismissedLight
420bf6ff41 update community toolkit dependencies 2023-08-09 23:37:38 +08:00
Lightczx
5b269c7b3d refactor viewmodel 2023-08-09 17:06:19 +08:00
DismissedLight
9387b955ee card refine 2023-08-08 23:50:53 +08:00
DismissedLight
f8173208f4 Merge pull request #828 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/main-dependencies-5bd43afe2e
Bump the main-dependencies group in /src/Snap.Hutao with 1 update
2023-08-08 23:03:10 +08:00
Lightczx
3368ae1b25 refresh view 2023-08-08 20:40:48 +08:00
Lightczx
5706e877e7 fix dependency property 2023-08-07 23:45:34 +08:00
Lightczx
3cf0cd1c9a refactor viewmodels 2023-08-07 17:28:39 +08:00
dependabot[bot]
312aa5f71a Bump the main-dependencies group in /src/Snap.Hutao with 1 update
Bumps the main-dependencies group in /src/Snap.Hutao with 1 update: [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).

- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.6.3...v17.7.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: main-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 07:44:18 +00:00
Lightczx
4dca174019 update announcement regex 2023-08-07 08:18:38 +08:00
Lightczx
fd52334c13 Really fast 2023-08-03 23:39:35 +08:00
Lightczx
0ee875d28d user service left 2023-08-02 21:58:46 +08:00
DismissedLight
1c46412324 Merge pull request #821 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/main-dependencies-b6424fe871
Bump the main-dependencies group in /src/Snap.Hutao with 1 update
2023-08-02 20:21:36 +08:00
Lightczx
a69ae12e4f fix #825 2023-08-01 22:20:50 +08:00
dependabot[bot]
d738c60d3c Bump the main-dependencies group in /src/Snap.Hutao with 1 update
Bumps the main-dependencies group in /src/Snap.Hutao with 1 update: [Microsoft.WindowsAppSDK](https://github.com/microsoft/windowsappsdk).

- [Release notes](https://github.com/microsoft/windowsappsdk/releases)
- [Commits](https://github.com/microsoft/windowsappsdk/commits)

---
updated-dependencies:
- dependency-name: Microsoft.WindowsAppSDK
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: main-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 08:02:16 +00:00
Lightczx
4f6c2905d2 Update GameService.cs 2023-07-30 22:37:40 +08:00
Lightczx
4c337a79b9 package convert fixed 2023-07-30 21:54:10 +08:00
Lightczx
a661530025 fix package converter missing directory 2023-07-30 21:51:16 +08:00
Lightczx
d3b72ecb98 fix CI 2023-07-30 21:33:35 +08:00
Lightczx
69dc8355ad fix #811 2023-07-30 21:32:15 +08:00
Lightczx
2c45274cd3 fix #819 2023-07-30 16:30:56 +08:00
Lightczx
93ee1a3386 fix CI 2023-07-30 12:47:22 +08:00
Lightczx
c5ab707b66 remove manual dependency property 2023-07-30 00:35:41 +08:00
Lightczx
4226598442 refactor gacha service 3 2023-07-28 14:57:51 +08:00
Lightczx
5f38c370c1 delay task 2023-07-27 22:59:31 +08:00
Lightczx
de9abcfad4 refactor gacha service 2 2023-07-27 22:26:19 +08:00
Lightczx
53044b0dda refactor gacha service 2023-07-27 17:23:28 +08:00
Lightczx
e843c84374 refactor dailynote service and partial gacha service 2023-07-26 23:46:11 +08:00
Lightczx
8525aeafac refactor 2023-07-26 08:13:27 +08:00
DismissedLight
d665ba22e5 refactor cultivation service 2023-07-20 16:49:36 +08:00
DismissedLight
d03f8185b8 refactor services 2023-07-20 15:43:51 +08:00
Lightczx
2ad87d25df refactor achievementservice 2023-07-20 08:08:29 +08:00
DismissedLight
01fdcda729 refactor model 2023-07-19 16:44:38 +08:00
Lightczx
f531684e6a refactor naming 2023-07-18 23:20:02 +08:00
DismissedLight
cd066e1462 mapping abstraction 2023-07-18 16:54:42 +08:00
DismissedLight
e9ee31a604 refactor controls 2023-07-17 17:14:15 +08:00
DismissedLight
f2fc5e443c Merge pull request #810 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/MSTest.TestFramework-3.1.1
Bump MSTest.TestFramework from 3.0.4 to 3.1.1 in /src/Snap.Hutao
2023-07-17 15:50:05 +08:00
DismissedLight
4d2b54d49d Merge pull request #809 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.EntityFrameworkCore.Tools-7.0.9
Bump Microsoft.EntityFrameworkCore.Tools from 7.0.8 to 7.0.9 in /src/Snap.Hutao
2023-07-17 15:49:47 +08:00
dependabot[bot]
bea9d5caf2 Bump Microsoft.EntityFrameworkCore.Tools in /src/Snap.Hutao
Bumps [Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/efcore) from 7.0.8 to 7.0.9.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.8...v7.0.9)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 07:49:04 +00:00
dependabot[bot]
2fbf904987 Bump MSTest.TestFramework from 3.0.4 to 3.1.1 in /src/Snap.Hutao
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.0.4 to 3.1.1.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.0.4...v3.1.1)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 07:48:57 +00:00
DismissedLight
6cd7e14ac9 Merge pull request #808 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.EntityFrameworkCore.Sqlite-7.0.9
Bump Microsoft.EntityFrameworkCore.Sqlite from 7.0.8 to 7.0.9 in /src/Snap.Hutao
2023-07-17 15:48:30 +08:00
DismissedLight
7826e019d7 Merge pull request #807 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/MSTest.TestAdapter-3.1.1
Bump MSTest.TestAdapter from 3.0.4 to 3.1.1 in /src/Snap.Hutao
2023-07-17 15:48:03 +08:00
dependabot[bot]
e3e8400978 Bump Microsoft.EntityFrameworkCore.Sqlite in /src/Snap.Hutao
Bumps [Microsoft.EntityFrameworkCore.Sqlite](https://github.com/dotnet/efcore) from 7.0.8 to 7.0.9.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.8...v7.0.9)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 07:39:37 +00:00
dependabot[bot]
500003f9c2 Bump MSTest.TestAdapter from 3.0.4 to 3.1.1 in /src/Snap.Hutao
Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.0.4 to 3.1.1.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.0.4...v3.1.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 07:39:33 +00:00
Lightczx
147312c289 Update ObjectExtension.cs 2023-07-16 21:56:07 +08:00
Lightczx
e90f76ead8 delete unused resources 2023-07-16 18:13:35 +08:00
Lightczx
8631933ef4 fix #804 2023-07-16 16:37:29 +08:00
Lightczx
c5dada3f72 guide window 2023-07-15 22:54:50 +08:00
DismissedLight
9aaeb327b6 Merge pull request #799 from DGP-Studio/l10n_main
New Crowdin updates
2023-07-13 19:22:44 +08:00
Masterain
75e771c75e New translations SH.resx (English) 2023-07-13 04:11:51 -07:00
Masterain
d4515936bd New translations SH.resx (Chinese Traditional) 2023-07-13 04:11:49 -07:00
Masterain
d9f2261129 New translations SH.resx (Korean) 2023-07-13 04:11:48 -07:00
Masterain
f0d4ea9a10 New translations SH.resx (Japanese) 2023-07-13 04:11:47 -07:00
Lightczx
bdcbba3237 update text 2023-07-13 19:09:58 +08:00
Masterain
a931a661cd New translations SH.resx (English) 2023-07-13 04:09:52 -07:00
Masterain
0fb2991085 New translations SH.resx (Chinese Traditional) 2023-07-13 04:09:50 -07:00
Masterain
cda04c6aa7 New translations SH.resx (Korean) 2023-07-13 04:09:49 -07:00
Masterain
b23659bf80 New translations SH.resx (Japanese) 2023-07-13 04:09:48 -07:00
DismissedLight
2a3f119fb0 update translation tips 2023-07-13 16:48:09 +08:00
DismissedLight
30e888ffb2 Add Gacha pull prediction 2023-07-13 15:39:10 +08:00
Lightczx
27b79659a4 Add animation to hutao cloud gacha statistics view 2023-07-12 22:18:37 +08:00
DismissedLight
4cef096cb5 refine code 2023-07-12 17:27:17 +08:00
DismissedLight
e065ba0964 Add GI 4.0 materials for Cultivation 2023-07-12 16:41:21 +08:00
DismissedLight
4c0d86fd13 Add InventoryItems localization support 2023-07-12 16:20:17 +08:00
DismissedLight
70ab81bb0f fix #796 2023-07-12 13:21:48 +08:00
DismissedLight
72be37834d Add compatibility with GI 4.0 2023-07-12 10:33:29 +08:00
DismissedLight
f1a2a828c8 Update HutaoStatisticsCard.xaml 2023-07-10 16:36:56 +08:00
DismissedLight
3eaaf0e4e2 Add Hutao GachaEvent Statistics 2023-07-10 15:13:41 +08:00
Lightczx
41d7441eb3 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-07-10 09:31:53 +08:00
Lightczx
173a1343c1 Hutao GachaEvent Statistics 1 2023-07-10 09:31:16 +08:00
DismissedLight
0fc0413612 remove unnecessary DS handler in UserClient 2023-07-10 09:30:32 +08:00
DismissedLight
1fc1f9b08d Update .gitignore 2023-07-10 09:25:23 +08:00
Lightczx
133a1a532e code style 2023-07-07 22:50:07 +08:00
Lightczx
f4fa08a939 fix developer hint visibility 2023-07-07 22:22:43 +08:00
Lightczx
5020621c46 add gachalogserviceexpiredat in hutaocloudservice 2023-07-07 11:13:47 +08:00
Lightczx
4d526bb363 DependencyProperty generation 2023-07-07 10:11:54 +08:00
DismissedLight
b37d5331a2 Merge pull request #784 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.NET.Test.Sdk-17.6.3
Bump Microsoft.NET.Test.Sdk from 17.6.2 to 17.6.3 in /src/Snap.Hutao
2023-07-03 15:41:55 +08:00
dependabot[bot]
451486e3fc Bump Microsoft.NET.Test.Sdk from 17.6.2 to 17.6.3 in /src/Snap.Hutao
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.2 to 17.6.3.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.6.2...v17.6.3)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 07:18:55 +00:00
Lightczx
bbacb038cb fix CHS cannot load correct metadata 2023-06-27 20:43:22 +08:00
DismissedLight
7aba05d9c8 Merge pull request #781 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.EntityFrameworkCore.Tools-7.0.8
Bump Microsoft.EntityFrameworkCore.Tools from 7.0.7 to 7.0.8 in /src/Snap.Hutao
2023-06-26 17:12:58 +08:00
dependabot[bot]
fbcd43c0af Bump Microsoft.EntityFrameworkCore.Tools in /src/Snap.Hutao
Bumps [Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/efcore) from 7.0.7 to 7.0.8.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.7...v7.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 09:12:40 +00:00
DismissedLight
51314da123 Merge pull request #782 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.EntityFrameworkCore.Sqlite-7.0.8
Bump Microsoft.EntityFrameworkCore.Sqlite from 7.0.7 to 7.0.8 in /src/Snap.Hutao
2023-06-26 17:12:20 +08:00
DismissedLight
8ed2e8cc72 Merge pull request #783 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/StyleCop.Analyzers.Unstable-1.2.0.507
Bump StyleCop.Analyzers.Unstable from 1.2.0.435 to 1.2.0.507 in /src/Snap.Hutao
2023-06-26 17:12:03 +08:00
dependabot[bot]
7ab10d7824 Bump StyleCop.Analyzers.Unstable in /src/Snap.Hutao
Bumps [StyleCop.Analyzers.Unstable](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) from 1.2.0.435 to 1.2.0.507.
- [Release notes](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/releases)
- [Changelog](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/KnownChanges.md)
- [Commits](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/commits)

---
updated-dependencies:
- dependency-name: StyleCop.Analyzers.Unstable
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 08:05:16 +00:00
dependabot[bot]
ca5f84911b Bump Microsoft.EntityFrameworkCore.Sqlite in /src/Snap.Hutao
Bumps [Microsoft.EntityFrameworkCore.Sqlite](https://github.com/dotnet/efcore) from 7.0.7 to 7.0.8.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.7...v7.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 08:04:49 +00:00
DismissedLight
500efa60ed Merge pull request #779 from DGP-Studio/l10n_main
New Crowdin updates
2023-06-21 22:01:21 +08:00
Masterain
650e5d8a6e New translations SH.resx (Japanese) 2023-06-21 07:00:22 -07:00
Masterain
79a9128434 New translations SH.resx (English) 2023-06-21 07:00:21 -07:00
Masterain
c7c883bb11 New translations SH.resx (Chinese Traditional) 2023-06-21 07:00:20 -07:00
Masterain
3ee98810f1 New translations SH.resx (Korean) 2023-06-21 07:00:18 -07:00
Lightczx
f09c924a61 reduce NoModuleFound chance 2023-06-18 20:02:09 +08:00
DismissedLight
86d9a381c4 Merge pull request #772 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.NET.Test.Sdk-17.6.2
Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.2 in /src/Snap.Hutao
2023-06-16 16:30:38 +08:00
dependabot[bot]
b2d141af11 Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.2 in /src/Snap.Hutao
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.2.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.2)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 08:30:07 +00:00
DismissedLight
15865eb746 Merge pull request #771 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/MSTest.TestFramework-3.0.4
Bump MSTest.TestFramework from 3.0.2 to 3.0.4 in /src/Snap.Hutao
2023-06-16 16:29:58 +08:00
dependabot[bot]
242b9aa036 Bump MSTest.TestFramework from 3.0.2 to 3.0.4 in /src/Snap.Hutao
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.0.2 to 3.0.4.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.0.2...v3.0.4)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 08:29:51 +00:00
DismissedLight
7b81cc7a9c Merge pull request #770 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/Microsoft.CodeAnalysis.CSharp-4.6.0
Bump Microsoft.CodeAnalysis.CSharp from 4.5.0 to 4.6.0 in /src/Snap.Hutao
2023-06-16 16:29:41 +08:00
DismissedLight
2060f20707 Merge pull request #769 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/coverlet.collector-6.0.0
Bump coverlet.collector from 3.2.0 to 6.0.0 in /src/Snap.Hutao
2023-06-16 16:29:27 +08:00
DismissedLight
27a6c4f1fb Merge pull request #768 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/MSTest.TestAdapter-3.0.4
Bump MSTest.TestAdapter from 3.0.2 to 3.0.4 in /src/Snap.Hutao
2023-06-16 16:29:14 +08:00
dependabot[bot]
6427fe23ef Bump Microsoft.CodeAnalysis.CSharp in /src/Snap.Hutao
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/commits)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 08:22:18 +00:00
dependabot[bot]
6cf516f883 Bump coverlet.collector from 3.2.0 to 6.0.0 in /src/Snap.Hutao
Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 3.2.0 to 6.0.0.
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/compare/v3.2.0...v6.0.0)

---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 08:22:06 +00:00
dependabot[bot]
080674f648 Bump MSTest.TestAdapter from 3.0.2 to 3.0.4 in /src/Snap.Hutao
Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.0.2 to 3.0.4.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.0.2...v3.0.4)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 08:22:01 +00:00
Lightczx
09834ada6b fix dependabot 2023-06-16 16:20:43 +08:00
786 changed files with 23462 additions and 12790 deletions

View File

@@ -1,6 +1,7 @@
name: 问题反馈
name: BUG Report 问题反馈
description: 告诉我们你的问题
title: "[Bug]: 在这里填写一个合适的标题"
labels: ["BUG"]
body:
- type: markdown
attributes:

View File

@@ -1,6 +1,6 @@
name: 功能请求
description: 告诉我们你的想法
title: "[Feat]: 在这里填写一个合适的标题"
name: Feature Request 功能请求
description: Tell us about your thought 告诉我们你的想法
title: "[Feat]: Place your title here 在这里填写一个合适的标题"
labels: ["功能"]
assignees:
- Lightczx
@@ -8,20 +8,21 @@ body:
- type: markdown
attributes:
value: |
Please fill the form below
请按下方的要求填写完整的问题表单。
- type: textarea
id: back
attributes:
label: 背景与动机
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
label: Background & Motivation 背景与动机
description: Reason why this feature is needed. If multiple features is requested, please open multiple issues for each of them. 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
validations:
required: true
- type: textarea
id: req
attributes:
label: 想要实现或优化的功能
description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
label: Detail of the Feature 想要实现或优化的功能
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted. 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
validations:
required: true

View File

@@ -5,7 +5,12 @@
version: 2
updates:
- package-ecosystem: "nuget" # See documentation for possible values
directory: "/" # Location of package manifests
- package-ecosystem: "nuget"
directory: "/src/Snap.Hutao" # Snap.Hutao.csproj
target-branch: "develop"
schedule:
interval: "weekly"
groups:
packages:
patterns:
- "*"

3
.gitignore vendored
View File

@@ -2,8 +2,11 @@ desktop.ini
*.csproj.user
*.pubxml
*.DotSettings.user
.vs/
.idea/
src/Snap.Hutao/_ReSharper.Caches
src/Snap.Hutao/Snap.Hutao/bin/
src/Snap.Hutao/Snap.Hutao/obj/

View File

@@ -3,7 +3,8 @@
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Microsoft CsWin32" value="https://pkgs.dev.azure.com/azure-public/winsdk/_packaging/CI/nuget/v3/index.json" />
<add key="CommunityToolkit Labs" value="https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json" />
<add key="CommunityToolkit-MainLatest" value="https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-MainLatest/nuget/v3/index.json" />
<add key="CommunityToolkit-Labs" value="https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json" />
</packageSources>
<packageRestore>
<add key="enabled" value="True" />

View File

@@ -1,4 +1,4 @@
![](https://repository-images.githubusercontent.com/482734649/c47a0a8e-868d-4d07-a66f-6d4473abfe46)
![](res/HutaoRepoBanner2.png)
胡桃工具箱是一个 Windows 平台的开源的原神工具箱,旨在帮助玩家获得更好的游戏体验; 它是对官方移动端工具的一种非破坏性功能扩展,为不习惯在移动端进行原神游戏的 PC 玩家提供一个在 Windows 平台下获得接近移动端功能权利的途径
@@ -29,13 +29,12 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
* [CommunityToolkit/dotnet](https://github.com/CommunityToolkit/dotnet)
* [CommunityToolkit/Labs-Windows](https://github.com/CommunityToolkit/Labs-Windows)
* [CommunityToolkit/WindowsCommunityToolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit)
* [CommunityToolkit/Windows](https://github.com/CommunityToolkit/Windows)
* [dahall/taskscheduler](https://github.com/dahall/taskscheduler)
* [dotnet/efcore](https://github.com/dotnet/efcore)
* [dotnet/runtime](https://github.com/dotnet/runtime)
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)
* [microsoft/CsWin32](https://github.com/microsoft/CsWin32)
* [microsoft/vs-threading](https://github.com/microsoft/vs-threading)
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
@@ -45,5 +44,5 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
* [Snap.Hutao.Server](https://github.com/DGP-Studio/Snap.Hutao.Server)
* [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)
## 近期活跃数据 / Active Stat
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
## 开发 / Development
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)

View File

@@ -11,6 +11,7 @@ trigger:
branches:
include:
- main
- develop
paths:
exclude:
- README.md

BIN
res/HutaoRepoBanner2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

BIN
res/HutaoRepoBanner2.psd Normal file

Binary file not shown.

View File

@@ -1,3 +1,3 @@
本文件夹中的所有图片,均由 [DGP Studio](https://github.com/DGP-Studio) 委托 [Bilibili 画画的芦苇](https://space.bilibili.com/274422134) 绘制
Copyright ©2023 DGP Studio, All Rights Reserved.
Copyright © 2023 DGP Studio, All Rights Reserved.

View File

@@ -57,6 +57,7 @@ dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_diagnostic.CA1000.severity = suggestion
[*.cs]
#### 命名样式 ####
@@ -162,11 +163,166 @@ dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.VSTHRD111.severity = suggestion
dotnet_diagnostic.VSTHRD111.severity = silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
# SA1600: Elements should be documented
dotnet_diagnostic.SA1600.severity = none
dotnet_diagnostic.SA1601.severity = silent
dotnet_diagnostic.SA1602.severity = silent
# CA1008: 枚举应具有零值
dotnet_diagnostic.CA1008.severity = suggestion
# CA1010: 还应实现泛型接口
dotnet_diagnostic.CA1010.severity = suggestion
# CA1012: 抽象类型不应具有公共构造函数
dotnet_diagnostic.CA1012.severity = suggestion
# CA1024: 在适用处使用属性
dotnet_diagnostic.CA1024.severity = suggestion
# CA1034: 嵌套类型应不可见
dotnet_diagnostic.CA1034.severity = suggestion
# CA1036: 重写可比较类型中的方法
dotnet_diagnostic.CA1036.severity = suggestion
# CA1040: 避免使用空接口
dotnet_diagnostic.CA1040.severity = suggestion
# CA1044: 属性不应是只写的
dotnet_diagnostic.CA1044.severity = suggestion
# CA1043: 将整型或字符串参数用于索引器
dotnet_diagnostic.CA1043.severity = suggestion
# CA1046: 不要对引用类型重载相等运算符
dotnet_diagnostic.CA1046.severity = suggestion
# CA1051: 不要声明可见实例字段
dotnet_diagnostic.CA1051.severity = suggestion
# CA1052: 静态容器类型应为 Static 或 NotInheritable
dotnet_diagnostic.CA1052.severity = suggestion
# CA1058: 类型不应扩展某些基类型
dotnet_diagnostic.CA1058.severity = suggestion
# CA1063: 正确实现 IDisposable
dotnet_diagnostic.CA1063.severity = suggestion
# CA1065: 不要在意外的位置引发异常
dotnet_diagnostic.CA1065.severity = suggestion
# CA1066: 重写 Object.Equals 时实现 IEquatable
dotnet_diagnostic.CA1066.severity = suggestion
# CA1304: 指定 CultureInfo
dotnet_diagnostic.CA1304.severity = suggestion
# CA1305: 指定 IFormatProvider
dotnet_diagnostic.CA1305.severity = suggestion
# CA1307: 为了清晰起见,请指定 StringComparison
dotnet_diagnostic.CA1307.severity = suggestion
# CA1310: 为了确保正确,请指定 StringComparison
dotnet_diagnostic.CA1310.severity = suggestion
# CA1308: 将字符串规范化为大写
dotnet_diagnostic.CA1308.severity = suggestion
# CA1501: 避免过度继承
dotnet_diagnostic.CA1501.severity = suggestion
# CA1502: 避免过度复杂性
dotnet_diagnostic.CA1502.severity = suggestion
# CA1505: 避免使用无法维护的代码
dotnet_diagnostic.CA1505.severity = suggestion
# CA1506: 避免过度的类耦合
dotnet_diagnostic.CA1506.severity = suggestion
# CA1508: 避免死条件代码
dotnet_diagnostic.CA1508.severity = suggestion
# CA1810: 以内联方式初始化引用类型的静态字段
dotnet_diagnostic.CA1810.severity = suggestion
# CA1813: 避免使用非密封特性
dotnet_diagnostic.CA1813.severity = suggestion
# CA1814: 与多维数组相比,首选使用交错数组
dotnet_diagnostic.CA1814.severity = suggestion
# CA1819: 属性不应返回数组
dotnet_diagnostic.CA1819.severity = suggestion
# CA1820: 使用字符串长度测试是否有空字符串
dotnet_diagnostic.CA1820.severity = suggestion
# CA1823: 避免未使用的私有字段
dotnet_diagnostic.CA1823.severity = suggestion
# CA1849: 当在异步方法中时,调用异步方法
dotnet_diagnostic.CA1849.severity = suggestion
# CA1852: 密封内部类型
dotnet_diagnostic.CA1852.severity = suggestion
# CA2000: 丢失范围之前释放对象
dotnet_diagnostic.CA2000.severity = suggestion
# CA2002: 不要锁定具有弱标识的对象
dotnet_diagnostic.CA2002.severity = suggestion
# CA2007: 考虑对等待的任务调用 ConfigureAwait
dotnet_diagnostic.CA2007.severity = suggestion
# CA2008: 不要在未传递 TaskScheduler 的情况下创建任务
dotnet_diagnostic.CA2008.severity = suggestion
# CA2100: 检查 SQL 查询是否存在安全漏洞
dotnet_diagnostic.CA2100.severity = suggestion
# CA2109: 检查可见的事件处理程序
dotnet_diagnostic.CA2109.severity = suggestion
# CA2119: 密封满足私有接口的方法
dotnet_diagnostic.CA2119.severity = suggestion
# CA2153: 不要捕获损坏状态异常
dotnet_diagnostic.CA2153.severity = suggestion
# CA2201: 不要引发保留的异常类型
dotnet_diagnostic.CA2201.severity = suggestion
# CA2207: 以内联方式初始化值类型的静态字段
dotnet_diagnostic.CA2207.severity = suggestion
# CA2213: 应释放可释放的字段
dotnet_diagnostic.CA2213.severity = suggestion
# CA2214: 不要在构造函数中调用可重写的方法
dotnet_diagnostic.CA2214.severity = suggestion
# CA2215: Dispose 方法应调用基类释放
dotnet_diagnostic.CA2215.severity = suggestion
# CA2216: 可释放类型应声明终结器
dotnet_diagnostic.CA2216.severity = suggestion
# CA2227: 集合属性应为只读
dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
[*.vb]
#### 命名样式 ####

View File

@@ -2,13 +2,10 @@
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
namespace Snap.Hutao.SourceGeneration.Automation;
@@ -16,7 +13,7 @@ namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class CommandGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Core.Annotation.CommandAttribute";
public const string AttributeName = "Snap.Hutao.Core.Annotation.CommandAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
@@ -79,7 +76,6 @@ internal sealed class CommandGenerator : IIncrementalGenerator
string className = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
// TODO: 支持嵌套类
string code = $$"""
using CommunityToolkit.Mvvm.Input;
@@ -99,4 +95,4 @@ internal sealed class CommandGenerator : IIncrementalGenerator
string normalizedClassName = classSymbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.{commandName}.g.cs", code);
}
}
}

View File

@@ -2,9 +2,9 @@
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Snap.Hutao.SourceGeneration.Primitive;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -17,6 +17,9 @@ namespace Snap.Hutao.SourceGeneration.Automation;
internal sealed class ConstructorGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Core.Annotation.ConstructorGeneratedAttribute";
private const string CompilerGenerated = "System.Runtime.CompilerServices.CompilerGeneratedAttribute";
//private static readonly DiagnosticDescriptor genericTypeNotSupportedDescriptor = new("SH102", "Generic type is not supported to generate .ctor", "Type [{0}] is not supported", "Quality", DiagnosticSeverity.Error, true);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
@@ -62,17 +65,18 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
AttributeData constructorInfo = context2.SingleAttribute(AttributeName);
bool resolveHttpClient = constructorInfo.HasNamedArgumentWith<bool>("ResolveHttpClient", value => value);
bool callBaseConstructor = constructorInfo.HasNamedArgumentWith<bool>("CallBaseConstructor", value => value);
string httpclient = resolveHttpClient ? ", System.Net.Http.HttpClient httpClient" : string.Empty;
FieldValueAssignmentOptions options = new(resolveHttpClient);
FieldValueAssignmentOptions options = new(resolveHttpClient, callBaseConstructor);
StringBuilder sourceBuilder = new StringBuilder().Append($$"""
namespace {{context2.Symbol.ContainingNamespace}};
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(ConstructorGenerator)}}", "1.0.0.0")]
partial class {{context2.Symbol.Name}}
partial class {{context2.Symbol.ToDisplayString(SymbolDisplayFormats.QualifiedNonNullableFormat)}}
{
public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}})
public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}}){{(options.CallBaseConstructor ? " : base(serviceProvider)" : string.Empty)}}
{
""");
@@ -83,8 +87,9 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
}
}
""");
production.AddSource($"{context2.Symbol.ToDisplayString()}.ctor.g.cs", sourceBuilder.ToString());
string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.ctor.g.cs", sourceBuilder.ToString());
}
private static void FillUpWithFieldValueAssignment(StringBuilder builder, GeneratorSyntaxContext2 context2, FieldValueAssignmentOptions options)
@@ -95,6 +100,31 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
foreach (IFieldSymbol fieldSymbol in fields)
{
if (fieldSymbol.Name.AsSpan()[0] is '<')
{
continue;
}
bool shoudSkip = false;
foreach (SyntaxReference syntaxReference in fieldSymbol.DeclaringSyntaxReferences)
{
if (syntaxReference.GetSyntax() is VariableDeclaratorSyntax declarator)
{
if (declarator.Initializer is not null)
{
// Skip field with initializer
builder.Append(" // Skip field with initializer: ").AppendLine(fieldSymbol.Name);
shoudSkip = true;
break;
}
}
}
if (shoudSkip)
{
continue;
}
if (fieldSymbol.IsReadOnly && !fieldSymbol.IsStatic)
{
switch (fieldSymbol.Type.ToDisplayString())
@@ -137,7 +167,7 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
}
}
foreach(INamedTypeSymbol interfaceSymbol in context2.Symbol.Interfaces)
foreach (INamedTypeSymbol interfaceSymbol in context2.Symbol.Interfaces)
{
if (interfaceSymbol.Name == "IRecipient")
{
@@ -152,10 +182,12 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
private readonly struct FieldValueAssignmentOptions
{
public readonly bool ResolveHttpClient;
public readonly bool CallBaseConstructor;
public FieldValueAssignmentOptions(bool resolveHttpClient)
public FieldValueAssignmentOptions(bool resolveHttpClient, bool callBaseConstructor)
{
ResolveHttpClient = resolveHttpClient;
ResolveHttpClient = resolveHttpClient;
CallBaseConstructor = callBaseConstructor;
}
}
}

View File

@@ -0,0 +1,149 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Core.Annotation.DependencyPropertyAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> commands =
context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, CommandMethod)
.Where(GeneratorSyntaxContext2.NotNull)
.Collect();
context.RegisterImplementationSourceOutput(commands, GenerateDependencyPropertyImplementations);
}
private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
return node is ClassDeclarationSyntax classDeclarationSyntax
&& classDeclarationSyntax.Modifiers.Count > 1
&& classDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2 CommandMethod(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? methodSymbol))
{
ImmutableArray<AttributeData> attributes = methodSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, methodSymbol, attributes);
}
}
return default;
}
private static void GenerateDependencyPropertyImplementations(SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> context2s)
{
foreach (GeneratorSyntaxContext2 context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString()))
{
GenerateDependencyPropertyImplementation(production, context2);
}
}
private static void GenerateDependencyPropertyImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2)
{
foreach (AttributeData propertyInfo in context2.Attributes.Where(attr => attr.AttributeClass!.ToDisplayString() == AttributeName))
{
string owner = context2.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
Dictionary<string, TypedConstant> namedArguments = propertyInfo.NamedArguments.ToDictionary();
bool isAttached = namedArguments.TryGetValue("IsAttached", out TypedConstant constant) && (bool)constant.Value!;
string register = isAttached ? "RegisterAttached" : "Register";
ImmutableArray<TypedConstant> arguments = propertyInfo.ConstructorArguments;
string propertyName = (string)arguments[0].Value!;
string propertyType = arguments[1].Value!.ToString();
string defaultValue = GetLiteralString(arguments.ElementAtOrDefault(2)) ?? "default";
string propertyChangedCallback = arguments.ElementAtOrDefault(3) is { IsNull: false } arg3 ? $", {arg3.Value}" : string.Empty;
string code;
if (isAttached)
{
string objType = namedArguments.TryGetValue("AttachedType", out TypedConstant attachedType)
? attachedType.Value!.ToString()
: "object";
code = $$"""
using Microsoft.UI.Xaml;
namespace {{context2.Symbol.ContainingNamespace}};
partial class {{owner}}
{
private static readonly DependencyProperty {{propertyName}}Property =
DependencyProperty.RegisterAttached("{{propertyName}}", typeof({{propertyType}}), typeof({{owner}}), new PropertyMetadata(({{propertyType}}){{defaultValue}}{{propertyChangedCallback}}));
public static {{propertyType}} Get{{propertyName}}({{objType}} obj)
{
return ({{propertyType}})obj?.GetValue({{propertyName}}Property);
}
public static void Set{{propertyName}}({{objType}} obj, {{propertyType}} value)
{
obj.SetValue({{propertyName}}Property, value);
}
}
""";
}
else
{
code = $$"""
using Microsoft.UI.Xaml;
namespace {{context2.Symbol.ContainingNamespace}};
partial class {{owner}}
{
private static readonly DependencyProperty {{propertyName}}Property =
DependencyProperty.Register(nameof({{propertyName}}), typeof({{propertyType}}), typeof({{owner}}), new PropertyMetadata(({{propertyType}}){{defaultValue}}{{propertyChangedCallback}}));
public {{propertyType}} {{propertyName}}
{
get => ({{propertyType}})GetValue({{propertyName}}Property);
set => SetValue({{propertyName}}Property, value);
}
}
""";
}
string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code);
}
}
private static string? GetLiteralString(TypedConstant typedConstant)
{
if (typedConstant.IsNull)
{
return default;
}
if (typedConstant.Value is bool boolValue)
{
return boolValue ? "true" : "false";
}
string result = typedConstant.Value!.ToString();
if (string.IsNullOrEmpty(result))
{
return default;
}
return result;
}
}

View File

@@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Snap.Hutao.SourceGeneration.Primitive;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -24,6 +23,8 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
private const string UseDynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute";
private const string CRLF = "\r\n";
private static readonly DiagnosticDescriptor injectionShouldOmitDescriptor = new("SH201", "Injection 特性可以省略", "HttpClient 特性已将 {0} 注册为 Transient 服务", "Quality", DiagnosticSeverity.Warning, true);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> injectionClasses = context.SyntaxProvider
@@ -91,6 +92,17 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
{
if (context.SingleOrDefaultAttribute(InjectionGenerator.AttributeName) is AttributeData injectionData)
{
if (injectionData.ConstructorArguments[0].ToCSharpString() == InjectionGenerator.InjectAsTransientName)
{
if (injectionData.ConstructorArguments.Length < 2)
{
production.ReportDiagnostic(Diagnostic.Create(injectionShouldOmitDescriptor, context.Context.Node.GetLocation(), context.Context.Node));
}
}
}
lineBuilder.Clear().Append(CRLF);
lineBuilder.Append(@" services.AddHttpClient<");

View File

@@ -16,11 +16,10 @@ namespace Snap.Hutao.SourceGeneration.DependencyInjection;
[Generator(LanguageNames.CSharp)]
internal sealed class InjectionGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
private const string CRLF = "\r\n";
public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
public const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
public const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
public const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
private static readonly DiagnosticDescriptor invalidInjectionDescriptor = new("SH101", "无效的 InjectAs 枚举值", "尚未支持生成 {0} 配置", "Quality", DiagnosticSeverity.Error, true);
@@ -87,7 +86,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
{
lineBuilder.Clear().Append(CRLF);
lineBuilder.Clear().AppendLine();
AttributeData injectionInfo = context.SingleAttribute(AttributeName);
ImmutableArray<TypedConstant> arguments = injectionInfo.ConstructorArguments;
@@ -105,7 +104,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
lineBuilder.Append(" services.AddScoped<");
break;
default:
production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, null, injectAsName));
production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, context.Context.Node.GetLocation(), injectAsName));
break;
}

View File

@@ -0,0 +1,78 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Immutable;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.DependencyInjection;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class ServiceAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor NonSingletonUseServiceProviderDescriptor = new("SH301", "Non Singleton service should avoid direct use of IServiceProvider", "Non Singleton service should avoid direct use of IServiceProvider", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor SingletonServiceCaptureNonSingletonServiceDescriptor = new("SH302", "Singleton service should avoid keep reference of non singleton service", "Singleton service should avoid keep reference of non singleton service", "Quality", DiagnosticSeverity.Info, true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get => new DiagnosticDescriptor[]
{
NonSingletonUseServiceProviderDescriptor,
SingletonServiceCaptureNonSingletonServiceDescriptor,
}.ToImmutableArray();
}
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(CompilationStart);
}
private static void CompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSyntaxNodeAction(HandleNonSingletonUseServiceProvider, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeAction(HandleSingletonServiceCaptureNonSingletonService, SyntaxKind.ClassDeclaration);
}
private static void HandleNonSingletonUseServiceProvider(SyntaxNodeAnalysisContext context)
{
ClassDeclarationSyntax classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
if (classDeclarationSyntax.HasAttributeLists())
{
INamedTypeSymbol? classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
if (classSymbol is not null)
{
foreach (AttributeData attributeData in classSymbol.GetAttributes())
{
if (attributeData.AttributeClass!.ToDisplayString() is InjectionGenerator.AttributeName)
{
string serviceType = attributeData.ConstructorArguments[0].ToCSharpString();
if (serviceType is InjectionGenerator.InjectAsTransientName or InjectionGenerator.InjectAsScopedName)
{
HandleNonSingletonUseServiceProviderActual(context, classSymbol);
}
}
}
}
}
}
private static void HandleNonSingletonUseServiceProviderActual(SyntaxNodeAnalysisContext context, INamedTypeSymbol classSymbol)
{
ISymbol? symbol = classSymbol.GetMembers().Where(m => m is IFieldSymbol f && f.Type.ToDisplayString() == "System.IServiceProvider").SingleOrDefault();
if (symbol is not null)
{
Diagnostic diagnostic = Diagnostic.Create(NonSingletonUseServiceProviderDescriptor, symbol.Locations.FirstOrDefault());
context.ReportDiagnostic(diagnostic);
}
}
private static void HandleSingletonServiceCaptureNonSingletonService(SyntaxNodeAnalysisContext context)
{
//classSymbol.GetMembers().Where(m => m is IFieldSymbol { IsReadOnly: true, DeclaredAccessibility: Accessibility.Private } f);
}
}

View File

@@ -14,7 +14,7 @@ internal class LocalizedEnumGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Resource.Localization.LocalizationAttribute";
private const string LocalizationKeyName = "Snap.Hutao.Resource.Localization.LocalizationKeyAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<GeneratorSyntaxContext2> localizationEnums = context.SyntaxProvider
@@ -116,7 +116,7 @@ internal class LocalizedEnumGenerator : IIncrementalGenerator
.Where(m => m.Kind == SymbolKind.Field)
.Cast<IFieldSymbol>();
foreach(IFieldSymbol fieldSymbol in fields)
foreach (IFieldSymbol fieldSymbol in fields)
{
AttributeData? localizationKeyInfo = fieldSymbol.GetAttributes()
.SingleOrDefault(data => data.AttributeClass!.ToDisplayString() == LocalizationKeyName);

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.Primitive;
@@ -32,4 +33,9 @@ internal static class EnumerableExtension
while (enumerator.MoveNext());
}
}
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source)
{
return source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.SourceGeneration.Primitive;
internal static class GeneratorSyntaxContextExtension
{
public static bool TryGetDeclaredSymbol<TSymbol>(this GeneratorSyntaxContext context, System.Threading.CancellationToken token,[NotNullWhen(true)] out TSymbol? symbol)
public static bool TryGetDeclaredSymbol<TSymbol>(this GeneratorSyntaxContext context, System.Threading.CancellationToken token, [NotNullWhen(true)] out TSymbol? symbol)
where TSymbol : class, ISymbol
{
symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, token) as TSymbol;

View File

@@ -12,4 +12,10 @@ internal static class SymbolDisplayFormats
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
public static SymbolDisplayFormat QualifiedNonNullableFormat { get; } = new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
}

View File

@@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@@ -10,14 +10,6 @@
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug As Fake Elevated|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>
@@ -28,7 +20,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
</ItemGroup>
</Project>

View File

@@ -2,6 +2,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -16,16 +17,33 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor typeInternalDescriptor = new("SH001", "Type should be internal", "Type [{0}] should be internal", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor readOnlyStructRefDescriptor = new("SH002", "ReadOnly struct should be passed with ref-like key word", "ReadOnly Struct [{0}] should be passed with ref-like key word", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor useValueTaskIfPossibleDescriptor = new("SH003", "Use ValueTask instead of Task whenever possible", "Use ValueTask instead of Task", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor useIsNotNullPatternMatchingDescriptor = new("SH004", "Use \"is not null\" instead of \"!= null\" whenever possible", "Use \"is not null\" instead of \"!= null\"", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor useIsNullPatternMatchingDescriptor = new("SH005", "Use \"is null\" instead of \"== null\" whenever possible", "Use \"is null\" instead of \"== null\"", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor useIsPatternRecursiveMatchingDescriptor = new("SH006", "Use \"is { } obj\" whenever possible", "Use \"is {{ }} {0}\"", "Quality", DiagnosticSeverity.Info, true);
private static readonly DiagnosticDescriptor useArgumentNullExceptionThrowIfNullDescriptor = new("SH007", "Use \"ArgumentNullException.ThrowIfNull()\" instead of \"!\"", "Use \"ArgumentNullException.ThrowIfNull()\"", "Quality", DiagnosticSeverity.Info, true);
private static readonly ImmutableHashSet<string> RefLikeKeySkipTypes = new HashSet<string>()
{
"System.Threading.CancellationToken",
"System.Guid"
}.ToImmutableHashSet();
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return new DiagnosticDescriptor[]
{
typeInternalDescriptor,
readOnlyStructRefDescriptor,
}.ToImmutableArray();
{
typeInternalDescriptor,
readOnlyStructRefDescriptor,
useValueTaskIfPossibleDescriptor,
useIsNotNullPatternMatchingDescriptor,
useIsNullPatternMatchingDescriptor,
useIsPatternRecursiveMatchingDescriptor,
useArgumentNullExceptionThrowIfNullDescriptor
}.ToImmutableArray();
}
}
@@ -37,23 +55,34 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
context.RegisterCompilationStartAction(CompilationStart);
}
private void CompilationStart(CompilationStartAnalysisContext context)
private static void CompilationStart(CompilationStartAnalysisContext context)
{
SyntaxKind[] types = new SyntaxKind[]
SyntaxKind[] types =
{
SyntaxKind.ClassDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.EnumDeclaration
SyntaxKind.EnumDeclaration,
};
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
context.RegisterSyntaxNodeAction(HandleTypeDeclaration, types);
SyntaxKind[] expressions =
{
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression);
context.RegisterSyntaxNodeAction(HandleMethodDeclaration, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorDeclaration, SyntaxKind.ConstructorDeclaration);
// TODO add analyzer for unnecessary IServiceProvider registration
// TODO add analyzer for Singlton service use Scoped or Transient services
}
private void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context)
{
BaseTypeDeclarationSyntax syntax = (BaseTypeDeclarationSyntax)context.Node;
@@ -87,15 +116,10 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
}
}
private void HandleMethodDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask(SyntaxNodeAnalysisContext context)
{
MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node;
// 跳过异步方法,因为异步方法无法使用 ref in out
if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.AsyncKeyword)))
{
return;
}
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!;
// 跳过重载方法
if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.OverrideKeyword)))
@@ -103,12 +127,57 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
return;
}
// ICommand can only use Task or Task<T>
if (methodSymbol.GetAttributes().Any(attr => attr.AttributeClass!.ToDisplayString() == Automation.CommandGenerator.AttributeName))
{
return;
}
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task"))
{
Location location = methodSyntax.ReturnType.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(useValueTaskIfPossibleDescriptor, location);
context.ReportDiagnostic(diagnostic);
}
}
private static void HandleMethodParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context)
{
MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node;
// 跳过方法定义 如 接口
if (methodSyntax.Body == null)
{
return;
}
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!;
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task"))
{
return;
}
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.ValueTask"))
{
return;
}
foreach (SyntaxToken token in methodSyntax.Modifiers)
{
// 跳过异步方法,因为异步方法无法使用 ref/in/out
if (token.IsKind(SyntaxKind.AsyncKeyword))
{
return;
}
// 跳过重载方法
if (token.IsKind(SyntaxKind.OverrideKeyword))
{
return;
}
}
foreach (ParameterSyntax parameter in methodSyntax.ParameterList.Parameters)
{
if (context.SemanticModel.GetDeclaredSymbol(parameter) is IParameterSymbol symbol)
@@ -118,8 +187,7 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
continue;
}
// 跳过 CancellationToken
if (symbol.Type.ToDisplayString() == "System.Threading.CancellationToken")
if (RefLikeKeySkipTypes.Contains(symbol.Type.ToDisplayString()))
{
continue;
}
@@ -134,7 +202,7 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
}
}
private void HandleConstructorDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleConstructorParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context)
{
ConstructorDeclarationSyntax constructorSyntax = (ConstructorDeclarationSyntax)context.Node;
@@ -163,7 +231,63 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
}
}
private bool IsBuiltInType(ITypeSymbol symbol)
public static void HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching(SyntaxNodeAnalysisContext context)
{
BinaryExpressionSyntax syntax = (BinaryExpressionSyntax)context.Node;
if (syntax.IsKind(SyntaxKind.NotEqualsExpression) && syntax.Right.IsKind(SyntaxKind.NullLiteralExpression))
{
Location location = syntax.OperatorToken.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(useIsNotNullPatternMatchingDescriptor, location);
context.ReportDiagnostic(diagnostic);
}
else if (syntax.IsKind(SyntaxKind.EqualsExpression) && syntax.Right.IsKind(SyntaxKind.NullLiteralExpression))
{
Location location = syntax.OperatorToken.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(useIsNullPatternMatchingDescriptor, location);
context.ReportDiagnostic(diagnostic);
}
}
private static void HandleIsPatternShouldUseRecursivePattern(SyntaxNodeAnalysisContext context)
{
IsPatternExpressionSyntax syntax = (IsPatternExpressionSyntax)context.Node;
if (syntax.Pattern is DeclarationPatternSyntax declaration)
{
ITypeSymbol? leftType = context.SemanticModel.GetTypeInfo(syntax.Expression).ConvertedType;
ITypeSymbol? rightType = context.SemanticModel.GetTypeInfo(declaration).ConvertedType;
if (SymbolEqualityComparer.Default.Equals(leftType, rightType))
{
Location location = declaration.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(useIsPatternRecursiveMatchingDescriptor, location, declaration.Designation);
context.ReportDiagnostic(diagnostic);
}
}
}
private static void HandleArgumentNullExceptionThrowIfNull(SyntaxNodeAnalysisContext context)
{
PostfixUnaryExpressionSyntax syntax = (PostfixUnaryExpressionSyntax)context.Node;
if (syntax.Operand is LiteralExpressionSyntax literal)
{
if (literal.IsKind(SyntaxKind.DefaultLiteralExpression))
{
return;
}
}
if (syntax.Operand is DefaultExpressionSyntax expression)
{
return;
}
Location location = syntax.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(useArgumentNullExceptionThrowIfNullDescriptor, location);
context.ReportDiagnostic(diagnostic);
}
private static bool IsBuiltInType(ITypeSymbol symbol)
{
return symbol.SpecialType switch
{

View File

@@ -1,61 +1,57 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Snap.Hutao.Test;
[TestClass]
public class DependencyInjectionTest
public sealed class DependencyInjectionTest
{
[ClassInitialize]
public void Setup()
{
}
private readonly IServiceProvider services = new ServiceCollection()
.AddSingleton<IService, ServiceA>()
.AddSingleton<IService, ServiceB>()
.AddScoped<IScopedService, ServiceA>()
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
.BuildServiceProvider();
[TestMethod]
public void OriginalTypeNotDiscoverable()
public void OriginalTypeCannotResolved()
{
IServiceProvider services = new ServiceCollection()
.AddSingleton<IService, ServiceA>()
.AddSingleton<IService, ServiceB>()
.BuildServiceProvider();
Assert.IsNull(services.GetService<ServiceA>());
Assert.IsNull(services.GetService<ServiceB>());
}
[TestMethod]
public void ScopedServiceInitializeMultipleTimesInScope()
{
IServiceProvider services = new ServiceCollection()
.AddScoped<IService, ServiceA>()
.BuildServiceProvider();
IServiceScopeFactory scopeFactory = services.GetRequiredService<IServiceScopeFactory>();
using (IServiceScope scope = scopeFactory.CreateScope())
{
IService service1 = scope.ServiceProvider.GetRequiredService<IService>();
IService service2 = scope.ServiceProvider.GetRequiredService<IService>();
Assert.AreNotEqual(service1.Id, service2.Id);
}
}
[TestMethod]
public void GenericServicesCanBeResolved()
{
IServiceProvider services = new ServiceCollection()
.AddTransient(typeof(IGenericService<>),typeof(GenericService<>))
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
.BuildServiceProvider();
Assert.IsNotNull(services.GetService<IGenericService<int>>());
}
[TestMethod]
public void ScopedServiceInitializeMultipleTimesInScope()
{
using (IServiceScope scope = services.CreateScope())
{
IScopedService service1 = scope.ServiceProvider.GetRequiredService<IScopedService>();
IScopedService service2 = scope.ServiceProvider.GetRequiredService<IScopedService>();
Assert.AreNotEqual(service1.Id, service2.Id);
}
}
private interface IService
{
Guid Id { get; }
}
private sealed class ServiceA : IService
private interface IScopedService
{
Guid Id { get; }
}
private sealed class ServiceA : IService, IScopedService
{
public Guid Id
{

View File

@@ -13,13 +13,13 @@ public class JsonSerializeTest
}
""";
private const string SmapleNumberObjectJson = """
private const string SmapleEmptyStringObjectJson = """
{
"A" : ""
}
""";
private const string SmapleNumberDictionaryJson = """
private const string SmapleNumberKeyDictionaryJson = """
{
"111" : "12",
"222" : "34"
@@ -34,21 +34,11 @@ public class JsonSerializeTest
}
[TestMethod]
[ExpectedException(typeof(JsonException))]
public void EmptyStringCannotSerializeAsNumber()
{
bool caught = false;
try
{
// Throw
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleNumberObjectJson)!;
Assert.AreEqual(sample.A, 0);
}
catch
{
caught = true;
}
Assert.IsTrue(caught);
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleEmptyStringObjectJson)!;
Assert.AreEqual(sample.A, 0);
}
[TestMethod]
@@ -59,17 +49,17 @@ public class JsonSerializeTest
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
Dictionary<int,string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberDictionaryJson, options)!;
Dictionary<int,string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
Assert.AreEqual(sample[111], "12");
}
private class Sample
private sealed class Sample
{
public int A { get => B; set => B = value; }
public int B { get; set; }
}
private class StringNumberSample
private sealed class StringNumberSample
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int A { get; set; }

View File

@@ -3,7 +3,7 @@
namespace Snap.Hutao.Test.RuntimeBehavior;
[TestClass]
internal sealed class EnumRuntimeBehaviorTest
public sealed class EnumRuntimeBehaviorTest
{
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
@@ -20,7 +20,6 @@ internal sealed class EnumRuntimeBehaviorTest
}
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void EnumToStringDecimal()
{
Assert.AreEqual("2", EnumA.ValueB.ToString("D"));

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Snap.Hutao.Test.RuntimeBehavior;
[TestClass]
public class ForEachRuntimeBehaviorTest
public sealed class ForEachRuntimeBehaviorTest
{
[TestMethod]
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()

View File

@@ -3,7 +3,7 @@
namespace Snap.Hutao.Test.RuntimeBehavior;
[TestClass]
internal sealed class PropertyRuntimeBehaviorTest
public sealed class PropertyRuntimeBehaviorTest
{
[TestMethod]
public void GetTwiceOnPropertyResultsNotSame()

View File

@@ -2,7 +2,8 @@
namespace Snap.Hutao.Test.RuntimeBehavior;
public class RangeRuntimeBehaviorTest
[TestClass]
public sealed class RangeRuntimeBehaviorTest
{
[TestMethod]
public void RangeTrimLastOne()

View File

@@ -3,7 +3,7 @@
namespace Snap.Hutao.Test.RuntimeBehavior;
[TestClass]
internal sealed class StringRuntimeBehaviorTest
public sealed class StringRuntimeBehaviorTest
{
[TestMethod]
public unsafe void NullStringFixedIsNullPointer()
@@ -32,4 +32,4 @@ internal sealed class StringRuntimeBehaviorTest
ReadOnlySpan<char> testSpan = testStr;
Assert.IsTrue(testSpan.Length == 0);
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Snap.Hutao.Test.RuntimeBehavior;
[TestClass]
public sealed class UnsafeRuntimeBehaviorTest
{
[TestMethod]
public unsafe void UInt32AllSetIs()
{
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, };
fixed (byte* pBytes = bytes)
{
Assert.AreEqual(uint.MaxValue, *(uint*)pBytes);
}
}
}

View File

@@ -12,10 +12,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -119,6 +119,11 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]}
RESX_ShowErrorsInErrorList = False
RESX_SortFileContentOnSave = True
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
RESX_NeutralResourcesLanguage = zh-CN
RESX_AutoApplyExistingTranslations = False
EndGlobalSection
EndGlobal

View File

@@ -2,8 +2,10 @@
x:Class="Snap.Hutao.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwcw="using:CommunityToolkit.WinUI.Controls"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
xmlns:shvc="using:Snap.Hutao.View.Converter">
<Application.Resources>
@@ -42,16 +44,25 @@
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!-- OpenPaneLength -->
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
<!-- Length -->
<GridLength x:Key="CompatGridLength2">288</GridLength>
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength3">320</x:Double>
<x:Double x:Key="HomeAdaptiveCardHeight">180</x:Double>
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
<x:Double x:Key="LargeAppBarButtonWidth">100</x:Double>
<!-- ProgressBar -->
<x:Double x:Key="LargeBackgroundProgressBarOpacity">0.2</x:Double>
<ThemeShadow x:Key="CompatShadow"/>
<!-- Brushes -->
<SolidColorBrush x:Key="AvatarPropertyAddValueBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
<SolidColorBrush x:Key="BlueBrush" Color="#FF5180CB"/>
<SolidColorBrush x:Key="PurpleBrush" Color="#FFA156E0"/>
<SolidColorBrush x:Key="OrangeBrush" Color="#FFBC6932"/>
<SolidColorBrush x:Key="GuaranteePullBrush" Color="#FF0063FF"/>
<SolidColorBrush x:Key="UpPullBrush" Color="#FFFFA400"/>
<!-- Settings -->
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
@@ -65,6 +76,12 @@
<Setter Property="Margin" Value="1,29,0,5"/>
</Style.Setters>
</Style>
<Style
x:Key="SettingsContentComboBoxStyle"
BasedOn="{StaticResource DefaultComboBoxStyle}"
TargetType="ComboBox">
<Setter Property="MinWidth" Value="120"/>
</Style>
<!-- Uris -->
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
<x:String x:Key="DocumentLink_BugReport">https://hut.ao/statements/bug-report.html</x:String>
@@ -73,7 +90,15 @@
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
<x:String x:Key="Sponsor_Afadian">https://afdian.net/a/DismissedLight</x:String>
<!-- Images -->
<x:String x:Key="UI_ItemIcon_None">https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png</x:String>
<x:String x:Key="UI_MarkTower">https://static.snapgenshin.com/Bg/UI_MarkTower.png</x:String>
<x:String x:Key="UI_Icon_Intee_Explore_1">https://static.snapgenshin.com/Bg/UI_Icon_Intee_Explore_1.png</x:String>
<x:String x:Key="UI_MarkQuest_Events_Proce">https://static.snapgenshin.com/Bg/UI_MarkQuest_Events_Proce.png</x:String>
<x:String x:Key="UI_ItemIcon_201">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_201.png</x:String>
<x:String x:Key="UI_ItemIcon_204">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_204.png</x:String>
<x:String x:Key="UI_ItemIcon_210">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_210.png</x:String>
<x:String x:Key="UI_ItemIcon_220021">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_220021.png</x:String>
<x:String x:Key="UI_ImgSign_ItemIcon">https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png</x:String>
<x:String x:Key="UI_AvatarIcon_Costume_Card">https://static.snapgenshin.com/AvatarCard/UI_AvatarIcon_Costume_Card.png</x:String>
<x:String x:Key="UI_EmotionIcon25">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon25.png</x:String>
@@ -81,6 +106,16 @@
<x:String x:Key="UI_EmotionIcon250">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png</x:String>
<x:String x:Key="UI_EmotionIcon272">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png</x:String>
<x:String x:Key="UI_EmotionIcon293">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png</x:String>
<!-- FontIcon Content -->
<x:String x:Key="FontIconContentAdd">&#xE710;</x:String>
<x:String x:Key="FontIconContentSetting">&#xE713;</x:String>
<x:String x:Key="FontIconContentRefresh">&#xE72C;</x:String>
<x:String x:Key="FontIconContentDelete">&#xE74D;</x:String>
<x:String x:Key="FontIconContentFolder">&#xE8B7;</x:String>
<x:String x:Key="FontIconContentCheckList">&#xE9D5;</x:String>
<x:String x:Key="FontIconContentAsteriskBadge12">&#xEDAD;</x:String>
<x:String x:Key="FontIconContentZipFolder">&#xF012;</x:String>
<!-- Converters -->
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
@@ -110,8 +145,8 @@
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
<shvc:StringBoolConverter x:Key="StringBoolConverter"/>
<!-- Styles -->
<!-- Styles -->
<Style
x:Key="LargeGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
@@ -134,324 +169,85 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
</Style>
<Style x:Key="WebView2ContentDialogStyle" TargetType="ContentDialog">
<Setter Property="Foreground" Value="{ThemeResource ContentDialogForeground}"/>
<Setter Property="Background" Value="{ThemeResource ContentDialogBackground}"/>
<Setter Property="BorderThickness" Value="{ThemeResource ContentDialogBorderWidth}"/>
<Setter Property="BorderBrush" Value="{ThemeResource ContentDialogBorderBrush}"/>
<Style x:Key="BorderGridStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
</Style>
<Style TargetType="shci:CachedImage">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="LazyLoadingThreshold" Value="300"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentDialog">
<Border x:Name="Container">
<Grid x:Name="LayoutRoot" Visibility="Collapsed">
<Rectangle x:Name="SmokeLayerBackground" Fill="{ThemeResource ContentDialogSmokeFill}"/>
<Border
x:Name="BackgroundElement"
MinWidth="{ThemeResource ContentDialogMinWidth}"
MinHeight="{ThemeResource ContentDialogMinHeight}"
MaxWidth="{ThemeResource ContentDialogMaxWidth}"
MaxHeight="{ThemeResource ContentDialogMaxHeight}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
BackgroundSizing="InnerBorderEdge"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
FlowDirection="{TemplateBinding FlowDirection}"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<ScaleTransform x:Name="ScaleTransform"/>
</Border.RenderTransform>
<Grid x:Name="DialogSpace" CornerRadius="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer
x:Name="ContentScrollViewer"
HorizontalScrollBarVisibility="Disabled"
IsTabStop="False"
VerticalScrollBarVisibility="Disabled"
ZoomMode="Disabled">
<Grid
Padding="0"
BorderBrush="{ThemeResource ContentDialogSeparatorBorderBrush}"
BorderThickness="{ThemeResource ContentDialogSeparatorThickness}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl
x:Name="Title"
Margin="{ThemeResource ContentDialogTitleMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{TemplateBinding Title}"
ContentTemplate="{TemplateBinding TitleTemplate}"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
MaxLines="2"
TextWrapping="Wrap"/>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<ContentPresenter
x:Name="Content"
Grid.Row="1"
Margin="0,0,0,8"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="{StaticResource ControlContentThemeFontSize}"
Foreground="{TemplateBinding Foreground}"
TextWrapping="Wrap"/>
</Grid>
</ScrollViewer>
<Grid
x:Name="CommandSpace"
Grid.Row="1"
Padding="8,0,8,8"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
XYFocusKeyboardNavigation="Enabled">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="PrimaryColumn" Width="*"/>
<ColumnDefinition x:Name="FirstSpacer" Width="0"/>
<ColumnDefinition x:Name="SecondaryColumn" Width="0"/>
<ColumnDefinition x:Name="SecondSpacer" Width="{ThemeResource ContentDialogButtonSpacing}"/>
<ColumnDefinition x:Name="CloseColumn" Width="*"/>
</Grid.ColumnDefinitions>
<Button
x:Name="PrimaryButton"
HorizontalAlignment="Stretch"
Content="{TemplateBinding PrimaryButtonText}"
ElementSoundMode="FocusOnly"
IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}"
IsTabStop="False"
Style="{TemplateBinding PrimaryButtonStyle}"/>
<Button
x:Name="SecondaryButton"
HorizontalAlignment="Stretch"
Content="{TemplateBinding SecondaryButtonText}"
ElementSoundMode="FocusOnly"
IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}"
IsTabStop="False"
Style="{TemplateBinding SecondaryButtonStyle}"/>
<Button
x:Name="CloseButton"
Grid.Column="4"
HorizontalAlignment="Stretch"
Content="{TemplateBinding CloseButtonText}"
ElementSoundMode="FocusOnly"
IsTabStop="False"
Style="{TemplateBinding CloseButtonStyle}"/>
</Grid>
</Grid>
</Border>
</Grid>
<ControlTemplate TargetType="shci:CachedImage">
<Grid
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Image
Name="PlaceholderImage"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Opacity="1.0"
Source="{TemplateBinding PlaceholderSource}"
Stretch="{TemplateBinding PlaceholderStretch}"/>
<Image
Name="Image"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
NineGrid="{TemplateBinding NineGrid}"
Opacity="0.0"
Stretch="{TemplateBinding Stretch}"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DialogShowingStates">
<VisualStateGroup.Transitions>
<VisualTransition To="DialogHidden">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="False"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFastAnimationDuration}"
Value="1.05"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFastAnimationDuration}"
Value="1.05"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="DialogShowing">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.0"/>
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="DialogHidden"/>
<VisualState x:Name="DialogShowing">
<VisualState.Setters>
<Setter Target="PrimaryButton.IsTabStop" Value="True"/>
<Setter Target="SecondaryButton.IsTabStop" Value="True"/>
<Setter Target="CloseButton.IsTabStop" Value="True"/>
<Setter Target="LayoutRoot.Visibility" Value="Visible"/>
<Setter Target="BackgroundElement.TabFocusNavigation" Value="Cycle"/>
</VisualState.Setters>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Failed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="DialogShowingWithoutSmokeLayer">
<VisualState.Setters>
<Setter Target="PrimaryButton.IsTabStop" Value="True"/>
<Setter Target="SecondaryButton.IsTabStop" Value="True"/>
<Setter Target="CloseButton.IsTabStop" Value="True"/>
<Setter Target="LayoutRoot.Visibility" Value="Visible"/>
<Setter Target="LayoutRoot.Background" Value="{x:Null}"/>
</VisualState.Setters>
<VisualState x:Name="Loading">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DialogSizingStates">
<VisualState x:Name="DefaultDialogSizing"/>
<VisualState x:Name="FullDialogSizing">
<VisualState.Setters>
<Setter Target="BackgroundElement.VerticalAlignment" Value="Stretch"/>
</VisualState.Setters>
<VisualState x:Name="Loaded">
<Storyboard>
<DoubleAnimation
AutoReverse="False"
BeginTime="0"
Storyboard.TargetName="Image"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.5"/>
<DoubleAnimation
AutoReverse="False"
BeginTime="0"
Storyboard.TargetName="PlaceholderImage"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.5"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ButtonsVisibilityStates">
<VisualState x:Name="AllVisible">
<VisualState.Setters>
<Setter Target="FirstSpacer.Width" Value="{ThemeResource ContentDialogButtonSpacing}"/>
<Setter Target="SecondaryColumn.Width" Value="*"/>
<Setter Target="SecondaryButton.(Grid.Column)" Value="2"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NoneVisible">
<VisualState.Setters>
<Setter Target="CommandSpace.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PrimaryVisible">
<VisualState.Setters>
<Setter Target="PrimaryButton.(Grid.Column)" Value="4"/>
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SecondaryVisible">
<VisualState.Setters>
<Setter Target="SecondaryButton.(Grid.Column)" Value="4"/>
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="CloseVisible">
<VisualState.Setters>
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PrimaryAndSecondaryVisible">
<VisualState.Setters>
<Setter Target="SecondaryButton.(Grid.Column)" Value="4"/>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PrimaryAndCloseVisible">
<VisualState.Setters>
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SecondaryAndCloseVisible">
<VisualState.Setters>
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DefaultButtonStates">
<VisualState x:Name="NoDefaultButton"/>
<VisualState x:Name="PrimaryAsDefaultButton">
<VisualState.Setters>
<Setter Target="PrimaryButton.Style" Value="{StaticResource AccentButtonStyle}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SecondaryAsDefaultButton">
<VisualState.Setters>
<Setter Target="SecondaryButton.Style" Value="{StaticResource AccentButtonStyle}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="CloseAsDefaultButton">
<VisualState.Setters>
<Setter Target="CloseButton.Style" Value="{StaticResource AccentButtonStyle}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DialogBorderStates">
<VisualState x:Name="NoBorder"/>
<VisualState x:Name="AccentColorBorder">
<VisualState.Setters>
<Setter Target="BackgroundElement.BorderBrush" Value="{ThemeResource SystemControlForegroundAccentBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unloaded"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
@@ -460,9 +256,16 @@
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
<ItemsStackPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapPanelTemplate">
<cwcw:WrapPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
<!-- Transitions -->
<TransitionCollection x:Key="ReorderThemeTransitions">
<ReorderThemeTransition/>
</TransitionCollection>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -74,10 +74,10 @@ public sealed partial class App : Application
private void LogDiagnosticInformation()
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
logger.LogInformation("FamilyName: {name}", hutaoOptions.FamilyName);
logger.LogInformation("Version: {version}", hutaoOptions.Version);
logger.LogInformation("LocalCache: {folder}", hutaoOptions.LocalCache);
logger.LogInformation("FamilyName: {name}", runtimeOptions.FamilyName);
logger.LogInformation("Version: {version}", runtimeOptions.Version);
logger.LogInformation("LocalCache: {folder}", runtimeOptions.LocalCache);
}
}

View File

@@ -0,0 +1 @@
CA1501: 8

View File

@@ -0,0 +1,39 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.Foundation.Collections;
namespace Snap.Hutao.Control.Alternating;
[DependencyProperty("ItemAlternateBackground", typeof(Brush))]
internal sealed partial class AlternatingItemsControl : ItemsControl
{
private readonly VectorChangedEventHandler<object> itemsVectorChangedEventHandler;
public AlternatingItemsControl()
{
itemsVectorChangedEventHandler = OnItemsVectorChanged;
Items.VectorChanged += itemsVectorChangedEventHandler;
}
private void OnItemsVectorChanged(IObservableVector<object> items, IVectorChangedEventArgs args)
{
if (args.CollectionChange is CollectionChange.Reset)
{
int index = (int)args.Index;
for (int i = index; i < items.Count; i++)
{
if (items[i] is IAlternatingItem item)
{
item.Background = i % 2 is 0 ? default : ItemAlternateBackground;
}
else
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Media;
namespace Snap.Hutao.Control.Alternating;
internal interface IAlternatingItem
{
public Brush? Background { get; set; }
}

View File

@@ -13,4 +13,14 @@ internal static class AnimationDurations
/// 图片缩放动画
/// </summary>
public static readonly TimeSpan ImageZoom = TimeSpan.FromSeconds(0.5);
/// <summary>
/// 图像淡入
/// </summary>
public static readonly TimeSpan ImageFadeIn = TimeSpan.FromSeconds(0.3);
/// <summary>
/// 图像淡出
/// </summary>
public static readonly TimeSpan ImageFadeOut = TimeSpan.FromSeconds(0.2);
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI;
using CommunityToolkit.WinUI.UI.Animations;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Composition;
using System.Numerics;
@@ -21,7 +21,7 @@ internal sealed class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
{
Duration = AnimationDurations.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
To = Core.StringLiterals.OnePointOne;
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI;
using CommunityToolkit.WinUI.UI.Animations;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Composition;
using System.Numerics;
@@ -21,7 +21,7 @@ internal sealed class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
{
Duration = AnimationDurations.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
To = Core.StringLiterals.One;
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Behaviors;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
@@ -10,41 +10,30 @@ namespace Snap.Hutao.Control.Behavior;
/// 按给定比例自动调整高度的行为
/// </summary>
[HighQuality]
internal sealed class AutoHeightBehavior : BehaviorBase<FrameworkElement>
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoHeightBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
private readonly SizeChangedEventHandler sizeChangedEventHandler;
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
public AutoHeightBehavior()
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
sizeChangedEventHandler = OnSizeChanged;
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
protected override bool Initialize()
{
UpdateElement();
AssociatedObject.SizeChanged += OnSizeChanged;
AssociatedObject.SizeChanged += sizeChangedEventHandler;
return true;
}
/// <inheritdoc/>
protected override void OnDetaching()
protected override bool Uninitialize()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
base.OnDetaching();
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
return true;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
@@ -54,6 +43,6 @@ internal sealed class AutoHeightBehavior : BehaviorBase<FrameworkElement>
private void UpdateElement()
{
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
AssociatedObject.Height = AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Behaviors;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
@@ -10,41 +10,30 @@ namespace Snap.Hutao.Control.Behavior;
/// 按给定比例自动调整高度的行为
/// </summary>
[HighQuality]
internal sealed class AutoWidthBehavior : BehaviorBase<FrameworkElement>
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoWidthBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
private readonly SizeChangedEventHandler sizeChangedEventHandler;
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
public AutoWidthBehavior()
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
sizeChangedEventHandler = OnSizeChanged;
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
protected override bool Initialize()
{
UpdateElement();
AssociatedObject.SizeChanged += OnSizeChanged;
AssociatedObject.SizeChanged += sizeChangedEventHandler;
return true;
}
/// <inheritdoc/>
protected override void OnDetaching()
protected override bool Uninitialize()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
base.OnDetaching();
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
return true;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
@@ -54,6 +43,6 @@ internal sealed class AutoWidthBehavior : BehaviorBase<FrameworkElement>
private void UpdateElement()
{
AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
AssociatedObject.Width = AssociatedObject.Height * (TargetWidth / TargetHeight);
}
}

View File

@@ -1,51 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.UI.Behaviors;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// AppTitleBar Workaround
/// https://github.com/microsoft/microsoft-ui-xaml/issues/7756
/// </summary>
internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
{
private readonly IMessenger messenger;
/// <summary>
/// AppTitleBar Workaround
/// </summary>
public ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior()
{
messenger = Ioc.Default.GetRequiredService<IMessenger>();
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
AssociatedObject.DropDownOpened += OnDropDownOpened;
AssociatedObject.DropDownClosed += OnDropDownClosed;
}
/// <inheritdoc/>
protected override void OnDetaching()
{
AssociatedObject.DropDownOpened -= OnDropDownOpened;
AssociatedObject.DropDownClosed -= OnDropDownClosed;
base.OnDetaching();
}
private void OnDropDownOpened(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(true));
}
private void OnDropDownClosed(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(false));
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Behaviors;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
@@ -10,34 +10,14 @@ namespace Snap.Hutao.Control.Behavior;
/// 在元素加载完成后执行命令的行为
/// </summary>
[HighQuality]
internal sealed class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
[DependencyProperty("Command", typeof(ICommand))]
[DependencyProperty("CommandParameter", typeof(object))]
internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
{
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<ICommand>(nameof(Command));
private static readonly DependencyProperty CommandParameterProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<object>(nameof(CommandParameter));
/// <summary>
/// 待执行的命令
/// </summary>
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// 命令参数
/// </summary>
[MaybeNull]
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
if (Command != null && Command.CanExecute(CommandParameter))
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}

View File

@@ -17,6 +17,6 @@ internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction
public object Execute(object sender, object parameter)
{
FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
return null!;
return default!;
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Behavior;
internal sealed class SelectedItemInViewBehavior : BehaviorBase<ListViewBase>
{
protected override bool Initialize()
{
if (AssociatedObject.SelectedItem is { } item)
{
AssociatedObject.ScrollIntoView(item);
}
return true;
}
}

View File

@@ -12,16 +12,7 @@ namespace Snap.Hutao.Control;
/// when object is not used anymore.
/// </summary>
[HighQuality]
internal sealed class BindingProxy : DependencyObject
[DependencyProperty("DataContext", typeof(object))]
internal sealed partial class BindingProxy : DependencyObject
{
private static readonly DependencyProperty DataContextProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));
/// <summary>
/// 数据上下文
/// </summary>
public object? DataContext
{
get => GetValue(DataContextProperty);
set => SetValue(DataContextProperty, value);
}
}

View File

@@ -9,21 +9,6 @@ namespace Snap.Hutao.Control;
[HighQuality]
internal static class BoxedValues
{
/// <summary>
/// <see cref="double"/> 0
/// </summary>
public static readonly object DoubleZero = 0D;
/// <summary>
/// <see cref="double"/> 0
/// </summary>
public static readonly object DoubleOne = 1D;
/// <summary>
/// <see cref="int"/> 0
/// </summary>
public static readonly object Int32Zero = 0;
/// <summary>
/// <see cref="true"/>
/// </summary>

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.UI;
namespace Snap.Hutao.Control;
internal sealed class ColorSegment : IColorSegment
{
public ColorSegment(Color color, double value)
{
Color = color;
Value = value;
}
public Color Color { get; set; }
public double Value { get; set; }
}

View File

@@ -17,34 +17,13 @@ internal static class ContentDialogExtension
/// <param name="contentDialog">对话框</param>
/// <param name="taskContext">任务上下文</param>
/// <returns>用于恢复用户交互</returns>
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog, ITaskContext taskContext)
public static async ValueTask<ContentDialogHideToken> BlockAsync(this ContentDialog contentDialog, ITaskContext taskContext)
{
await taskContext.SwitchToMainThreadAsync();
contentDialog.ShowAsync().AsTask().SafeForget();
// E_ASYNC_OPERATION_NOT_STARTED 0x80000019
// Only a single ContentDialog can be open at any time.
return new ContentDialogHider(contentDialog, taskContext);
}
}
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1400")]
[SuppressMessage("", "SA1600")]
file readonly struct ContentDialogHider : IDisposable
{
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
public ContentDialogHider(ContentDialog contentDialog, ITaskContext taskContext)
{
this.contentDialog = contentDialog;
this.taskContext = taskContext;
}
public void Dispose()
{
// Hide() must be called on main thread.
taskContext.InvokeOnMainThread(contentDialog.Hide);
contentDialog.ShowAsync().AsTask().SafeForget();
return new ContentDialogHideToken(contentDialog, taskContext);
}
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Extension;
internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable
{
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
private bool disposed = false;
private bool disposing = false;
public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext)
{
this.contentDialog = contentDialog;
this.taskContext = taskContext;
}
public void Dispose()
{
if (!disposed && !disposing)
{
disposing = true;
taskContext.InvokeOnMainThread(contentDialog.Hide); // Hide() must be called on main thread.
disposing = false;
disposed = true;
}
}
public async ValueTask DisposeAsync()
{
if (!disposed && !disposing)
{
disposing = true;
await taskContext.SwitchToMainThreadAsync();
contentDialog.Hide();
disposing = false;
disposed = true;
}
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Extension;
internal static class DependencyObjectExtension
{
public static IServiceProvider ServiceProvider(this DependencyObject obj)
{
return Ioc.Default;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Extension;
internal static class FrameworkElementExtension
{
/// <summary>
/// Make properties below false:
/// <code>
/// * AllowFocusOnInteraction
/// * IsDoubleTapEnabled
/// * IsHitTestVisible
/// * IsHoldingEnabled
/// * IsRightTapEnabled
/// * IsTabStop
/// </code>
/// </summary>
/// <param name="frameworkElement">元素</param>
public static void DisableInteraction(this FrameworkElement frameworkElement)
{
frameworkElement.AllowFocusOnInteraction = false;
frameworkElement.IsDoubleTapEnabled = false;
frameworkElement.IsHitTestVisible = false;
frameworkElement.IsHoldingEnabled = false;
frameworkElement.IsRightTapEnabled = false;
frameworkElement.IsTabStop = false;
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("SquareLength", typeof(double), 0D, nameof(OnSquareLengthChanged), IsAttached = true, AttachedType = typeof(FrameworkElement))]
public sealed partial class FrameworkElementHelper
{
private static void OnSquareLengthChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
Microsoft.UI.Xaml.Controls.Control control = (Microsoft.UI.Xaml.Controls.Control)dp;
control.Width = (double)e.NewValue;
control.Height = (double)e.NewValue;
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.UI;
namespace Snap.Hutao.Control;
internal interface IColorSegment
{
Color Color { get; }
double Value { get; }
}

View File

@@ -32,16 +32,10 @@ internal sealed class CachedImage : ImageEx
try
{
Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri);
// BitmapImage need to be created by main thread.
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true);
// check token state to determine whether the operation should be canceled.
token.ThrowIfCancellationRequested();
// BitmapImage initialize with a uri will increase image quality and loading speed.
return new BitmapImage(new(file));
Verify.Operation(!string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri);
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed.
}
catch (COMException)
{
@@ -54,9 +48,5 @@ internal sealed class CachedImage : ImageEx
// task was explicitly canceled
return null;
}
catch
{
throw;
}
}
}

View File

@@ -52,12 +52,15 @@ internal static class CompositionExtension
Mode = blendEffectMode,
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
using (effect)
{
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter(Background, background);
brush.SetSourceParameter(Foreground, foreground);
brush.SetSourceParameter(Background, background);
brush.SetSourceParameter(Foreground, foreground);
return brush;
return brush;
}
}
/// <summary>
@@ -75,11 +78,14 @@ internal static class CompositionExtension
Source = new CompositionEffectSourceParameter(Source),
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
using (effect)
{
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter(Source, source);
brush.SetSourceParameter(Source, source);
return brush;
return brush;
}
}
/// <summary>
@@ -97,11 +103,14 @@ internal static class CompositionExtension
Source = new CompositionEffectSourceParameter(Source),
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
using (effect)
{
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter(Source, sourceBrush);
brush.SetSourceParameter(Source, sourceBrush);
return brush;
return brush;
}
}
/// <summary>
@@ -116,18 +125,21 @@ internal static class CompositionExtension
CompositionBrush sourceBrush,
CompositionBrush alphaMask)
{
AlphaMaskEffect maskEffect = new()
AlphaMaskEffect effect = new()
{
AlphaMask = new CompositionEffectSourceParameter(AlphaMask),
Source = new CompositionEffectSourceParameter(Source),
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(maskEffect).CreateBrush();
using (effect)
{
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter(AlphaMask, alphaMask);
brush.SetSourceParameter(Source, sourceBrush);
brush.SetSourceParameter(AlphaMask, alphaMask);
brush.SetSourceParameter(Source, sourceBrush);
return brush;
return brush;
}
}
/// <summary>

View File

@@ -1,16 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Animations;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Animation;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Service.Notification;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using Windows.Foundation;
namespace Snap.Hutao.Control.Image;
@@ -19,50 +22,37 @@ namespace Snap.Hutao.Control.Image;
/// 为其他图像类控件提供基类
/// </summary>
[HighQuality]
internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
[DependencyProperty("EnableLazyLoading", typeof(bool), true, nameof(OnSourceChanged))]
[DependencyProperty("Source", typeof(Uri), default!, nameof(OnSourceChanged))]
internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
private static readonly DependencyProperty EnableLazyLoadingProperty = Property<CompositionImage>.DependBoxed<bool>(nameof(EnableLazyLoading), BoxedValues.True);
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
private readonly ConcurrentCancellationTokenSource loadingTokenSource = new();
private readonly IServiceProvider serviceProvider;
private readonly RoutedEventHandler unloadEventHandler;
private readonly SizeChangedEventHandler sizeChangedEventHandler;
private readonly TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs> loadedImageSourceLoadCompletedEventHandler;
private TaskCompletionSource? surfaceLoadTaskCompletionSource;
private SpriteVisual? spriteVisual;
private bool isShow = true;
/// <summary>
/// 构造一个新的单色图像
/// </summary>
public CompositionImage()
protected CompositionImage()
{
serviceProvider = Ioc.Default;
serviceProvider = this.ServiceProvider();
this.DisableInteraction();
AllowFocusOnInteraction = false;
IsDoubleTapEnabled = false;
IsHitTestVisible = false;
IsHoldingEnabled = false;
IsRightTapEnabled = false;
IsTabStop = false;
unloadEventHandler = OnUnload;
Unloaded += unloadEventHandler;
SizeChanged += OnSizeChanged;
}
sizeChangedEventHandler = OnSizeChanged;
SizeChanged += sizeChangedEventHandler;
/// <summary>
/// 源
/// </summary>
public Uri Source
{
get => (Uri)GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
/// <summary>
/// 启用延迟加载
/// </summary>
public bool EnableLazyLoading
{
get => (bool)GetValue(EnableLazyLoadingProperty);
set => SetValue(EnableLazyLoadingProperty, value);
loadedImageSourceLoadCompletedEventHandler = OnLoadImageSurfaceLoadCompleted;
}
/// <summary>
@@ -73,26 +63,19 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
/// <returns>拼合视觉</returns>
protected abstract SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface);
/// <summary>
/// 异步加载图像表面
/// </summary>
/// <param name="file">文件</param>
/// <param name="token">取消令牌</param>
/// <returns>加载的图像表面</returns>
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
protected virtual void LoadImageSurfaceCompleted(LoadedImageSurface surface)
{
}
protected virtual void Unloading()
{
TaskCompletionSource loadCompleteTaskSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
await loadCompleteTaskSource.Task.ConfigureAwait(true);
return surface;
}
/// <summary>
/// 更新视觉对象
/// </summary>
/// <param name="spriteVisual">拼合视觉</param>
protected virtual void OnUpdateVisual(SpriteVisual spriteVisual)
protected virtual void UpdateVisual(SpriteVisual spriteVisual)
{
spriteVisual.Size = ActualSize;
}
@@ -100,7 +83,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{
CompositionImage image = (CompositionImage)sender;
CancellationToken token = LoadingTokenSource.Register(image);
CancellationToken token = image.loadingTokenSource.Register();
IServiceProvider serviceProvider = image.serviceProvider;
ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>();
@@ -127,7 +110,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
if (exception is HttpRequestException httpRequestException)
{
infoBarService.Error(httpRequestException, string.Format(SH.ControlImageCompositionImageHttpRequest, uri));
infoBarService.Error(httpRequestException, SH.ControlImageCompositionImageHttpRequest.Format(uri));
}
else
{
@@ -139,11 +122,11 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
}
}
private async Task ApplyImageAsync(Uri? uri, CancellationToken token)
private async ValueTask ApplyImageAsync(Uri? uri, CancellationToken token)
{
await HideAsync(token).ConfigureAwait(true);
if (uri != null)
if (uri is not null)
{
LoadedImageSurface? imageSurface = null;
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
@@ -164,18 +147,31 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
imageCache.Remove(uri);
}
if (imageSurface != null)
if (imageSurface is not null)
{
spriteVisual = CompositeSpriteVisual(compositor, imageSurface);
OnUpdateVisual(spriteVisual);
using (imageSurface)
{
spriteVisual = CompositeSpriteVisual(compositor, imageSurface);
UpdateVisual(spriteVisual);
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
await ShowAsync(token).ConfigureAwait(true);
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
await ShowAsync(token).ConfigureAwait(true);
}
}
}
}
private async Task ShowAsync(CancellationToken token)
private async ValueTask<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
{
surfaceLoadTaskCompletionSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
surface.LoadCompleted += loadedImageSourceLoadCompletedEventHandler;
await surfaceLoadTaskCompletionSource.Task.ConfigureAwait(true);
LoadImageSurfaceCompleted(surface);
return surface;
}
private async ValueTask ShowAsync(CancellationToken token)
{
if (!isShow)
{
@@ -183,7 +179,11 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
if (EnableLazyLoading)
{
await AnimationBuilder.Create().Opacity(from: 0D, to: 1D).StartAsync(this, token).ConfigureAwait(true);
await AnimationBuilder
.Create()
.Opacity(from: 0D, to: 1D, duration: AnimationDurations.ImageFadeIn)
.StartAsync(this, token)
.ConfigureAwait(true);
}
else
{
@@ -192,7 +192,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
}
}
private async Task HideAsync(CancellationToken token)
private async ValueTask HideAsync(CancellationToken token)
{
if (isShow)
{
@@ -200,7 +200,11 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
if (EnableLazyLoading)
{
await AnimationBuilder.Create().Opacity(from: 1D, to: 0D).StartAsync(this, token).ConfigureAwait(true);
await AnimationBuilder
.Create()
.Opacity(from: 1D, to: 0D, duration: AnimationDurations.ImageFadeOut)
.StartAsync(this, token)
.ConfigureAwait(true);
}
else
{
@@ -209,11 +213,27 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
}
}
private void OnLoadImageSurfaceLoadCompleted(LoadedImageSurface surface, LoadedImageSourceLoadCompletedEventArgs e)
{
surfaceLoadTaskCompletionSource?.TrySetResult();
surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize != e.PreviousSize && spriteVisual != null)
if (e.NewSize != e.PreviousSize && spriteVisual is not null)
{
OnUpdateVisual(spriteVisual);
UpdateVisual(spriteVisual);
}
}
private void OnUnload(object sender, RoutedEventArgs e)
{
Unloading();
spriteVisual?.Dispose();
spriteVisual = null;
SizeChanged -= sizeChangedEventHandler;
Unloaded -= unloadEventHandler;
}
}

View File

@@ -13,50 +13,27 @@ namespace Snap.Hutao.Control.Image;
/// 渐变图像
/// </summary>
[HighQuality]
internal sealed class Gradient : CompositionImage
[DependencyProperty("BackgroundDirection", typeof(GradientDirection), GradientDirection.TopToBottom)]
[DependencyProperty("ForegroundDirection", typeof(GradientDirection), GradientDirection.TopToBottom)]
internal sealed partial class Gradient : CompositionImage
{
private static readonly DependencyProperty BackgroundDirectionProperty = Property<Gradient>.Depend(nameof(BackgroundDirection), GradientDirection.TopToBottom);
private static readonly DependencyProperty ForegroundDirectionProperty = Property<Gradient>.Depend(nameof(ForegroundDirection), GradientDirection.TopToBottom);
private double imageAspectRatio;
/// <summary>
/// 背景方向
/// </summary>
public GradientDirection BackgroundDirection
{
get => (GradientDirection)GetValue(BackgroundDirectionProperty);
set => SetValue(BackgroundDirectionProperty, value);
}
/// <summary>
/// 前景方向
/// </summary>
public GradientDirection ForegroundDirection
{
get => (GradientDirection)GetValue(ForegroundDirectionProperty);
set => SetValue(ForegroundDirectionProperty, value);
}
/// <inheritdoc/>
protected override void OnUpdateVisual(SpriteVisual spriteVisual)
protected override void UpdateVisual(SpriteVisual? spriteVisual)
{
if (spriteVisual is not null)
if (spriteVisual is null)
{
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
spriteVisual.Size = ActualSize;
return;
}
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
spriteVisual.Size = ActualSize;
}
/// <inheritdoc/>
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
protected override void LoadImageSurfaceCompleted(LoadedImageSurface surface)
{
TaskCompletionSource loadCompleteTaskSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
await loadCompleteTaskSource.Task.ConfigureAwait(true);
imageAspectRatio = surface.NaturalSize.AspectRatio();
return surface;
}
/// <inheritdoc/>

View File

@@ -6,8 +6,6 @@ namespace Snap.Hutao.Control.Image;
/// <summary>
/// 渐变锚点
/// </summary>
/// <param name="Offset">便宜</param>
/// <param name="Color">颜色</param>
[HighQuality]
internal readonly struct GradientStop
{

View File

@@ -7,6 +7,7 @@ using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Theme;
using Windows.Foundation;
namespace Snap.Hutao.Control.Image;
@@ -16,6 +17,8 @@ namespace Snap.Hutao.Control.Image;
[HighQuality]
internal sealed class MonoChrome : CompositionImage
{
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
private CompositionColorBrush? backgroundBrush;
/// <summary>
@@ -23,15 +26,16 @@ internal sealed class MonoChrome : CompositionImage
/// </summary>
public MonoChrome()
{
ActualThemeChanged += OnActualThemeChanged;
actualThemeChangedEventHandler = OnActualThemeChanged;
ActualThemeChanged += actualThemeChangedEventHandler;
}
/// <inheritdoc/>
protected override SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface)
{
CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black);
CompositionColorBrush blackLayerBrush = compositor.CreateColorBrush(Colors.Black);
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay);
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBrush, imageSurfaceBrush, BlendEffectMode.Overlay);
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
backgroundBrush = compositor.CreateColorBrush();
@@ -41,9 +45,19 @@ internal sealed class MonoChrome : CompositionImage
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
}
protected override void Unloading()
{
ActualThemeChanged -= actualThemeChangedEventHandler;
backgroundBrush?.Dispose();
backgroundBrush = null;
base.Unloading();
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
if (backgroundBrush != null)
if (backgroundBrush is not null)
{
SetBackgroundColor(backgroundBrush);
}

View File

@@ -21,7 +21,7 @@ internal sealed class BitmapIconExtension : MarkupExtension
/// <summary>
/// Gets or sets a value indicating whether to display the icon as monochrome.
/// </summary>
public bool ShowAsMonochrome { get; set; } = true;
public bool ShowAsMonochrome { get; set; }
/// <inheritdoc/>
protected override object ProvideValue()

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;
[MarkupExtensionReturnType(ReturnType = typeof(int))]
internal sealed class Int32Extension : MarkupExtension
{
public string Value { get; set; } = default!;
protected override object ProvideValue()
{
_ = int.TryParse(Value, out int result);
return result;
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
using System.Globalization;
namespace Snap.Hutao.Control.Markup;
@@ -20,6 +21,6 @@ internal sealed class ResourceStringExtension : MarkupExtension
/// <inheritdoc/>
protected override object ProvideValue()
{
return SH.ResourceManager.GetString(Name ?? string.Empty) ?? Name ?? string.Empty;
return SH.ResourceManager.GetString(Name ?? string.Empty, CultureInfo.CurrentCulture) ?? Name ?? string.Empty;
}
}

View File

@@ -1,24 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;
/// <summary>
/// 类型拓展
/// </summary>
[MarkupExtensionReturnType(ReturnType = typeof(Type))]
internal sealed class TypeExtension : MarkupExtension
{
/// <summary>
/// 类型
/// </summary>
public Type Type { get; set; } = default!;
/// <inheritdoc/>
protected override object ProvideValue()
{
return Type;
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;
[MarkupExtensionReturnType(ReturnType = typeof(uint))]
internal sealed class UInt32Extension : MarkupExtension
{
public string Value { get; set; } = default!;
protected override object ProvideValue()
{
_ = uint.TryParse(Value, out uint result);
return result;
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Media;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Windows.UI;
@@ -38,7 +39,7 @@ internal struct Bgra32
/// </summary>
/// <param name="color">颜色</param>
/// <returns>新的 BGRA8 结构</returns>
public static unsafe Bgra32 FromColor(Color color)
public static unsafe implicit operator Bgra32(Color color)
{
Unsafe.SkipInit(out Bgra32 bgra8);
*(uint*)&bgra8 = BinaryPrimitives.ReverseEndianness(*(uint*)&color);

View File

@@ -0,0 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
// Some part of this file came from:
// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
namespace Snap.Hutao.Control.Media;
/// <summary>
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
/// </summary>
internal struct Hsl32
{
/// <summary>
/// The Hue in 0..360 range.
/// </summary>
public double H;
/// <summary>
/// The Saturation in 0..1 range.
/// </summary>
public double S;
/// <summary>
/// The Lightness in 0..1 range.
/// </summary>
public double L;
/// <summary>
/// The Alpha/opacity in 0..1 range.
/// </summary>
public double A;
}

View File

@@ -3,9 +3,7 @@
// Some part of this file came from:
// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
using CommunityToolkit.WinUI;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using Windows.UI;
namespace Snap.Hutao.Control.Media;
@@ -40,7 +38,7 @@ internal struct Rgba32
/// 构造一个新的 RGBA8 颜色
/// </summary>
/// <param name="hex">色值字符串</param>
public unsafe Rgba32(string hex)
public Rgba32(string hex)
: this(Convert.ToUInt32(hex, 16))
{
}
@@ -86,7 +84,7 @@ internal struct Rgba32
/// </summary>
/// <param name="hsl">HSL 颜色</param>
/// <returns>RGBA8颜色</returns>
public static Rgba32 FromHsl(HslColor hsl)
public static Rgba32 FromHsl(Hsl32 hsl)
{
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
double h1 = hsl.H / 60;
@@ -143,7 +141,7 @@ internal struct Rgba32
/// 转换到 HSL 颜色
/// </summary>
/// <returns>HSL 颜色</returns>
public HslColor ToHsl()
public readonly Hsl32 ToHsl()
{
const double toDouble = 1.0 / 255;
double r = toDouble * R;
@@ -176,7 +174,7 @@ internal struct Rgba32
double lightness = 0.5 * (max + min);
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
HslColor ret;
Hsl32 ret;
ret.H = 60 * h1;
ret.S = saturation;
ret.L = lightness;

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Windows.Foundation;
namespace Snap.Hutao.Control.Panel;
@@ -10,31 +9,12 @@ namespace Snap.Hutao.Control.Panel;
/// 纵横比控件
/// </summary>
[HighQuality]
internal class AspectRatio : Microsoft.UI.Xaml.Controls.Control
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AspectRatio : Microsoft.UI.Xaml.Controls.Control
{
private const double Epsilon = 2.2204460492503131e-016;
private static readonly DependencyProperty TargetWidthProperty = Property<AspectRatio>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AspectRatio>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
@@ -54,7 +34,7 @@ internal class AspectRatio : Microsoft.UI.Xaml.Controls.Control
}
// 更高
else if (ratioAvailable < ratio)
if (ratioAvailable < ratio)
{
double newHeight = availableSize.Width / ratio;
return new Size(availableSize.Width, newHeight);

View File

@@ -1,31 +1,20 @@
<SplitButton
<cwc:Segmented
x:Class="Snap.Hutao.Control.Panel.PanelSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
Name="RootSplitButton"
Padding="0,6"
Click="SplitButtonClick"
Loaded="OnRootControlLoaded"
mc:Ignorable="d">
<SplitButton.Content>
<FontIcon Name="IconPresenter" Glyph="&#xE8FD;"/>
</SplitButton.Content>
<SplitButton.Flyout>
<MenuFlyout>
<RadioMenuFlyoutItem
Click="RadioMenuFlyoutItemClick"
Icon="{shcm:FontIcon Glyph=&#xE8FD;}"
Tag="List"
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
<RadioMenuFlyoutItem
Click="RadioMenuFlyoutItemClick"
Icon="{shcm:FontIcon Glyph=&#xF0E2;}"
Tag="Grid"
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
<cwc:SegmentedItem
Icon="{shcm:FontIcon Glyph=&#xE8FD;}"
Tag="List"
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
<cwc:SegmentedItem
Icon="{shcm:FontIcon Glyph=&#xF0E2;}"
Tag="Grid"
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
</cwc:Segmented>

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Panel;
@@ -10,11 +10,21 @@ namespace Snap.Hutao.Control.Panel;
/// 面板选择器
/// </summary>
[HighQuality]
internal sealed partial class PanelSelector : SplitButton
[DependencyProperty("Current", typeof(string), List)]
internal sealed partial class PanelSelector : Segmented
{
private const string List = nameof(List);
public const string List = nameof(List);
public const string Grid = nameof(Grid);
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), List, OnCurrentChanged);
private static readonly Dictionary<int, string> IndexTypeMap = new()
{
[0] = List,
[1] = Grid,
};
private readonly RoutedEventHandler loadedEventHandler;
private readonly RoutedEventHandler unloadedEventHandler;
private readonly long selectedIndexChangedCallbackToken;
/// <summary>
/// 构造一个新的面板选择器
@@ -22,71 +32,31 @@ internal sealed partial class PanelSelector : SplitButton
public PanelSelector()
{
InitializeComponent();
loadedEventHandler = OnRootLoaded;
Loaded += loadedEventHandler;
unloadedEventHandler = OnRootUnload;
Unloaded += unloadedEventHandler;
selectedIndexChangedCallbackToken = RegisterPropertyChangedCallback(SelectedIndexProperty, OnSelectedIndexChanged);
}
/// <summary>
/// 当前选择
/// </summary>
public string Current
private void OnSelectedIndexChanged(DependencyObject sender, DependencyProperty dp)
{
get => (string)GetValue(CurrentProperty);
set => SetValue(CurrentProperty, value);
Current = IndexTypeMap[(int)GetValue(dp)];
}
private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
private void OnRootLoaded(object sender, RoutedEventArgs e)
{
OnCurrentChanged((PanelSelector)obj, (string)args.NewValue);
}
private static void OnCurrentChanged(PanelSelector sender, string current)
{
MenuFlyout menuFlyout = (MenuFlyout)sender.RootSplitButton.Flyout;
RadioMenuFlyoutItem targetItem = menuFlyout.Items
.Cast<RadioMenuFlyoutItem>()
.Single(i => (string)i.Tag == current);
targetItem.IsChecked = true;
sender.IconPresenter.Glyph = ((FontIcon)targetItem.Icon).Glyph;
}
private void OnRootControlLoaded(object sender, RoutedEventArgs e)
{
// because the GroupName shares in global
// we have to implement a control scoped GroupName.
PanelSelector selector = (PanelSelector)sender;
MenuFlyout menuFlyout = (MenuFlyout)selector.RootSplitButton.Flyout;
int hash = GetHashCode();
foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast<RadioMenuFlyoutItem>())
{
item.GroupName = $"{nameof(PanelSelector)}GroupOf@{hash}";
}
OnCurrentChanged(selector, Current);
selector.SelectedItem = selector.Items.Cast<SegmentedItem>().Single(item => (string)item.Tag == Current);
}
private void SplitButtonClick(SplitButton sender, SplitButtonClickEventArgs args)
private void OnRootUnload(object sender, RoutedEventArgs e)
{
MenuFlyout menuFlyout = (MenuFlyout)sender.Flyout;
int i = 0;
for (; i < menuFlyout.Items.Count; i++)
{
if ((string)menuFlyout.Items[i].Tag == Current)
{
break;
}
}
++i;
i %= menuFlyout.Items.Count; // move the count index to 0
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)menuFlyout.Items[i];
item.IsChecked = true;
Current = (string)item.Tag;
}
private void RadioMenuFlyoutItemClick(object sender, RoutedEventArgs e)
{
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)sender;
Current = (string)item.Tag;
UnregisterPropertyChangedCallback(SelectedIndexProperty, selectedIndexChangedCallbackToken);
Loaded -= loadedEventHandler;
Unloaded -= unloadedEventHandler;
}
}

View File

@@ -1,117 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control;
/// <summary>
/// 快速创建 <see cref="TOwner"/> 的 <see cref="DependencyProperty"/>
/// </summary>
/// <typeparam name="TOwner">所有者的类型</typeparam>
[HighQuality]
internal static class Property<TOwner>
{
/// <summary>
/// 注册依赖属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty Depend<TProperty>(string name)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(default(TProperty)));
}
/// <summary>
/// 注册依赖属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">默认值</param>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty Depend<TProperty>(string name, TProperty defaultValue)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
/// 注册依赖属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">封装的默认值</param>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty DependBoxed<TProperty>(string name, object defaultValue)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
/// 注册依赖属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">封装的默认值</param>
/// <param name="callback">属性更改回调</param>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty DependBoxed<TProperty>(string name, object defaultValue, Action<DependencyObject, DependencyPropertyChangedEventArgs> callback)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue, new(callback)));
}
/// <summary>
/// 注册依赖属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">默认值</param>
/// <param name="callback">属性更改回调</param>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty Depend<TProperty>(
string name,
TProperty defaultValue,
Action<DependencyObject, DependencyPropertyChangedEventArgs> callback)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue, new(callback)));
}
/// <summary>
/// 注册附加属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <returns>注册的附加属性</returns>
public static DependencyProperty Attach<TProperty>(string name)
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(default(TProperty)));
}
/// <summary>
/// 注册附加属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">默认值</param>
/// <returns>注册的附加属性</returns>
public static DependencyProperty Attach<TProperty>(string name, TProperty defaultValue)
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
/// 注册附加属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">默认值</param>
/// <param name="callback">属性更改回调</param>
/// <returns>注册的附加属性</returns>
public static DependencyProperty Attach<TProperty>(
string name,
TProperty defaultValue,
Action<DependencyObject, DependencyPropertyChangedEventArgs> callback)
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(defaultValue, new(callback)));
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using Snap.Hutao.Service.Navigation;
@@ -17,17 +18,19 @@ namespace Snap.Hutao.Control;
internal class ScopedPage : Page
{
// Allow GC to Collect the IServiceScope
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(null!);
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(default!);
private readonly RoutedEventHandler unloadEventHandler;
private readonly CancellationTokenSource viewCancellationTokenSource = new();
private readonly IServiceScope currentScope;
/// <summary>
/// 构造一个新的页面
/// </summary>
public ScopedPage()
protected ScopedPage()
{
Unloaded += OnScopedPageUnloaded;
unloadEventHandler = OnUnloaded;
Unloaded += unloadEventHandler;
currentScope = Ioc.Default.CreateScope();
DisposePreviousScope();
@@ -46,27 +49,14 @@ internal class ScopedPage : Page
}
}
/// <summary>
/// 初始化
/// 应当在 InitializeComponent() 前调用
/// </summary>
/// <typeparam name="TViewModel">视图模型类型</typeparam>
public void InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
/// <summary>
/// 异步通知接收器
/// </summary>
/// <param name="extra">额外内容</param>
/// <returns>任务</returns>
public async Task NotifyRecipentAsync(INavigationData extra)
public async ValueTask NotifyRecipientAsync(INavigationData extra)
{
if (extra.Data != null && DataContext is INavigationRecipient recipient)
if (extra.Data is not null && DataContext is INavigationRecipient recipient)
{
await recipient.ReceiveAsync(extra).ConfigureAwait(false);
}
@@ -74,6 +64,19 @@ internal class ScopedPage : Page
extra.NotifyNavigationCompleted();
}
/// <summary>
/// 初始化
/// 应当在 InitializeComponent() 前调用
/// </summary>
/// <typeparam name="TViewModel">视图模型类型</typeparam>
protected void InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
/// <inheritdoc/>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
@@ -100,13 +103,13 @@ internal class ScopedPage : Page
{
if (e.Parameter is INavigationData extra)
{
NotifyRecipentAsync(extra).SafeForget();
NotifyRecipientAsync(extra).SafeForget();
}
}
private void OnScopedPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
private void OnUnloaded(object sender, RoutedEventArgs e)
{
DataContext = null;
Unloaded -= OnScopedPageUnloaded;
Unloaded -= unloadEventHandler;
}
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Control;
[DependencyProperty("Source", typeof(List<IColorSegment>), default!, nameof(OnSourceChanged))]
internal sealed partial class SegmentedBar : ContentControl
{
private readonly LinearGradientBrush brush = new() { StartPoint = new(0, 0), EndPoint = new(1, 0), };
public SegmentedBar()
{
Content = new Rectangle()
{
Fill = brush,
};
}
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SegmentedBar segmentedBar = (SegmentedBar)obj;
GradientStopCollection collection = segmentedBar.brush.GradientStops;
collection.Clear();
if (args.NewValue as List<IColorSegment> is [_, ..] list)
{
double total = list.Sum(seg => seg.Value);
double offset = 0;
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(list))
{
collection.Add(new GradientStop() { Color = segment.Color, Offset = offset, });
offset += segment.Value / total;
collection.Add(new GradientStop() { Color = segment.Color, Offset = offset, });
}
}
}
}

View File

@@ -1,14 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Control.Theme;
using Windows.Foundation;
using Windows.UI;
namespace Snap.Hutao.Control.Text;
@@ -19,83 +19,76 @@ namespace Snap.Hutao.Control.Text;
/// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
/// </summary>
[HighQuality]
internal sealed class DescriptionTextBlock : ContentControl
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
internal sealed partial class DescriptionTextBlock : ContentControl
{
private static readonly DependencyProperty DescriptionProperty = Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".Length;
private static readonly int ItalicTagFullLength = "<i></i>".Length;
private static readonly int ItalicTagLeftLength = "<i>".Length;
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
/// <summary>
/// 构造一个新的呈现描述文本的文本块
/// </summary>
public DescriptionTextBlock()
{
IsTabStop = false;
this.DisableInteraction();
Content = new TextBlock()
{
TextWrapping = TextWrapping.Wrap,
};
ActualThemeChanged += OnActualThemeChanged;
}
/// <summary>
/// 可绑定的描述文本
/// </summary>
public string Description
{
get => (string)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
actualThemeChangedEventHandler = OnActualThemeChanged;
ActualThemeChanged += actualThemeChangedEventHandler;
}
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock text = (TextBlock)((DescriptionTextBlock)d).Content;
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
ReadOnlySpan<char> description = (string)e.NewValue;
UpdateDescription(text, description);
UpdateDescription(textBlock, description);
}
private static void UpdateDescription(TextBlock text, in ReadOnlySpan<char> description)
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
{
text.Inlines.Clear();
textBlock.Inlines.Clear();
int last = 0;
for (int i = 0; i < description.Length;)
{
// newline
if (description.Slice(i, 2).SequenceEqual(@"\n"))
if (description[i..].StartsWith(@"\n"))
{
AppendText(text, description[last..i]);
AppendLineBreak(text);
AppendText(textBlock, description[last..i]);
AppendLineBreak(textBlock);
i += 1;
last = i;
}
// color tag
else if (description.Slice(i, 2).SequenceEqual("<c"))
else if (description[i..].StartsWith("<c"))
{
AppendText(text, description[last..i]);
AppendText(textBlock, description[last..i]);
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(text, description.Slice(i + ColorTagLeftLength, length), color);
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
i += length + ColorTagFullLength;
last = i;
}
// italic
else if (description.Slice(i, 2).SequenceEqual("<i"))
else if (description[i..].StartsWith("<i"))
{
AppendText(text, description[last..i]);
AppendText(textBlock, description[last..i]);
int length = description[(i + ItalicTagLeftLength)..].IndexOf('<');
AppendItalicText(text, description.Slice(i + ItalicTagLeftLength, length));
AppendItalicText(textBlock, description.Slice(i + ItalicTagLeftLength, length));
i += length + ItalicTagFullLength;
last = i;
@@ -107,7 +100,7 @@ internal sealed class DescriptionTextBlock : ContentControl
if (i == description.Length - 1)
{
AppendText(text, description[last..(i + 1)]);
AppendText(textBlock, description[last..(i + 1)]);
}
}
}
@@ -126,7 +119,8 @@ internal sealed class DescriptionTextBlock : ContentControl
}
else
{
HslColor hsl = color.ToHsl();
// Make lighter in light mode
Hsl32 hsl = color.ToHsl();
hsl.L *= 0.3;
targetColor = Rgba32.FromHsl(hsl);
}

View File

@@ -97,8 +97,273 @@
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<!-- TODO: When will DefaultInfoBarStyle added -->
<Style TargetType="InfoBar">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="CloseButtonStyle" Value="{StaticResource InfoBarCloseButtonStyle}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{ThemeResource InfoBarBorderBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource InfoBarBorderThickness}"/>
<Setter Property="AutomationProperties.LandmarkType" Value="Custom"/>
<Setter Property="AutomationProperties.IsDialog" Value="True"/>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="InfoBar">
<Border
x:Name="ContentRoot"
VerticalAlignment="Top"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<!-- Background is used here so that it overrides the severity status color if set. -->
<Grid
MinHeight="{ThemeResource InfoBarMinHeight}"
Padding="{StaticResource InfoBarContentRootPadding}"
HorizontalAlignment="Stretch"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- Icon -->
<ColumnDefinition Width="*"/>
<!-- Title, message, and action -->
<ColumnDefinition Width="Auto"/>
<!-- Close button -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="StandardIconArea" Visibility="Collapsed">
<TextBlock
x:Name="IconBackground"
Grid.Column="0"
Margin="{StaticResource InfoBarIconMargin}"
VerticalAlignment="Top"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource InfoBarIconFontSize}"
Foreground="{ThemeResource InfoBarInformationalSeverityIconBackground}"
Text="{StaticResource InfoBarIconBackgroundGlyph}"/>
<TextBlock
x:Name="StandardIcon"
Grid.Column="0"
Margin="{StaticResource InfoBarIconMargin}"
VerticalAlignment="Top"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource InfoBarIconFontSize}"
Foreground="{ThemeResource InfoBarInformationalSeverityIconForeground}"
Text="{StaticResource InfoBarInformationalIconGlyph}"/>
</Grid>
<Viewbox
x:Name="UserIconBox"
Grid.Column="0"
MaxWidth="{ThemeResource InfoBarIconFontSize}"
MaxHeight="{ThemeResource InfoBarIconFontSize}"
Margin="{ThemeResource InfoBarIconMargin}"
VerticalAlignment="Top"
Child="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.IconElement}"
Visibility="Collapsed"/>
<InfoBarPanel
Grid.Column="1"
Margin="{StaticResource InfoBarPanelMargin}"
HorizontalOrientationPadding="{StaticResource InfoBarPanelHorizontalOrientationPadding}"
VerticalOrientationPadding="{StaticResource InfoBarPanelVerticalOrientationPadding}">
<TextBlock
x:Name="Title"
FontSize="{StaticResource InfoBarTitleFontSize}"
FontWeight="{StaticResource InfoBarTitleFontWeight}"
Foreground="{ThemeResource InfoBarTitleForeground}"
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarTitleHorizontalOrientationMargin}"
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarTitleVerticalOrientationMargin}"
Text="{TemplateBinding Title}"
TextWrapping="WrapWholeWords"/>
<TextBlock
x:Name="Message"
FontSize="{StaticResource InfoBarMessageFontSize}"
FontWeight="{StaticResource InfoBarMessageFontWeight}"
Foreground="{ThemeResource InfoBarMessageForeground}"
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarMessageHorizontalOrientationMargin}"
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarMessageVerticalOrientationMargin}"
Text="{TemplateBinding Message}"
TextWrapping="WrapWholeWords"/>
<ContentPresenter
VerticalAlignment="Top"
Content="{TemplateBinding ActionButton}"
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarActionHorizontalOrientationMargin}"
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarActionVerticalOrientationMargin}">
<ContentPresenter.Resources>
<Style BasedOn="{StaticResource DefaultHyperlinkButtonStyle}" TargetType="HyperlinkButton">
<Style.Setters>
<Setter Property="Margin" Value="{StaticResource InfoBarHyperlinkButtonMargin}"/>
<Setter Property="Foreground" Value="{ThemeResource InfoBarHyperlinkButtonForeground}"/>
</Style.Setters>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</InfoBarPanel>
<ContentPresenter
Grid.Row="1"
Grid.Column="1"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<Button
Name="CloseButton"
Grid.Column="2"
Command="{TemplateBinding CloseButtonCommand}"
CommandParameter="{TemplateBinding CloseButtonCommandParameter}"
Style="{TemplateBinding CloseButtonStyle}">
<Button.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Button.Resources>
<Viewbox
Width="{StaticResource InfoBarCloseButtonGlyphSize}"
Height="{StaticResource InfoBarCloseButtonGlyphSize}"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<SymbolIcon Symbol="{StaticResource InfoBarCloseButtonSymbol}"/>
</Viewbox>
</Button>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SeverityLevels">
<VisualState x:Name="Informational"/>
<VisualState x:Name="Error">
<VisualState.Setters>
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarErrorSeverityBackgroundBrush}"/>
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconBackground}"/>
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarErrorIconGlyph}"/>
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconForeground}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarWarningSeverityBackgroundBrush}"/>
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconBackground}"/>
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarWarningIconGlyph}"/>
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconForeground}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Success">
<VisualState.Setters>
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarSuccessSeverityBackgroundBrush}"/>
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconBackground}"/>
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarSuccessIconGlyph}"/>
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconForeground}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="IconStates">
<VisualState x:Name="StandardIconVisible">
<VisualState.Setters>
<Setter Target="UserIconBox.Visibility" Value="Collapsed"/>
<Setter Target="StandardIconArea.Visibility" Value="Visible"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="UserIconVisible">
<VisualState.Setters>
<Setter Target="UserIconBox.Visibility" Value="Visible"/>
<Setter Target="StandardIconArea.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NoIconVisible"/>
</VisualStateGroup>
<VisualStateGroup>
<VisualState x:Name="CloseButtonVisible"/>
<VisualState x:Name="CloseButtonCollapsed">
<VisualState.Setters>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="InfoBarVisibility">
<VisualState x:Name="InfoBarVisible"/>
<VisualState x:Name="InfoBarCollapsed">
<VisualState.Setters>
<Setter Target="ContentRoot.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup>
<VisualState x:Name="ForegroundNotSet"/>
<VisualState x:Name="ForegroundSet">
<VisualState.Setters>
<Setter Target="Title.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
<Setter Target="Message.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -25,9 +25,9 @@ internal abstract class ValueConverter<TFrom, TTo> : IValueConverter
Ioc.Default
.GetRequiredService<ILogger<ValueConverter<TFrom, TTo>>>()
.LogError(ex, "值转换器异常");
}
return null;
throw;
}
#else
return Convert((TFrom)value);
#endif

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Abstraction;
/// </summary>
/// <typeparam name="TSelf">自身类型</typeparam>
[HighQuality]
internal interface ICloneable<TSelf>
internal interface ICloneable<out TSelf>
{
/// <summary>
/// 克隆

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Abstraction;
/// <typeparam name="T1">元组的第一个类型</typeparam>
/// <typeparam name="T2">元组的第二个类型</typeparam>
[HighQuality]
internal interface IDeconstructable<T1, T2>
internal interface IDeconstruct<T1, T2>
{
/// <summary>
/// 解构

View File

@@ -0,0 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics.Contracts;
namespace Snap.Hutao.Core.Abstraction;
internal interface IMappingFrom<TSelf, TFrom>
where TSelf : IMappingFrom<TSelf, TFrom>
{
[Pure]
static abstract TSelf From(TFrom source);
}
internal interface IMappingFrom<TSelf, T1, T2>
where TSelf : IMappingFrom<TSelf, T1, T2>
{
[Pure]
static abstract TSelf From(T1 t1, T2 t2);
}
internal interface IMappingFrom<TSelf, T1, T2, T3>
where TSelf : IMappingFrom<TSelf, T1, T2, T3>
{
[Pure]
static abstract TSelf From(T1 t1, T2 t2, T3 t3);
}

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Core.Annotation;
/// <summary>
/// 指示此方法为命令的调用方法
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
internal sealed class CommandAttribute : Attribute
{
/// <summary>

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Core.Annotation;
/// <summary>
/// 指示此类自动生成构造器
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
internal sealed class ConstructorGeneratedAttribute : Attribute
{
/// <summary>
@@ -16,6 +16,11 @@ internal sealed class ConstructorGeneratedAttribute : Attribute
{
}
/// <summary>
/// 是否调用基类构造函数
/// </summary>
public bool CallBaseConstructor { get; set; }
/// <summary>
/// 在构造函数中插入 HttpClient
/// </summary>

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Annotation;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
internal sealed class DependencyPropertyAttribute : Attribute
{
public DependencyPropertyAttribute(string name, Type type)
{
}
public DependencyPropertyAttribute(string name, Type type, object defaultValue)
{
}
public DependencyPropertyAttribute(string name, Type type, object defaultValue, string valueChangedCallbackName)
{
}
public bool IsAttached { get; set; }
public Type AttachedType { get; set; } = default!;
}

View File

@@ -1,12 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics;
namespace Snap.Hutao.Core.Annotation;
/// <summary>
/// 高质量代码
/// </summary>
[AttributeUsage(AttributeTargets.All, Inherited = false)]
internal class HighQualityAttribute : Attribute
[Conditional("DEBUG")]
internal sealed class HighQualityAttribute : Attribute
{
}

View File

@@ -2,22 +2,22 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.IO;
namespace Snap.Hutao.Core.Caching;
/// <summary>
/// 为图像缓存提供抽象
/// </summary>
/// <typeparam name="T">缓存类型</typeparam>
[HighQuality]
internal interface IImageCache : ICastableService
internal interface IImageCache : ICastService
{
/// <summary>
/// Gets the file path containing cached item for given Uri
/// </summary>
/// <param name="uri">Uri of the item.</param>
/// <returns>a string path</returns>
Task<string> GetFileFromCacheAsync(Uri uri);
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri);
/// <summary>
/// Removed items based on uri list passed

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.IO;
namespace Snap.Hutao.Core.Caching;
/// <summary>
@@ -15,5 +17,5 @@ internal interface IImageCacheFilePathOperation
/// <param name="category">分类</param>
/// <param name="fileName">文件名</param>
/// <returns>文件路径</returns>
string GetFilePathFromCategoryAndFileName(string category, string fileName);
ValueFile GetFileFromCategoryAndName(string category, string fileName);
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.IO;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
@@ -23,6 +24,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
{
private const string CacheFolderName = nameof(ImageCache);
// TODO: use FrozenDictionary
private static readonly Dictionary<int, TimeSpan> RetryCountToDelay = new()
{
[0] = TimeSpan.FromSeconds(4),
@@ -34,7 +36,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
};
private readonly ILogger logger;
private readonly HttpClient httpClient;
private readonly IHttpClientFactory httpClientFactory;
private readonly IServiceProvider serviceProvider;
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
@@ -46,12 +48,10 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
/// Initializes a new instance of the <see cref="ImageCache"/> class.
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="logger">日志器</param>
/// <param name="httpClientFactory">http客户端工厂</param>
public ImageCache(IServiceProvider serviceProvider)
{
logger = serviceProvider.GetRequiredService<ILogger<ImageCache>>();
httpClient = serviceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(nameof(ImageCache));
httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
this.serviceProvider = serviceProvider;
}
@@ -59,20 +59,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
/// <inheritdoc/>
public void RemoveInvalid()
{
string folder = GetCacheFolder();
string[] files = Directory.GetFiles(folder);
List<string> filesToDelete = new();
foreach (string file in files)
{
if (IsFileInvalid(file, false))
{
filesToDelete.Add(file);
}
}
RemoveInternal(filesToDelete);
RemoveInternal(Directory.GetFiles(GetCacheFolder()).Where(file => IsFileInvalid(file, false)));
}
/// <inheritdoc/>
@@ -84,7 +71,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
/// <inheritdoc/>
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
{
if (uriForCachedItems == null || uriForCachedItems.Length <= 0)
if (uriForCachedItems.Length <= 0)
{
return;
}
@@ -93,11 +80,10 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
string[] files = Directory.GetFiles(folder);
List<string> filesToDelete = new();
foreach (Uri uri in uriForCachedItems)
foreach (ref readonly Uri uri in uriForCachedItems)
{
string filePath = Path.Combine(folder, GetCacheFileName(uri));
if (Array.IndexOf(files, filePath) >= 0)
if (files.Contains(filePath))
{
filesToDelete.Add(filePath);
}
@@ -107,38 +93,40 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
}
/// <inheritdoc/>
public async Task<string> GetFileFromCacheAsync(Uri uri)
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
{
string fileName = GetCacheFileName(uri);
string filePath = Path.Combine(GetCacheFolder(), fileName);
if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0)
if (File.Exists(filePath) && new FileInfo(filePath).Length != 0)
{
TaskCompletionSource taskCompletionSource = new();
try
{
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
{
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
}
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
{
await task.ConfigureAwait(false);
}
return filePath;
}
concurrentTasks.TryRemove(fileName, out _);
}
finally
TaskCompletionSource taskCompletionSource = new();
try
{
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
{
taskCompletionSource.TrySetResult();
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
}
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
{
await task.ConfigureAwait(false);
}
concurrentTasks.TryRemove(fileName, out _);
}
finally
{
taskCompletionSource.TrySetResult();
}
return filePath;
}
/// <inheritdoc/>
public string GetFilePathFromCategoryAndFileName(string category, string fileName)
public ValueFile GetFileFromCategoryAndName(string category, string fileName)
{
Uri dummyUri = Web.HutaoEndpoints.StaticFile(category, fileName).ToUri();
return Path.Combine(GetCacheFolder(), GetCacheFileName(dummyUri));
@@ -173,16 +161,18 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
}
catch (Exception ex)
{
logger.LogWarning(ex, "Remove Cache Image Failed:{file}", filePath);
logger.LogWarning(ex, "Remove Cache Image Failed:{File}", filePath);
}
}
}
[SuppressMessage("", "SH003")]
private async Task DownloadFileAsync(Uri uri, string baseFile)
{
logger.LogInformation("Begin downloading for {uri}", uri);
logger.LogInformation("Begin downloading for {Uri}", uri);
int retryCount = 0;
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
while (retryCount < 6)
{
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
@@ -198,21 +188,24 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
}
}
}
else if (message.StatusCode == HttpStatusCode.NotFound)
switch (message.StatusCode)
{
// directly goto https://static.hut.ao
retryCount += 3;
}
else if (message.StatusCode == HttpStatusCode.TooManyRequests)
{
retryCount++;
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
logger.LogInformation("Retry {uri} after {delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false);
}
else
{
return;
case HttpStatusCode.NotFound:
// directly goto https://static.hut.ao
retryCount += 3;
break;
case HttpStatusCode.TooManyRequests:
{
retryCount++;
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
logger.LogInformation("Retry {Uri} after {Delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false);
break;
}
default:
return;
}
}
@@ -225,13 +218,15 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
private string GetCacheFolder()
{
if (cacheFolder == null)
if (cacheFolder is not null)
{
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
cacheFolder = info.FullName;
return cacheFolder;
}
return cacheFolder!;
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
cacheFolder = info.FullName;
return cacheFolder;
}
}

View File

@@ -34,7 +34,7 @@ internal sealed class CommandLineBuilder
/// <returns>命令行建造器</returns>
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
{
return AppendIf(name, value != null, value);
return AppendIf(name, value is not null, value);
}
/// <summary>
@@ -58,11 +58,13 @@ internal sealed class CommandLineBuilder
s.Append(WhiteSpace);
s.Append(key);
if (!string.IsNullOrEmpty(value))
if (string.IsNullOrEmpty(value))
{
s.Append(WhiteSpace);
s.Append(value);
continue;
}
s.Append(WhiteSpace);
s.Append(value);
}
return s.ToString();

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Database;
internal interface IReorderable
{
int Index { get; set; }
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Database;
internal sealed class ObservableReorderableDbCollection<T> : ObservableCollection<T>
where T : class, IReorderable
{
private readonly DbContext dbContext;
private bool previousChangeIsRemoved;
public ObservableReorderableDbCollection(List<T> items, DbContext dbContext)
: base(AdjustIndex(items))
{
this.dbContext = dbContext;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
previousChangeIsRemoved = true;
break;
case NotifyCollectionChangedAction.Add:
if (!previousChangeIsRemoved)
{
return;
}
OnReorder();
previousChangeIsRemoved = false;
break;
}
}
private static List<T> AdjustIndex(List<T> list)
{
Span<T> span = CollectionsMarshal.AsSpan(list);
for (int i = 0; i < list.Count; i++)
{
ref readonly T item = ref span[i];
item.Index = i;
}
return list;
}
private void OnReorder()
{
AdjustIndex((List<T>)Items);
DbSet<T> dbSet = dbContext.Set<T>();
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
{
dbSet.UpdateAndSave(item);
}
}
}

View File

@@ -35,8 +35,8 @@ internal static class QueryableExtension
/// <param name="token">取消令牌</param>
/// <returns>SQL返回个数</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
public static ValueTask<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
{
return source.Where(predicate).ExecuteDeleteAsync(token);
return source.Where(predicate).ExecuteDeleteAsync(token).AsValueTask();
}
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity.Database;
namespace Snap.Hutao.Core.Database;
@@ -13,7 +14,8 @@ namespace Snap.Hutao.Core.Database;
/// </summary>
/// <typeparam name="TEntity">实体的类型</typeparam>
/// <typeparam name="TMessage">消息的类型</typeparam>
internal sealed class ScopedDbCurrent<TEntity, TMessage>
[ConstructorGenerated]
internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TEntity>, new()
{
@@ -22,16 +24,6 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
private TEntity? current;
/// <summary>
/// 构造一个新的数据库当前项
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
public ScopedDbCurrent(IServiceProvider serviceProvider)
{
messenger = serviceProvider.GetRequiredService<IMessenger>();
this.serviceProvider = serviceProvider;
}
/// <summary>
/// 当前选中的项
/// </summary>
@@ -46,31 +38,86 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
return;
}
// TODO: Troubeshooting why the serviceProvider will NRE
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
// only update when not processing a deletion
if (value != null)
if (value is not null && current is not null)
{
if (current != null)
{
current.IsSelected = false;
dbSet.UpdateAndSave(current);
}
current.IsSelected = false;
dbSet.UpdateAndSave(current);
}
TMessage message = new() { OldValue = current, NewValue = value };
current = value;
if (current != null)
if (current is not null)
{
current.IsSelected = true;
dbSet.UpdateAndSave(current);
}
messenger.Send(message);
}
}
}
}
[ConstructorGenerated]
internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
where TEntityOnly : class, IEntityOnly<TEntity>
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TEntityOnly>, new()
{
private readonly IServiceProvider serviceProvider;
private readonly IMessenger messenger;
private TEntityOnly? current;
/// <summary>
/// 当前选中的项
/// </summary>
public TEntityOnly? Current
{
get => current;
set
{
// prevent useless sets
if (current == value)
{
return;
}
// TODO: Troubeshooting why the serviceProvider will NRE
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
// only update when not processing a deletion
if (value is not null)
{
if (current is not null)
{
current.Entity.IsSelected = false;
dbSet.UpdateAndSave(current.Entity);
}
}
TMessage message = new() { OldValue = current, NewValue = value };
current = value;
if (current is not null)
{
current.Entity.IsSelected = true;
dbSet.UpdateAndSave(current.Entity);
}
messenger.Send(message);
}
}

View File

@@ -17,6 +17,7 @@ internal static class SelectableExtension
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>
/// <returns>选中的值或默认值</returns>
/// <exception cref="InvalidOperationException">存在多个选中的值</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
where TSource : ISelectable

View File

@@ -6,6 +6,6 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// <summary>
/// 可转换类型服务
/// </summary>
internal interface ICastableService
internal interface ICastService
{
}

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// 有名称的对象
/// 指示该对象可通过名称区分
/// </summary>
[Obsolete("无意义的接口")]
[HighQuality]
internal interface INamedService
{

View File

@@ -4,12 +4,13 @@
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// <summary>
/// 海外服/Hoyolab 可区分
/// 海外服/HoYoLAB 可区分
/// </summary>
[Obsolete("Use IOverseaSupportFactory instead")]
internal interface IOverseaSupport
{
/// <summary>
/// 是否为 海外服/Hoyolab
/// 是否为 海外服/HoYoLAB
/// </summary>
public bool IsOversea { get; }
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.User;
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
internal interface IOverseaSupportFactory<TClient>
{
TClient Create(bool isOversea);
TClient CreateFor(UserAndUid userAndUid)
{
return Create(userAndUid.User.IsOversea);
}
}

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