Compare commits

..

646 Commits
1.6.5 ... 1.7.6

Author SHA1 Message Date
DismissedLight
53c1a0156d 1.7.6 package 2023-09-25 22:46:14 +08:00
DismissedLight
165b77aa80 Merge pull request #959 from DGP-Studio/develop 2023-09-25 22:32:54 +08:00
DismissedLight
47cd9807b2 Merge pull request #949 from DGP-Studio/l10n_develop 2023-09-25 22:31:29 +08:00
DismissedLight
cbaf1ef5ae Merge pull request #958 from DGP-Studio/develop 2023-09-25 22:30:36 +08:00
DismissedLight
c2d3bd1798 fix GI 4.1 metadata ccrash on spiralabyss & improve wiki avatar ui 2023-09-25 22:20:14 +08:00
Masterain
bfa5325a09 Create Qodana profile 2023-09-24 17:27:23 -07:00
DismissedLight
43aebf7d0c fix #956 2023-09-24 17:54:08 +08:00
DismissedLight
ffcfb8cd65 fix #948 2023-09-23 21:17:15 +08:00
DismissedLight
7d9612cf13 fix #865 2023-09-23 21:02:11 +08:00
DismissedLight
4fb997c8d0 fix non-elevated auto click 2023-09-23 15:11:08 +08:00
DismissedLight
6029acc7f1 mouse simulator 2023-09-23 14:55:47 +08:00
Lightczx
ef352303e7 remove statichutao 2023-09-22 17:31:00 +08:00
Lightczx
b5579aef6e static resouce download refactor and ui/ux improvement 2023-09-22 16:47:08 +08:00
Masterain
9c20e06387 New translations sh.resx (English) 2023-09-21 17:19:17 -07:00
Masterain
323e9fbcff New translations sh.resx (Chinese Traditional) 2023-09-21 17:19:16 -07:00
Masterain
10d947aa61 New translations sh.resx (Korean) 2023-09-21 17:19:15 -07:00
Masterain
c18581fc34 New translations sh.resx (Japanese) 2023-09-21 17:19:13 -07:00
DismissedLight
b72b5ddf91 refactor window controller 2023-09-21 22:45:40 +08:00
DismissedLight
2821f7f2af refine announcement color matching 2023-09-21 20:57:44 +08:00
Lightczx
427275d122 ui/ux 2023-09-21 17:30:42 +08:00
Lightczx
22393b612c fix #947 2023-09-21 15:03:11 +08:00
Masterain
ffc380f96d Update issue templates 2023-09-20 22:15:18 -07:00
DismissedLight
2a757dce51 UI/UX fine tuning 2023-09-20 23:50:24 +08:00
Masterain
0921671de4 Merge pull request #945 from DGP-Studio/develop
Update README.md
2023-09-19 23:31:47 -07:00
Masterain
5a99488582 Update README.md
Test purpose
2023-09-19 23:31:01 -07:00
Lightczx
a14c41d10f 1.7.5 package 2023-09-20 13:51:24 +08:00
DismissedLight
ce8aee2cb1 Merge pull request #944 from DGP-Studio/develop 2023-09-20 13:36:31 +08:00
DismissedLight
f9ffd84429 Merge pull request #943 from DGP-Studio/l10n_develop 2023-09-20 13:34:41 +08:00
Masterain
6e69064140 New translations sh.resx (English) 2023-09-19 22:34:22 -07:00
Masterain
ff315a3b58 New translations sh.resx (English) 2023-09-19 21:33:56 -07:00
Masterain
aaf28132a4 New translations sh.resx (Chinese Traditional) 2023-09-19 21:33:55 -07:00
Masterain
5730c82f84 New translations sh.resx (Korean) 2023-09-19 21:33:54 -07:00
Masterain
a71fd42d6a New translations sh.resx (Japanese) 2023-09-19 21:33:53 -07:00
Lightczx
3f818fce50 typo fix 2023-09-20 12:26:03 +08:00
Lightczx
301ead44a2 fix CI build 2023-09-20 11:38:54 +08:00
Masterain
ff79e81ff4 New translations sh.resx (English) 2023-09-19 20:33:18 -07:00
Masterain
d9e942319c New translations sh.resx (Chinese Traditional) 2023-09-19 20:33:17 -07:00
Masterain
4787798825 New translations sh.resx (Korean) 2023-09-19 20:33:16 -07:00
Masterain
fb398dbf21 New translations sh.resx (Japanese) 2023-09-19 20:33:15 -07:00
Lightczx
68a6834d39 fix #930 2023-09-20 11:28:34 +08:00
Lightczx
4285abd531 fix avatar template 2023-09-20 09:29:47 +08:00
Masterain
8b5f017bea Merge pull request #935 from DGP-Studio/l10n_develop
New Crowdin updates
2023-09-19 14:28:54 -07:00
Masterain
add9318a8e New translations sh.resx (English) 2023-09-19 13:53:06 -07:00
Masterain
997fcb0e51 New translations sh.resx (English) 2023-09-19 08:49:52 -07:00
Masterain
bd0832ca98 New translations sh.resx (Chinese Traditional) 2023-09-19 08:49:51 -07:00
Masterain
4545017a3b New translations sh.resx (Korean) 2023-09-19 08:49:50 -07:00
Masterain
d28cd36162 New translations sh.resx (Japanese) 2023-09-19 08:49:48 -07:00
DismissedLight
cf8a9581d6 spiral abyss rework done 2023-09-19 23:43:25 +08:00
Masterain
b5d12f5f5f New translations sh.resx (English) 2023-09-19 03:17:39 -07:00
Masterain
7ddf51ef91 New translations sh.resx (Chinese Traditional) 2023-09-19 03:17:38 -07:00
Masterain
c2c529f399 New translations sh.resx (Korean) 2023-09-19 03:17:35 -07:00
Masterain
63028170b8 New translations sh.resx (Japanese) 2023-09-19 03:17:34 -07:00
Lightczx
156740de14 spiral abyss rework 4 2023-09-19 17:14:17 +08:00
Masterain
db82cd70ee New translations sh.resx (Japanese) 2023-09-19 00:59:17 -07:00
Masterain
d04f6a9218 New translations sh.resx (Japanese) 2023-09-18 23:22:05 -07:00
DismissedLight
f2365604dc spiral abyss rework 3 2023-09-18 23:48:33 +08:00
Lightczx
f569f24213 ui/ux adjust 2023-09-18 16:25:58 +08:00
DismissedLight
ca468365ab Merge pull request #938 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-8af96ff2ab 2023-09-18 15:40:37 +08:00
Lightczx
5fa8cc37e8 fix #880 2023-09-18 15:38:23 +08:00
dependabot[bot]
ab78f68df1 Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [Microsoft.EntityFrameworkCore.Sqlite](https://github.com/dotnet/efcore) and [Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/efcore).


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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 07:31:14 +00:00
Masterain
62fdd89943 New translations SH.resx (Chinese Traditional) 2023-09-18 00:05:37 -07:00
Masterain
7fa19dd830 New translations SH.resx (Chinese Traditional) 2023-09-17 22:48:26 -07:00
Masterain
f4100f1c20 New translations SH.resx (English) 2023-09-17 21:26:43 -07:00
Lightczx
7f595a6981 fix spiral abyss record refresh 2023-09-18 11:44:36 +08:00
Masterain
60f2020b6c New translations SH.resx (English) 2023-09-17 20:21:13 -07:00
Masterain
d21d59efe1 New translations SH.resx (Chinese Traditional) 2023-09-17 20:21:12 -07:00
Masterain
c14847b330 New translations SH.resx (Korean) 2023-09-17 20:21:10 -07:00
Masterain
86481b747d New translations SH.resx (Japanese) 2023-09-17 20:21:09 -07:00
Lightczx
2765a2e744 impl #875 2023-09-18 11:19:25 +08:00
Lightczx
261794927c impl #929 2023-09-18 10:02:58 +08:00
DismissedLight
68d8baa70c spiral abyss rework 2 2023-09-17 23:33:40 +08:00
Masterain
5e44dade7f New translations SH.resx (English) 2023-09-17 04:14:15 -07:00
Masterain
529eafc4e7 New translations SH.resx (Chinese Traditional) 2023-09-17 04:14:14 -07:00
Masterain
be3fa7054c New translations SH.resx (Korean) 2023-09-17 04:14:13 -07:00
Masterain
01cf457386 New translations SH.resx (Japanese) 2023-09-17 04:14:12 -07:00
DismissedLight
fb496497bf fix date format 2023-09-17 17:54:02 +08:00
Masterain
68a6af9341 Merge pull request #923 from DGP-Studio/l10n_develop
New Crowdin updates
2023-09-17 02:15:02 -07:00
Masterain
1ec5b2fa5e New translations SH.resx (English) 2023-09-17 02:08:04 -07:00
Masterain
aad57b67a6 New translations SH.resx (Chinese Traditional) 2023-09-17 01:06:47 -07:00
Masterain
9262217271 New translations SH.resx (Korean) 2023-09-17 01:06:46 -07:00
Masterain
b09e8b6bdc New translations SH.resx (Japanese) 2023-09-17 01:06:45 -07:00
Masterain
7c51a8ea6b New translations SH.resx (English) 2023-09-17 01:06:44 -07:00
DismissedLight
3db52e8184 spiral abyss rework 2023-09-17 16:05:10 +08:00
Masterain
9e660a7ae7 New translations SH.resx (Chinese Traditional) 2023-09-16 07:08:03 -07:00
Masterain
4127a1daa3 New translations SH.resx (Korean) 2023-09-16 07:08:02 -07:00
Masterain
4e07af8395 New translations SH.resx (Japanese) 2023-09-16 07:08:01 -07:00
Masterain
02707c3075 New translations SH.resx (English) 2023-09-16 07:08:00 -07:00
DismissedLight
98ca533706 add tower wave spec 2023-09-16 21:56:28 +08:00
DismissedLight
808b643b9f drop CommunityToolkit.WinUI.UI.Core 2023-09-16 19:28:34 +08:00
Masterain
d9705b94d4 New translations SH.resx (English) 2023-09-15 20:46:17 -07:00
Masterain
511471d06c New translations SH.resx (Japanese) 2023-09-15 03:10:43 -07:00
Lightczx
3b79d89f59 attempt to fix dailynote view verify 2023-09-15 17:27:28 +08:00
Masterain
fc1546e17f New translations SH.resx (Chinese Traditional) 2023-09-15 00:52:35 -07:00
Masterain
b816b0103d New translations SH.resx (Korean) 2023-09-15 00:52:34 -07:00
Masterain
51570038bc New translations SH.resx (Japanese) 2023-09-15 00:52:33 -07:00
Masterain
0b5b911709 New translations SH.resx (English) 2023-09-15 00:52:32 -07:00
Lightczx
1a22303dec impl #876 2023-09-15 15:47:07 +08:00
Masterain
51a2fe7d88 New translations SH.resx (Japanese) 2023-09-14 23:36:21 -07:00
Masterain
90c6c8ebc7 New translations SH.resx (Japanese) 2023-09-14 19:51:16 -07:00
Masterain
ed1d6240bf New translations SH.resx (Japanese) 2023-09-14 18:45:48 -07:00
Masterain
adf01172e0 New translations SH.resx (Japanese) 2023-09-14 01:09:32 -07:00
Masterain
2caf1145c9 New translations SH.resx (Japanese) 2023-09-14 00:10:43 -07:00
Masterain
33b2725a95 New translations SH.resx (Japanese) 2023-09-13 22:48:15 -07:00
Masterain
0103834e82 New translations SH.resx (Japanese) 2023-09-13 21:41:03 -07:00
Masterain
8273cd973d New translations SH.resx (Japanese) 2023-09-13 19:47:49 -07:00
DismissedLight
53c484c577 fix achievement check status save 2023-09-13 21:26:58 +08:00
DismissedLight
706f894ea5 fix #924 2023-09-13 20:56:07 +08:00
Masterain
7f31010041 New translations SH.resx (Japanese) 2023-09-13 02:17:10 -07:00
Masterain
70a17f325e New translations SH.resx (Chinese Traditional) 2023-09-13 00:30:06 -07:00
Masterain
a4d450a98e New translations SH.resx (Japanese) 2023-09-13 00:30:04 -07:00
Masterain
cce08ff728 New translations SH.resx (Chinese Traditional) 2023-09-12 07:27:55 -07:00
Masterain
669713389a New translations SH.resx (Korean) 2023-09-12 07:27:54 -07:00
Masterain
be82bc5d82 New translations SH.resx (Japanese) 2023-09-12 07:27:53 -07:00
Masterain
aa01154301 New translations SH.resx (English) 2023-09-12 07:27:51 -07:00
DismissedLight
2ff8d6d7f4 achievement versioning 2023-09-12 22:14:40 +08:00
Masterain
79f62bdbd2 New translations SH.resx (Japanese) 2023-09-12 01:30:52 -07:00
Lightczx
92c010f660 auto generate key annotations 2023-09-12 16:29:53 +08:00
Lightczx
18433814e8 salts auto generated 2023-09-12 16:11:31 +08:00
Lightczx
79c8cc78c6 Merge branch 'main' into develop 2023-09-12 15:05:23 +08:00
Lightczx
f6ec0f5d7e 1.7.4 package 2023-09-12 15:03:14 +08:00
Masterain
6e20c15c97 New translations SH.resx (Japanese) 2023-09-12 00:00:28 -07: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
8b3a311fc2 Merge pull request #885 from DGP-Studio/l10n_develop 2023-09-12 14:49:31 +08:00
Lightczx
01e2e21979 Update SignInClient.cs 2023-09-12 14:46:34 +08:00
Lightczx
38b5e4f244 fix signin challenge source 2023-09-12 09:21:28 +08:00
DismissedLight
5fae9f6291 UIIF definition 2023-09-11 23:09:49 +08:00
DismissedLight
f23b1c012f drop AdaptiveGridView 2023-09-11 22:33:44 +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
Masterain
16fb1772ab New translations SH.resx (English) 2023-09-10 18:15:23 -07:00
Lightczx
52353b7a5a overwrite icon when creating shortcut 2023-09-11 09:05:03 +08:00
Masterain
8c63a38249 New translations SH.resx (English) 2023-09-10 03:48:09 -07:00
DismissedLight
91d90cb3fa fix cultivate project selection on other page 2023-09-10 18:23:59 +08:00
Masterain
583717d601 New translations SH.resx (Chinese Traditional) 2023-09-10 02:45:01 -07:00
Masterain
dd34815b13 New translations SH.resx (Korean) 2023-09-10 02:45:00 -07:00
Masterain
8f18a89727 New translations SH.resx (Japanese) 2023-09-10 02:44:59 -07:00
Masterain
759689e2c9 New translations SH.resx (English) 2023-09-10 02:44:58 -07:00
DismissedLight
396da901a1 improve db layer & homecard experience 2023-09-10 17:38:18 +08:00
DismissedLight
f2c38bc72a fix #917 2023-09-10 12:51:14 +08:00
Masterain
b5ddc20d5f New translations SH.resx (English) 2023-09-09 15:49:58 -07:00
Masterain
f5af6e7b1a New translations SH.resx (English) 2023-09-09 14:50:21 -07:00
Masterain
772668a266 New translations SH.resx (Chinese Traditional) 2023-09-09 02:26:46 -07:00
Masterain
c342efe4be New translations SH.resx (Korean) 2023-09-09 02:26:45 -07:00
Masterain
9ff322cceb New translations SH.resx (Japanese) 2023-09-09 02:26:44 -07:00
Masterain
d2e8e6dac3 New translations SH.resx (English) 2023-09-09 02:26:43 -07:00
DismissedLight
1cbf3b548f batch add for avatarinfo cultivation 2023-09-09 17:22:38 +08:00
DismissedLight
ffd7468913 boost up gacha statistics view speed 2x 2023-09-08 20:53:06 +08:00
Masterain
1029175071 New translations SH.resx (Japanese) 2023-09-08 03:47:11 -07:00
Lightczx
b5f3cb92b2 fix bilibili server convert 2023-09-08 17:14:50 +08:00
Masterain
8d8a2af063 New translations SH.resx (Japanese) 2023-09-08 01:36:03 -07:00
Masterain
43bee2bd3e New translations SH.resx (Japanese) 2023-09-08 00:17:39 -07:00
Masterain
abce4571ea New translations SH.resx (Japanese) 2023-09-07 20:17:05 -07:00
Masterain
904335cfc9 New translations SH.resx (Japanese) 2023-09-07 19:13:07 -07:00
Masterain
e9a821e4af New translations SH.resx (Chinese Traditional) 2023-09-07 10:16:45 -07:00
Masterain
8f2d2773e1 New translations SH.resx (English) 2023-09-07 10:16:44 -07:00
Masterain
4352ba64f8 New translations SH.resx (Chinese Traditional) 2023-09-07 08:27:58 -07:00
Masterain
670c4366c4 New translations SH.resx (Korean) 2023-09-07 08:27:57 -07:00
Masterain
46c6a973c6 New translations SH.resx (Japanese) 2023-09-07 08:27:55 -07:00
Masterain
455505f0cb New translations SH.resx (English) 2023-09-07 08:27:54 -07:00
DismissedLight
8e625e19ee fix #907 2023-09-07 23:06:09 +08:00
DismissedLight
1fefcacd9a fix #887 2023-09-07 22:21:22 +08:00
Masterain
2b585964f7 New translations SH.resx (Chinese Traditional) 2023-09-07 06:58:43 -07:00
Masterain
6e541dfeb5 New translations SH.resx (Korean) 2023-09-07 06:58:42 -07:00
Masterain
2c9af48cb2 New translations SH.resx (Japanese) 2023-09-07 06:58:41 -07:00
Masterain
d384915c9d New translations SH.resx (English) 2023-09-07 06:58:40 -07:00
DismissedLight
1d923f4b24 fix #904 2023-09-07 21:51:18 +08:00
Masterain
0997532564 New translations SH.resx (Japanese) 2023-09-07 03:15:05 -07:00
Masterain
dd9053a11c New translations SH.resx (English) 2023-09-07 03:15:04 -07:00
Masterain
a8bf268fce New translations SH.resx (Chinese Traditional) 2023-09-07 02:19:29 -07:00
Masterain
44d1dd50e6 New translations SH.resx (Korean) 2023-09-07 02:19:27 -07:00
Masterain
14e98a43f6 New translations SH.resx (Japanese) 2023-09-07 02:19:26 -07:00
Masterain
69239674e6 New translations SH.resx (English) 2023-09-07 02:19:25 -07:00
Lightczx
4e972f35dd add ability to create desktop shortcut 2023-09-07 17:01:41 +08:00
Masterain
7187b913b0 New translations SH.resx (Chinese Traditional) 2023-09-07 00:04:11 -07:00
Masterain
4904ab1335 New translations SH.resx (Korean) 2023-09-07 00:04:09 -07:00
Masterain
d572bc5753 New translations SH.resx (Japanese) 2023-09-07 00:04:08 -07:00
Masterain
950c4568ee New translations SH.resx (English) 2023-09-07 00:04:07 -07:00
Lightczx
a7bf82d7e7 Add ability to switch powershell instance 2023-09-07 14:41:16 +08:00
Lightczx
42035803c1 update metadata models 2023-09-07 13:33:52 +08:00
DismissedLight
3161964bcf fix banner typo 2023-09-05 21:39:07 +08: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
Masterain
4b2d6cc27c New translations SH.resx (Chinese Traditional) 2023-09-04 05:55:59 -07:00
Masterain
44068c4446 New translations SH.resx (Chinese Traditional) 2023-09-04 02:18:58 -07:00
Masterain
21399b3781 New translations SH.resx (Korean) 2023-09-04 02:18:57 -07:00
Masterain
dcea46edf4 New translations SH.resx (Japanese) 2023-09-04 02:18:56 -07:00
Masterain
825cc58955 New translations SH.resx (English) 2023-09-04 02:18:54 -07: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
Masterain
743cb9683b New translations SH.resx (English) 2023-09-02 07:04:24 -07:00
DismissedLight
1594edc16c 1.7.1 hotfix pacakge 2023-09-02 20:42:25 +08:00
Masterain
2420916509 New translations SH.resx (Chinese Traditional) 2023-09-02 05:33:33 -07:00
Masterain
479b4086c2 New translations SH.resx (Korean) 2023-09-02 05:33:31 -07:00
Masterain
0626cc0a49 New translations SH.resx (Japanese) 2023-09-02 05:33:30 -07:00
Masterain
93040e6ff8 New translations SH.resx (English) 2023-09-02 05:33:29 -07: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
844 changed files with 28067 additions and 14807 deletions

View File

@@ -0,0 +1,107 @@
name: BUG Report [English Form]
description: Tell us what issue you get
title: "[ENG][Bug]: Place your Issue Title Here"
labels: ["BUG"]
body:
- type: markdown
attributes:
value: |
> **Please use one sentence to briefly describe your issue as title above**
> Please follow the instruction below to fill the form, so we can locate the issue quickly
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: |-
Please complete the checklist, otherwise your issue may be ignored
options:
- label: I have read [FAQ page](https://hut.ao/advanced/FAQ.html) and [Exception page](https://hut.ao/advanced/exceptions.html) in Snap Hutao document, and my issue is not answered
required: true
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
required: true
- label: My issue is not a [fixed issue](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E4%BF%AE%E5%A4%8D), and it's not a duplicated issue
required: true
- type: input
id: winver
attributes:
label: Windows Version
description: |
Use `Win+R` and input `winver`, Windows build version is usually at the second line
placeholder: e.g. 22000.556
validations:
required: true
- type: input
id: shver
attributes:
label: Snap Hutao Version
description: You can find the version in application's title bar
placeholder: e.g. 1.4.15.0
validations:
required: true
- type: input
id: deviceid
attributes:
label: Device ID
description: |
In Snap Hutao's settings page, you can find and copy your device ID
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
validations:
required: false
- type: dropdown
id: user-set-category
attributes:
label: Issue Category
description: Please select the most associated category of your issue
options:
- Installation and Environment
- Achievement
- My Character
- Game Launcher
- Realtime Note
- Develop Plan
- File Cache
- Wish Export
- Game Record
- Hutao Database
- User Interface
- Snap Hutao Cloud
- Snap Hutao Account
- Checkin
- Wiki
- Announcement
- Other
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What Happened?
description: Describe your issue in detail to help us identify the issue. **If your issue is about program crash, you should check Windows Event Viewer, and attach associated `.Net Error` details here**
validations:
required: true
- type: textarea
id: what-expected
attributes:
label: What is expected?
description: Describe expected outcome, highlight the difference with current outcome
validations:
required: false
- type: checkboxes
id: checklist-final
attributes:
label: Last Step
description: Review your Form
options:
- label: I believe the description above is detail enough to allow developers to reproduce the issue
required: true

View File

@@ -1,11 +1,12 @@
name: 问题反馈
description: 告诉我们你的问题
title: "[Bug]: 在这里填写一个合适的标题"
labels: ["BUG"]
body:
- type: markdown
attributes:
value: |
> **请在上方设置一个合适的工单标题**
> **请在上方以一句话简短地概括你的问题作为标题**
> 请按下方的要求填写完整的问题表单,以便我们更快的定位问题。
- type: checkboxes
@@ -15,15 +16,14 @@ body:
description: |-
请确保你已完整执行检查清单,否则你的 Issue 可能会被忽略
options:
- label: 并未完整阅读[胡桃工具箱文档](https://hut.ao/advanced/FAQ.html)
- label: 已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
required: true
- label: 知道文档站的导航栏中有**搜索功能**也没有搜索过相关关键词
- label: 我知道文档站的导航栏中有**搜索功能**且已经搜索过相关关键词
required: true
- label: 使用的操作系统是不[受支持的版本](https://hut.ao/quick-start.html#%E6%9C%80%E4%BD%8E%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82)
- label: 我没有**通过搜索功能**确认其他人提出过相同或类似的问题
- label: 我不明白上述的勾选项是**一个有助于快速排查问题的检查清单**,而是随手确认的选项
- label: 的问题不是[已修复](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E4%BF%AE%E5%A4%8D)的问题也不是一个别人已发布的**重复的**问题
required: true
- type: input
id: winver
@@ -31,7 +31,7 @@ body:
label: Windows 版本
description: |
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
placeholder: 22621.1105
placeholder: 22000.556
validations:
required: true
@@ -65,13 +65,14 @@ body:
- 角色信息面板
- 游戏启动器
- 实时便笺
- 养成计算
- 用户面板
- 养成计算
- 文件缓存
- 祈愿记录
- 玩家查询
- 胡桃数据库
- 用户界面
- 胡桃云
- 胡桃帐号
- 签到
- Wiki
- 公告
@@ -83,7 +84,7 @@ body:
id: what-happened
attributes:
label: 发生了什么?
description: 详细的描述问题发生前后的行为,以便我们解决问题
description: 详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
validations:
required: true
@@ -94,4 +95,13 @@ body:
description: 详细的描述你期望发生的行为,突出与目前(可能不正确的)行为的不同
validations:
required: false
- type: checkboxes
id: checklist-final
attributes:
label: 最后一步
description: 回顾你的回答
options:
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
required: true

View File

@@ -1,5 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: 胡桃工具箱 - 官方文档
- name: Snap Hutao 官方文档 / Snap Hutao Document
url: https://hut.ao
about: 请在提出问题前阅读文档
about: 请在提出问题前阅读文档 / Read the document before submit the issue
- name: 常见问题 / FAQ
url: https://hut.ao/advanced/FAQ.html
about: 常见的用户提出的问题 / Common questions asked by users
- name: 常见程序异常 / Common Program Exceptions
url: https://hut.ao/advanced/exceptions.html
about: 用户通常能自行解决这些问题 / Users may solve these problems by themselves

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:
- "*"

View File

@@ -0,0 +1,19 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2023.2
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}

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 平台下获得接近移动端功能权利的途径
@@ -11,7 +11,7 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
## 贡献 / Contribute
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
* [在 Crowdin 上进行本地化 / Translate project on Crowdin](https://translate.hut.ao/)
* [在 Crowdin 上进行本地化 / Translate Project on Crowdin](https://translate.hut.ao/)
* [为我们更新文档 / Enhance our Document ](https://github.com/DGP-Studio/Snap.Hutao.Docs)
## 特别感谢 / Special Thanks
@@ -19,7 +19,7 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
* [HolographicHat](https://github.com/HolographicHat)
* [UIGF organization](https://uigf.org)
### 特定的原神项目 / Specific Genshin-related Project
### 特定的原神项目 / Specific Genshin-related Projects
* [biuuu/genshin-wish-export](https://github.com/biuuu/genshin-wish-export)
* [xunkong/xunkong](https://github.com/xunkong/xunkong)
@@ -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,7 @@ 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
## 开发 / Development
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
[![Star History Chart](https://api.star-history.com/svg?repos=DGP-Studio/Snap.Hutao&type=Date)](https://star-history.com/#DGP-Studio/Snap.Hutao&Date)

View File

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

30
qodana.yaml Normal file
View File

@@ -0,0 +1,30 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
exclude:
- name: Test
paths:
- Snap.Hutao.Test
- Snap.Hutao.SourceGeneration
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-dotnet:latest

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

@@ -0,0 +1,147 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class AttributeGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(GenerateAllAttributes);
}
public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationContext context)
{
string coreAnnotations = """
using System.Diagnostics;
namespace Snap.Hutao.Core.Annotation;
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
internal sealed class CommandAttribute : Attribute
{
public CommandAttribute(string name)
{
}
public bool AllowConcurrentExecutions { get; set; }
}
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
internal sealed class ConstructorGeneratedAttribute : Attribute
{
public ConstructorGeneratedAttribute()
{
}
public bool CallBaseConstructor { get; set; }
public bool ResolveHttpClient { get; set; }
}
[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;
}
[AttributeUsage(AttributeTargets.All, Inherited = false)]
[Conditional("DEBUG")]
internal sealed class HighQualityAttribute : Attribute
{
}
""";
context.AddSource("Snap.Hutao.Core.Annotation.Attributes.g.cs", coreAnnotations);
string coreDependencyInjectionAnnotationHttpClients = """
namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
internal sealed class HttpClientAttribute : Attribute
{
public HttpClientAttribute(HttpClientConfiguration configuration)
{
}
public HttpClientAttribute(HttpClientConfiguration configuration, Type interfaceType)
{
}
}
internal enum HttpClientConfiguration
{
/// <summary>
/// 默认配置
/// </summary>
Default,
/// <summary>
/// 米游社请求配置
/// </summary>
XRpc,
/// <summary>
/// 米游社登录请求配置
/// </summary>
XRpc2,
/// <summary>
/// Hoyolab app
/// </summary>
XRpc3,
}
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
{
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.MaxConnectionsPerServer"/>
public int MaxConnectionsPerServer { get; set; }
/// <summary>
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.UseCookies"/>
/// </summary>
public bool UseCookies { get; set; }
}
""";
context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.Attributes.g.cs", coreDependencyInjectionAnnotationHttpClients);
string coreDependencyInjectionAnnotations = """
namespace Snap.Hutao.Core.DependencyInjection.Annotation;
internal enum InjectAs
{
Singleton,
Transient,
Scoped,
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
internal sealed class InjectionAttribute : Attribute
{
public InjectionAttribute(InjectAs injectAs)
{
}
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
{
}
}
""";
context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations);
}
}

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;

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

@@ -0,0 +1,61 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using System.Net.Http;
using System.Runtime.Serialization;
namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class SaltConstantGenerator : IIncrementalGenerator
{
private static readonly HttpClient httpClient = new();
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(GenerateSaltContstants);
}
private static void GenerateSaltContstants(IncrementalGeneratorPostInitializationContext context)
{
string body = httpClient.GetStringAsync("https://internal.snapgenshin.cn/Archive/Salt/Latest").GetAwaiter().GetResult();
Response<SaltLatest> saltInfo = JsonParser.FromJson<Response<SaltLatest>>(body)!;
string code = $$"""
namespace Snap.Hutao.Web.Hoyolab;
internal sealed class SaltConstants
{
public const string CNVersion = "{{saltInfo.Data.CNVersion}}";
public const string CNK2 = "{{saltInfo.Data.CNK2}}";
public const string CNLK2 = "{{saltInfo.Data.CNLK2}}";
public const string OSVersion = "{{saltInfo.Data.OSVersion}}";
public const string OSK2 = "{{saltInfo.Data.OSK2}}";
public const string OSLK2 = "{{saltInfo.Data.OSLK2}}";
}
""";
context.AddSource("SaltConstants.g.cs", code);
}
private sealed class Response<T>
{
[DataMember(Name = "data")]
public T Data { get; set; } = default!;
}
internal sealed class SaltLatest
{
public string CNVersion { get; set; } = default!;
public string CNK2 { get; set; } = default!;
public string CNLK2 { get; set; } = default!;
public string OSVersion { get; set; } = default!;
public string OSK2 { get; set; } = default!;
public string OSLK2 { get; set; } = default!;
}
}

View File

@@ -8,7 +8,10 @@ internal sealed class NotNullWhenAttribute : Attribute
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
public NotNullWhenAttribute(bool returnValue)
{
ReturnValue = returnValue;
}
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }

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

@@ -89,6 +89,12 @@ internal sealed class IdentityGenerator : IIncrementalGenerator
{
return Value.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
return Value.ToString();
}
}
""");

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:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
xmlns:cwconv="using:CommunityToolkit.WinUI.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>
@@ -11,16 +13,19 @@
<ResourceDictionary.MergedDictionaries>
<muxc:XamlControlsResources/>
<ResourceDictionary Source="Control/Theme/FontStyle.xaml"/>
<ResourceDictionary Source="Control/Loading.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Color x:Key="AvatarPropertyAddValueColor">#FF74BF00</Color>
<Color x:Key="CompatBackgroundColor">#FFF4F4F4</Color>
<SolidColorBrush x:Key="DarkOnlyOverlayMaskBrush" Color="#00000000"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="AvatarPropertyAddValueColor">#FF90E800</Color>
<Color x:Key="CompatBackgroundColor">#FF242424</Color>
<SolidColorBrush x:Key="DarkOnlyOverlayMaskBrush" Color="#60000000"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- Modify Window title bar color -->
@@ -42,16 +47,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 +79,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 +93,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,10 +109,20 @@
<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"/>
<cwuc:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
<cwconv:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<cwconv:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
@@ -110,8 +148,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 +172,99 @@
<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
x:Name="NoneSelectionListViewItemStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,4,0,0"/>
</Style>
<Style
x:Name="NoneSelectionGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
TargetType="GridViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,0,2,4"/>
</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="256"/>
<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 +273,34 @@
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
<ItemsStackPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapPanelSpacing0Template">
<cwcont:WrapPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapPanelSpacing4Template">
<cwcont:WrapPanel HorizontalSpacing="4" VerticalSpacing="4"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="UniformGridColumns5Spacing4Template">
<cwcont:UniformGrid
ColumnSpacing="4"
Columns="5"
RowSpacing="4"/>
</ItemsPanelTemplate>
<!-- Transitions -->
<TransitionCollection x:Key="ReorderThemeTransitions">
<ReorderThemeTransition/>
</TransitionCollection>
<TransitionCollection x:Key="ContentThemeTransitions">
<ContentThemeTransition/>
</TransitionCollection>
<TransitionCollection x:Key="ListViewLikeThemeTransitions">
<AddDeleteThemeTransition/>
<ContentThemeTransition/>
<ReorderThemeTransition/>
<EntranceThemeTransition IsStaggeringEnabled="False"/>
</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,36 +10,40 @@ 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));
private bool executed;
/// <summary>
/// 待执行的命令
/// </summary>
public ICommand Command
protected override void OnAttached()
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
base.OnAttached();
/// <summary>
/// 命令参数
/// </summary>
[MaybeNull]
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
// FrameworkElement in a ItemsRepeater gets attached twice
if (AssociatedObject is FrameworkElement { IsLoaded: true })
{
TryExecuteCommand();
}
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
if (Command != null && Command.CanExecute(CommandParameter))
TryExecuteCommand();
}
private void TryExecuteCommand()
{
if (executed)
{
return;
}
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
executed = true;
}
}
}

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)
{
FrameworkElement element = (FrameworkElement)dp;
element.Width = (double)e.NewValue;
element.Height = (double)e.NewValue;
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("IsTextSelectionEnabled", typeof(bool), false, IsAttached = true, AttachedType = typeof(InfoBar))]
public sealed partial class InfoBarHelper
{
}

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

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Core.Caching;
@@ -13,7 +12,7 @@ namespace Snap.Hutao.Control.Image;
/// 缓存图像
/// </summary>
[HighQuality]
internal sealed class CachedImage : ImageEx
internal sealed class CachedImage : Implementation.ImageEx
{
/// <summary>
/// 构造一个新的缓存图像
@@ -32,16 +31,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 +47,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

@@ -0,0 +1,44 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Windows.Media.Casting;
namespace Snap.Hutao.Control.Image.Implementation;
internal class ImageEx : ImageExBase
{
private static readonly DependencyProperty NineGridProperty = DependencyProperty.Register(nameof(NineGrid), typeof(Thickness), typeof(ImageEx), new PropertyMetadata(default(Thickness)));
public ImageEx()
: base()
{
}
public Thickness NineGrid
{
get => (Thickness)GetValue(NineGridProperty);
set => SetValue(NineGridProperty, value);
}
public override CompositionBrush GetAlphaMask()
{
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
{
return image.GetAlphaMask();
}
return default!;
}
public CastingSource GetAsCastingSource()
{
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
{
return image.GetAsCastingSource();
}
return default!;
}
}

View File

@@ -0,0 +1,468 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System.IO;
using Windows.Foundation;
namespace Snap.Hutao.Control.Image.Implementation;
internal delegate void ImageExFailedEventHandler(object sender, ImageExFailedEventArgs e);
internal delegate void ImageExOpenedEventHandler(object sender, ImageExOpenedEventArgs e);
[SuppressMessage("", "CA1001")]
[SuppressMessage("", "SH003")]
[TemplateVisualState(Name = LoadingState, GroupName = CommonGroup)]
[TemplateVisualState(Name = LoadedState, GroupName = CommonGroup)]
[TemplateVisualState(Name = UnloadedState, GroupName = CommonGroup)]
[TemplateVisualState(Name = FailedState, GroupName = CommonGroup)]
[TemplatePart(Name = PartImage, Type = typeof(object))]
internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlphaMaskProvider
{
protected const string PartImage = "Image";
protected const string CommonGroup = "CommonStates";
protected const string LoadingState = "Loading";
protected const string LoadedState = "Loaded";
protected const string UnloadedState = "Unloaded";
protected const string FailedState = "Failed";
private static readonly DependencyProperty StretchProperty = DependencyProperty.Register(nameof(Stretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(Stretch.Uniform));
private static readonly DependencyProperty DecodePixelHeightProperty = DependencyProperty.Register(nameof(DecodePixelHeight), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
private static readonly DependencyProperty DecodePixelTypeProperty = DependencyProperty.Register(nameof(DecodePixelType), typeof(int), typeof(ImageExBase), new PropertyMetadata(DecodePixelType.Physical));
private static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register(nameof(DecodePixelWidth), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
private static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
private static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false, EnableLazyLoadingChanged));
private static readonly DependencyProperty LazyLoadingThresholdProperty = DependencyProperty.Register(nameof(LazyLoadingThreshold), typeof(double), typeof(ImageExBase), new PropertyMetadata(default(double), LazyLoadingThresholdChanged));
private static readonly DependencyProperty PlaceholderSourceProperty = DependencyProperty.Register(nameof(PlaceholderSource), typeof(ImageSource), typeof(ImageExBase), new PropertyMetadata(default(ImageSource), PlaceholderSourceChanged));
private static readonly DependencyProperty PlaceholderStretchProperty = DependencyProperty.Register(nameof(PlaceholderStretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(default(Stretch)));
private static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));
private CancellationTokenSource? tokenSource;
private object? lazyLoadingSource;
private bool isInViewport;
public event ImageExFailedEventHandler? ImageExFailed;
public event ImageExOpenedEventHandler? ImageExOpened;
public event EventHandler? ImageExInitialized;
public bool IsInitialized { get; private set; }
public int DecodePixelHeight
{
get => (int)GetValue(DecodePixelHeightProperty);
set => SetValue(DecodePixelHeightProperty, value);
}
public DecodePixelType DecodePixelType
{
get => (DecodePixelType)GetValue(DecodePixelTypeProperty);
set => SetValue(DecodePixelTypeProperty, value);
}
public int DecodePixelWidth
{
get => (int)GetValue(DecodePixelWidthProperty);
set => SetValue(DecodePixelWidthProperty, value);
}
public Stretch Stretch
{
get => (Stretch)GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
public bool IsCacheEnabled
{
get => (bool)GetValue(IsCacheEnabledProperty);
set => SetValue(IsCacheEnabledProperty, value);
}
public bool EnableLazyLoading
{
get => (bool)GetValue(EnableLazyLoadingProperty);
set => SetValue(EnableLazyLoadingProperty, value);
}
public double LazyLoadingThreshold
{
get => (double)GetValue(LazyLoadingThresholdProperty);
set => SetValue(LazyLoadingThresholdProperty, value);
}
public ImageSource PlaceholderSource
{
get => (ImageSource)GetValue(PlaceholderSourceProperty);
set => SetValue(PlaceholderSourceProperty, value);
}
public Stretch PlaceholderStretch
{
get => (Stretch)GetValue(PlaceholderStretchProperty);
set => SetValue(PlaceholderStretchProperty, value);
}
public object Source
{
get => GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
public bool WaitUntilLoaded
{
get => true;
}
protected object? Image { get; private set; }
public abstract CompositionBrush GetAlphaMask();
protected virtual void OnPlaceholderSourceChanged(DependencyPropertyChangedEventArgs e)
{
}
protected virtual Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{
// By default we just use the built-in UWP image cache provided within the Image control.
return Task.FromResult<ImageSource?>(new BitmapImage(imageUri));
}
protected virtual void OnImageOpened(object sender, RoutedEventArgs e)
{
VisualStateManager.GoToState(this, LoadedState, true);
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
}
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
{
VisualStateManager.GoToState(this, FailedState, true);
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new FileNotFoundException(e.ErrorMessage)));
}
protected void AttachImageOpened(RoutedEventHandler handler)
{
if (Image is Microsoft.UI.Xaml.Controls.Image image)
{
image.ImageOpened += handler;
}
else if (Image is ImageBrush brush)
{
brush.ImageOpened += handler;
}
}
protected void RemoveImageOpened(RoutedEventHandler handler)
{
if (Image is Microsoft.UI.Xaml.Controls.Image image)
{
image.ImageOpened -= handler;
}
else if (Image is ImageBrush brush)
{
brush.ImageOpened -= handler;
}
}
protected void AttachImageFailed(ExceptionRoutedEventHandler handler)
{
if (Image is Microsoft.UI.Xaml.Controls.Image image)
{
image.ImageFailed += handler;
}
else if (Image is ImageBrush brush)
{
brush.ImageFailed += handler;
}
}
protected void RemoveImageFailed(ExceptionRoutedEventHandler handler)
{
if (Image is Microsoft.UI.Xaml.Controls.Image image)
{
image.ImageFailed -= handler;
}
else if (Image is ImageBrush brush)
{
brush.ImageFailed -= handler;
}
}
protected override void OnApplyTemplate()
{
RemoveImageOpened(OnImageOpened);
RemoveImageFailed(OnImageFailed);
Image = GetTemplateChild(PartImage);
IsInitialized = true;
ImageExInitialized?.Invoke(this, EventArgs.Empty);
if (Source is null || !EnableLazyLoading || isInViewport)
{
lazyLoadingSource = null;
SetSource(Source);
}
else
{
lazyLoadingSource = Source;
}
AttachImageOpened(OnImageOpened);
AttachImageFailed(OnImageFailed);
base.OnApplyTemplate();
}
private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control)
{
bool value = (bool)e.NewValue;
if (value)
{
control.LayoutUpdated += control.ImageExBase_LayoutUpdated;
control.InvalidateLazyLoading();
}
else
{
control.LayoutUpdated -= control.ImageExBase_LayoutUpdated;
}
}
}
private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control && control.EnableLazyLoading)
{
control.InvalidateLazyLoading();
}
}
private static void PlaceholderSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control)
{
control.OnPlaceholderSourceChanged(e);
}
}
private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not ImageExBase control)
{
return;
}
if (e.OldValue is null || e.NewValue is null || !e.OldValue.Equals(e.NewValue))
{
if (e.NewValue is null || !control.EnableLazyLoading || control.isInViewport)
{
control.lazyLoadingSource = null;
control.SetSource(e.NewValue);
}
else
{
control.lazyLoadingSource = e.NewValue;
}
}
}
private static bool IsHttpUri(Uri uri)
{
return uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https");
}
private void AttachSource(ImageSource? source)
{
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
// We only need to call those methods if we fail in other cases before we get here.
if (Image is Microsoft.UI.Xaml.Controls.Image image)
{
image.Source = source;
}
else if (Image is ImageBrush brush)
{
brush.ImageSource = source;
}
if (source is null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
}
else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
{
VisualStateManager.GoToState(this, LoadedState, true);
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
}
}
[SuppressMessage("", "IDE0019")]
private async void SetSource(object? source)
{
if (!IsInitialized)
{
return;
}
tokenSource?.Cancel();
tokenSource = new CancellationTokenSource();
AttachSource(null);
if (source is null)
{
return;
}
VisualStateManager.GoToState(this, LoadingState, true);
ImageSource? imageSource = source as ImageSource;
if (imageSource is not null)
{
AttachSource(imageSource);
return;
}
Uri? uri = source as Uri;
if (uri is null)
{
string? url = source as string ?? source.ToString();
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
{
VisualStateManager.GoToState(this, FailedState, true);
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
return;
}
}
if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
{
uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
}
try
{
await LoadImageAsync(uri, tokenSource.Token).ConfigureAwait(true);
}
catch (OperationCanceledException)
{
// nothing to do as cancellation has been requested.
}
catch (Exception e)
{
VisualStateManager.GoToState(this, FailedState, true);
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
}
}
private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
{
if (imageUri is not null)
{
if (IsCacheEnabled)
{
ImageSource? img = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true);
ArgumentNullException.ThrowIfNull(tokenSource);
if (!tokenSource.IsCancellationRequested)
{
// Only attach our image if we still have a valid request.
AttachSource(img);
}
}
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
{
string source = imageUri.OriginalString;
const string base64Head = "base64,";
int index = source.IndexOf(base64Head, StringComparison.Ordinal);
if (index >= 0)
{
byte[] bytes = Convert.FromBase64String(source[(index + base64Head.Length)..]);
BitmapImage bitmap = new();
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
ArgumentNullException.ThrowIfNull(tokenSource);
if (!tokenSource.IsCancellationRequested)
{
AttachSource(bitmap);
}
}
}
else
{
AttachSource(new BitmapImage(imageUri)
{
CreateOptions = BitmapCreateOptions.IgnoreImageCache,
});
}
}
}
private void ImageExBase_LayoutUpdated(object? sender, object e)
{
InvalidateLazyLoading();
}
private void InvalidateLazyLoading()
{
if (!IsLoaded)
{
isInViewport = false;
return;
}
// Find the first ascendant ScrollViewer, if not found, use the root element.
FrameworkElement? hostElement = default;
IEnumerable<FrameworkElement> ascendants = this.FindAscendants().OfType<FrameworkElement>();
foreach (FrameworkElement ascendant in ascendants)
{
hostElement = ascendant;
if (hostElement is Microsoft.UI.Xaml.Controls.ScrollViewer)
{
break;
}
}
if (hostElement is null)
{
isInViewport = false;
return;
}
Rect controlRect = TransformToVisual(hostElement)
.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
double lazyLoadingThreshold = LazyLoadingThreshold;
Rect hostRect = new(
0 - lazyLoadingThreshold,
0 - lazyLoadingThreshold,
hostElement.ActualWidth + (2 * lazyLoadingThreshold),
hostElement.ActualHeight + (2 * lazyLoadingThreshold));
if (controlRect.IntersectsWith(hostRect))
{
isInViewport = true;
if (lazyLoadingSource is not null)
{
object source = lazyLoadingSource;
lazyLoadingSource = null;
SetSource(source);
}
}
else
{
isInViewport = false;
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Image.Implementation;
internal sealed class ImageExFailedEventArgs : EventArgs
{
public ImageExFailedEventArgs(Exception errorException)
{
ErrorMessage = ErrorException?.Message;
ErrorException = errorException;
}
public Exception? ErrorException { get; private set; }
public string? ErrorMessage { get; private set; }
}

View File

@@ -0,0 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Image.Implementation;
internal sealed class ImageExOpenedEventArgs : EventArgs
{
}

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

@@ -0,0 +1,48 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control;
[TemplateVisualState(Name = "LoadingIn", GroupName = "CommonStates")]
[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
{
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
[SuppressMessage("", "IDE0052")]
private FrameworkElement? presenter;
public Loading()
{
DefaultStyleKey = typeof(Loading);
DefaultStyleResourceUri = new("ms-appx:///Control/Loading.xaml");
}
public bool IsLoading
{
get => (bool)GetValue(IsLoadingProperty);
set => SetValue(IsLoadingProperty, value);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
Update();
}
private static void IsLoadingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Loading control = (Loading)d;
control.presenter ??= control.GetTemplateChild("ContentGrid") as FrameworkElement;
control?.Update();
}
private void Update()
{
VisualStateManager.GoToState(this, IsLoading ? "LoadingIn" : "LoadingOut", true);
}
}

View File

@@ -0,0 +1,85 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shc="using:Snap.Hutao.Control">
<Style TargetType="shc:Loading">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="shc:Loading">
<Border
x:Name="RootGrid"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Opacity="0"
Visibility="Collapsed">
<ContentPresenter
x:Name="ContentGrid"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter.RenderTransform>
<CompositeTransform/>
</ContentPresenter.RenderTransform>
</ContentPresenter>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="LoadingIn">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<QuadraticEase EasingMode="EaseInOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<QuadraticEase EasingMode="EaseInOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="LoadingOut">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<QuadraticEase EasingMode="EaseInOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<QuadraticEase EasingMode="EaseInOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

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,17 @@
// 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()
{
return XamlBindingHelper.ConvertValue(typeof(int), Value);
}
}

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

@@ -38,7 +38,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,8 +38,8 @@ internal struct Rgba32
/// 构造一个新的 RGBA8 颜色
/// </summary>
/// <param name="hex">色值字符串</param>
public unsafe Rgba32(string hex)
: this(Convert.ToUInt32(hex, 16))
public Rgba32(string hex)
: this(hex.Length == 6 ? Convert.ToUInt32($"{hex}FF", 16) : 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,84 @@ 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))]
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
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,
Style = TextStyle,
};
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 OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
text.Inlines.Clear();
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
textBlock.Style = (Style)e.NewValue;
}
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
{
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);
i += 1;
AppendText(textBlock, description[last..i]);
AppendLineBreak(textBlock);
i += 2;
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 +108,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 +127,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

@@ -0,0 +1,182 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Text;
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;
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
internal sealed partial class HtmlDescriptionTextBlock : ContentControl
{
private static readonly int ColorTagFullLength = "<color style='color:#FFFFFF;'></color>".Length;
private static readonly int ColorTagLeftLength = "<color style='color:#FFFFFF;'>".Length;
private static readonly int ItalicTagFullLength = "<i></i>".Length;
private static readonly int ItalicTagLeftLength = "<i>".Length;
private static readonly int BoldTagFullLength = "<b></b>".Length;
private static readonly int BoldTagLeftLength = "<b>".Length;
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
/// <summary>
/// 构造一个新的呈现描述文本的文本块
/// </summary>
public HtmlDescriptionTextBlock()
{
this.DisableInteraction();
Content = new TextBlock()
{
TextWrapping = TextWrapping.Wrap,
Style = TextStyle,
};
actualThemeChangedEventHandler = OnActualThemeChanged;
ActualThemeChanged += actualThemeChangedEventHandler;
}
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = (TextBlock)((HtmlDescriptionTextBlock)d).Content;
ReadOnlySpan<char> description = (string)e.NewValue;
UpdateDescription(textBlock, description);
}
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = (TextBlock)((HtmlDescriptionTextBlock)d).Content;
textBlock.Style = (Style)e.NewValue;
}
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
{
textBlock.Inlines.Clear();
int last = 0;
for (int i = 0; i < description.Length;)
{
// newline
if (description[i..].StartsWith(@"<br>"))
{
AppendText(textBlock, description[last..i]);
AppendLineBreak(textBlock);
i += 4;
last = i;
}
// color tag
else if (description[i..].StartsWith("<c"))
{
AppendText(textBlock, description[last..i]);
string a = description.Slice(i + 21, 6).ToString();
Rgba32 color = new(a);
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
i += length + ColorTagFullLength;
last = i;
}
// italic
else if (description[i..].StartsWith("<i"))
{
AppendText(textBlock, description[last..i]);
int length = description[(i + ItalicTagLeftLength)..].IndexOf('<');
AppendItalicText(textBlock, description.Slice(i + ItalicTagLeftLength, length));
i += length + ItalicTagFullLength;
last = i;
}
// bold
else if (description[i..].StartsWith("<b"))
{
AppendText(textBlock, description[last..i]);
int length = description[(i + BoldTagLeftLength)..].IndexOf('<');
AppendBoldText(textBlock, description.Slice(i + BoldTagLeftLength, length));
i += length + BoldTagFullLength;
last = i;
}
else
{
i += 1;
}
if (i == description.Length - 1)
{
AppendText(textBlock, description[last..(i + 1)]);
}
}
}
private static void AppendText(TextBlock text, in ReadOnlySpan<char> slice)
{
text.Inlines.Add(new Run { Text = slice.ToString() });
}
private static void AppendColorText(TextBlock text, in ReadOnlySpan<char> slice, Rgba32 color)
{
Color targetColor;
if (ThemeHelper.IsDarkMode(text.ActualTheme))
{
targetColor = color;
}
else
{
// Make lighter in light mode
Hsl32 hsl = color.ToHsl();
hsl.L *= 0.3;
targetColor = Rgba32.FromHsl(hsl);
}
text.Inlines.Add(new Run
{
Text = slice.ToString(),
Foreground = new SolidColorBrush(targetColor),
});
}
private static void AppendBoldText(TextBlock text, in ReadOnlySpan<char> slice)
{
text.Inlines.Add(new Run
{
Text = slice.ToString(),
FontWeight = FontWeights.Bold,
});
}
private static void AppendItalicText(TextBlock text, in ReadOnlySpan<char> slice)
{
text.Inlines.Add(new Run
{
Text = slice.ToString(),
FontStyle = Windows.UI.Text.FontStyle.Italic,
});
}
private static void AppendLineBreak(TextBlock text)
{
text.Inlines.Add(new LineBreak());
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
// Simply re-apply texts
UpdateDescription((TextBlock)Content, Description);
}
}

View File

@@ -1,4 +1,7 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shch="using:Snap.Hutao.Control.Helper">
<FontFamily x:Key="MiSans">ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
<FontFamily x:Key="CascadiaMonoAndMiSans">ms-appx:///Resource/Font/CascadiaMono.ttf#Cascadia Mono, ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
@@ -97,8 +100,276 @@
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<!-- TODO: When will DefaultInfoBarStyle added -->
<Style TargetType="InfoBar">
<Setter Property="shch:InfoBarHelper.IsTextSelectionEnabled" Value="False"/>
<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}"
IsTextSelectionEnabled="{TemplateBinding shch:InfoBarHelper.IsTextSelectionEnabled}"
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}"
IsTextSelectionEnabled="{TemplateBinding shch:InfoBarHelper.IsTextSelectionEnabled}"
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

@@ -1,24 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Annotation;
/// <summary>
/// 指示此方法为命令的调用方法
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class CommandAttribute : Attribute
{
/// <summary>
/// 指示此方法为命令的调用方法
/// </summary>
/// <param name="name">命令名称</param>
public CommandAttribute(string name)
{
}
/// <summary>
/// 是否允许并行执行
/// </summary>
public bool AllowConcurrentExecutions { get; set; }
}

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