Compare commits

..

535 Commits
1.7.5 ... 1.9.1

Author SHA1 Message Date
DismissedLight
b11526761e Merge pull request #1214 from DGP-Studio/develop 2023-12-23 19:19:45 +08:00
DismissedLight
d293149672 1.9.1 package 2023-12-23 19:18:29 +08:00
DismissedLight
3784df67a3 adjust launch page ui 2023-12-23 19:15:04 +08:00
DismissedLight
4aaca4d19f fix reentrant issue 2023-12-23 18:51:41 +08:00
DismissedLight
e6cf39831d fix daily note fetch uid crash 2023-12-23 18:22:12 +08:00
DismissedLight
24a2a18760 fix #1212 2023-12-23 17:34:44 +08:00
DismissedLight
d8dce5c062 empty sha256 tolerance 2023-12-23 14:48:24 +08:00
DismissedLight
6ee823094a Merge pull request #1207 from DGP-Studio/develop 2023-12-23 11:51:15 +08:00
Masterain
ccbb7f76d4 New Crowdin updates (#1205) 2023-12-23 11:48:39 +08:00
DismissedLight
857eea61f9 remove store buttons in setting page 2023-12-23 11:47:15 +08:00
DismissedLight
d82f416c10 code style 2023-12-22 22:03:37 +08:00
DismissedLight
b8bcad2107 1.9.0 package 2023-12-22 22:02:10 +08:00
Masterain
ad240a543d New Crowdin updates (#1189)
* New translations sh.resx (Indonesian)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (English)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)
2023-12-22 02:21:02 -08:00
Masterain
e7775b611f Update TestViewModel.cs 2023-12-22 01:12:45 -08:00
qhy040404
14894b0b47 Update issue templates 2023-12-22 16:59:22 +08:00
Masterain
53d920621c Update TestViewModel.cs 2023-12-22 00:50:00 -08:00
Lightczx
55cb346fb4 update service 2023-12-22 16:29:36 +08:00
Masterain
c0f63187cc Update TestViewModel.cs 2023-12-21 23:28:46 -08:00
Masterain
6834073603 Merge pull request #1204 from DGP-Studio/issue_template_publish
Update MGMT-publish.yml
2023-12-21 17:41:22 -08:00
qhy040404
911fe57fb2 check jihulab 2023-12-22 09:29:47 +08:00
qhy040404
7320cf7dd0 owner 2023-12-22 09:21:52 +08:00
qhy040404
bc6d03e442 Update MGMT-publish.yml 2023-12-22 09:20:30 +08:00
DismissedLight
884ec87edf disable quick edit for debug console 2023-12-21 20:16:00 +08:00
Lightczx
18d3180bc2 more announcement time fix 2023-12-21 15:57:09 +08:00
Lightczx
4908364e45 announcement time as local 2023-12-21 15:27:38 +08:00
DismissedLight
b7fe16c52c Merge pull request #1200 from DGP-Studio/1198 2023-12-21 15:22:30 +08:00
Lightczx
0c8646b499 fix announcement time 2023-12-21 15:20:22 +08:00
qhy040404
f5b0d07d32 impl #1198 2023-12-21 10:06:30 +08:00
qhy040404
231635ac89 fix wrong publisher 2023-12-21 09:42:18 +08:00
qhy040404
e0a28d0f90 Update CI Certificate 2023-12-21 09:34:38 +08:00
Lightczx
22e7942899 doc 2023-12-20 17:01:45 +08:00
Lightczx
d81e7f6624 fix announcement time incorrect for oversea 2023-12-20 16:57:07 +08:00
DismissedLight
92240a27a0 Merge pull request #1192 from DGP-Studio/feat/ann 2023-12-20 16:30:37 +08:00
Lightczx
c5313c078d code style 2023-12-20 16:29:00 +08:00
qhy040404
2c320fe7e6 revert some region 2023-12-20 15:49:18 +08:00
qhy040404
05a8ab990c replace all region 2023-12-20 15:27:59 +08:00
qhy040404
3661822852 use NameValue 2023-12-20 15:27:59 +08:00
qhy040404
7519d7b263 typo 2023-12-20 15:27:59 +08:00
qhy040404
47d0cbcf31 override ToString 2023-12-20 15:27:59 +08:00
qhy040404
449a5393a9 fix typo 2023-12-20 15:27:59 +08:00
qhy040404
3b636ecd27 Update SettingPage.xaml 2023-12-20 15:27:59 +08:00
qhy040404
95531db559 use struct 2023-12-20 15:27:59 +08:00
qhy040404
eeed58ed71 maybe code style 2023-12-20 15:27:58 +08:00
qhy040404
493af0fd4c impl #1112 (part 3)
ann client
2023-12-20 15:27:58 +08:00
qhy040404
3df70a5feb impl #1112 (part 2)
setting
2023-12-20 15:27:58 +08:00
qhy040404
879b930ea6 impl #1112 (part 1) 2023-12-20 15:27:58 +08:00
Lightczx
c5e0221a0b fix jsbridge 2023-12-20 15:26:08 +08:00
Lightczx
44fbb56d83 minor code style 2023-12-20 13:07:06 +08:00
Lightczx
1a1bdb7f85 #1190 cast data type nuint attempt 2023-12-20 12:43:08 +08:00
Lightczx
52cd505ed0 #1190 cast data type 2023-12-20 11:01:01 +08:00
Lightczx
cd16bebee2 fix #1190 2023-12-20 10:39:56 +08:00
Masterain
307a49b346 Merge pull request #1191 from DGP-Studio/Masterain98-patch-2
Update .gitlab-ci.yml
2023-12-19 18:08:36 -08:00
Masterain
9f8d80ff43 Update .gitlab-ci.yml 2023-12-19 13:48:49 -08:00
DismissedLight
2be2d6313b wiki avatar skill 2023-12-19 20:37:27 +08:00
Lightczx
bee7e48cb9 fix gamePath set null when closing page 2 2023-12-19 11:49:19 +08:00
DismissedLight
83cbc9bbe1 fix gamePath set null when closing page 2023-12-18 22:44:31 +08:00
DismissedLight
655d8a74af remove box 2023-12-17 19:30:49 +08:00
Masterain
d34130b6c0 Update .gitlab-ci.yml 2023-12-17 02:44:15 -08:00
DismissedLight
4cf76ebbc4 fix type issue 2023-12-17 16:50:53 +08:00
DismissedLight
10b282a88a unify response behavior 2023-12-17 16:48:16 +08:00
Masterain
2161f12069 Merge pull request #1187 from DGP-Studio/Masterain98-patch-1
Create .gitlab-ci.yml
2023-12-16 22:34:56 -08:00
Masterain
0bedd1894c Create .gitlab-ci.yml 2023-12-16 22:33:43 -08:00
DismissedLight
e60956c5c8 temp fix #1160 2023-12-16 19:17:09 +08:00
DismissedLight
442db0bae4 Merge pull request #1184 from DGP-Studio/develop 2023-12-16 15:07:24 +08:00
DismissedLight
aa4b544500 1.8.5 package 2023-12-16 15:06:25 +08:00
DismissedLight
3fc35cc3a5 Merge pull request #1183 from DGP-Studio/develop 2023-12-16 15:02:03 +08:00
Masterain
3233be6f25 New Crowdin updates (#1157) 2023-12-16 15:01:13 +08:00
DismissedLight
03f6778ec3 Merge pull request #1182 from DGP-Studio/develop 2023-12-16 15:00:09 +08:00
DismissedLight
0310afd77d correct game record requests 2023-12-15 21:34:04 +08:00
DismissedLight
e94f68d87b add console banner 2023-12-15 20:58:30 +08:00
DismissedLight
73dc103d11 Merge pull request #1179 from qhy040404/fix/pwsh 2023-12-15 19:27:59 +08:00
qhy040404
c947c759b8 fix pwsh argument 2023-12-15 19:26:17 +08:00
DismissedLight
4581bd79f9 fix gamepath reselect issue 2023-12-15 18:58:41 +08:00
Masterain
1b4fd995ce Merge pull request #1178 from DGP-Studio/Masterain98-patch-2
Update README.md
2023-12-15 01:54:21 -08:00
DismissedLight
72ebd1067b attempt to fix code 5001 2023-12-15 17:33:06 +08:00
Lightczx
e66819de55 fix #1060 2023-12-15 11:59:40 +08:00
Masterain
4d3bd6f438 Update README.md 2023-12-14 15:35:12 -08:00
DismissedLight
9f793670fe failed attempt: fight with device_fp 2023-12-14 22:47:17 +08:00
DismissedLight
414e0715a5 Merge pull request #1175 from DGP-Studio/feature/multi-gamepath 2023-12-14 15:25:03 +08:00
Lightczx
c8bea36540 code style 2023-12-14 15:22:20 +08:00
Lightczx
9e5b5e24d9 impl #1173 2023-12-14 15:15:29 +08:00
qhy040404
2968017663 Merge pull request #1176 from DGP-Studio/main
Sync action to develop
2023-12-14 15:06:55 +08:00
Lightczx
ac78df369c impl #526 2023-12-14 14:48:56 +08:00
qhy040404
2d7b3732e7 Update alpha.yml 2023-12-13 22:29:39 +08:00
DismissedLight
176baeb5c6 shadow improvement 2023-12-13 22:06:43 +08:00
DismissedLight
8fe1b48fd4 fix qrcode dialog 2023-12-13 20:22:29 +08:00
Lightczx
de46d5f9bf Update KnownReturnCode.cs 2023-12-13 17:24:44 +08:00
Lightczx
289b3219c9 fix some image blank 2023-12-13 13:32:42 +08:00
Masterain
af6a1208c6 Merge pull request #1172 from qhy040404/ci/action
Use GitHub Actions to generate Alpha
2023-12-12 19:47:02 -08:00
qhy040404
be6ad70ad6 misc 2023-12-12 22:02:54 +08:00
DismissedLight
d740632c27 code style 2023-12-12 21:40:18 +08:00
qhy040404
fd2e9980c7 fix 2023-12-12 19:24:52 +08:00
qhy040404
0b7b259d2f migrate to GitHub actions 2023-12-12 18:14:23 +08:00
Lightczx
c67dfea819 Update README.md 2023-12-12 17:13:47 +08:00
DismissedLight
b84cd98484 Merge pull request #1170 from DGP-Studio/develop 2023-12-12 17:08:56 +08:00
Lightczx
1c991aa120 user service refactor 2023-12-12 17:07:28 +08:00
Masterain
d92da924ff Update README.md 2023-12-11 23:58:02 -08:00
qhy040404
57f7ac944c fix signing 2023-12-12 15:31:37 +08:00
qhy040404
5ad4c0a5be Update appveyor signing 2023-12-12 15:23:59 +08:00
DismissedLight
6768d7b8f4 Merge pull request #1169 from qhy040404/feat/qr 2023-12-12 14:25:32 +08:00
Lightczx
ad20b83b4e minor fix 2023-12-12 14:25:05 +08:00
Lightczx
f4547b60de completing 2023-12-12 14:22:15 +08:00
qhy040404
dcf1b01566 Update azure-pipelines.yml 2023-12-12 10:42:08 +08:00
DismissedLight
217586fece Device needs rework 2023-12-11 22:55:47 +08:00
qhy040404
2fb6cd3441 code style (?) 2023-12-11 18:47:41 +08:00
qhy040404
a8d4dc84a1 impl #870 2023-12-11 14:31:34 +08:00
Masterain
c39a198c57 Update azure-pipelines.yml for Azure Pipelines 2023-12-10 21:07:17 -08:00
Masterain
9c106b24fb Update azure-pipelines.yml for Azure Pipelines 2023-12-10 21:02:19 -08:00
Masterain
73c62a63ea Update azure-pipelines.yml for Azure Pipelines 2023-12-10 20:44:05 -08:00
Lightczx
e8762d658f add console window 2023-12-11 11:44:03 +08:00
DismissedLight
824fba89a8 minor code style 2023-12-10 21:37:27 +08:00
DismissedLight
ecd17de279 text hint improvement 2023-12-09 18:26:02 +08:00
DismissedLight
46c683c570 Merge pull request #1164 from qhy040404/ci/cake 2023-12-09 15:04:50 +08:00
qhy040404
364d0ed0be Update azure-pipelines.yml 2023-12-09 14:28:43 +08:00
qhy040404
46a90be95c prepare release 2023-12-09 12:06:21 +08:00
qhy040404
d7863ab5e0 code style 2023-12-09 11:12:25 +08:00
qhy040404
e7e6467ea8 release version 2023-12-09 11:09:19 +08:00
qhy040404
5fa6bc03c8 更新 appveyor.yml 2023-12-09 10:17:03 +08:00
qhy040404
4d5115e11b add appveyor 2023-12-09 09:51:30 +08:00
DismissedLight
bc9b167c5b disable image lazy loading 2023-12-08 22:39:51 +08:00
qhy040404
f5c3e55b3e sign outside 2023-12-08 17:44:26 +08:00
qhy040404
abb559d35f prepare for veyor 2023-12-08 17:02:40 +08:00
qhy040404
f4d23d6174 better abstract 2023-12-08 16:39:36 +08:00
Lightczx
3cc17375f0 Settings folder size display 2023-12-08 16:19:38 +08:00
qhy040404
50c0fa2061 abstract 2023-12-08 11:41:13 +08:00
Lightczx
859492e580 infobarservice refactor 2023-12-08 11:16:55 +08:00
qhy040404
1ab1d182af Update PublishDistribution.yml 2023-12-08 10:30:07 +08:00
qhy040404
bde5122060 change target repo and avoid abs path 2023-12-08 10:01:34 +08:00
qhy040404
e090d7e04b wrong repo 2023-12-08 09:50:56 +08:00
qhy040404
7ef2834b42 Hello cake 2023-12-08 09:50:56 +08:00
qhy040404
c68fbe9d96 Auto sync appxmanifest 2023-12-08 09:50:48 +08:00
DismissedLight
f16769969e fix #1163 2023-12-07 23:00:40 +08:00
DismissedLight
24b66de082 code style 2023-12-07 22:55:32 +08:00
DismissedLight
a5bfdbaa4b impl #1016 2023-12-07 22:38:21 +08:00
Lightczx
559ae250bd cultivation wip [skip ci] 2023-12-07 17:25:48 +08:00
Lightczx
bd344e50ab minor game process optimization 2023-12-07 10:57:16 +08:00
Lightczx
e5d67a80dd move files 2023-12-07 10:37:15 +08:00
Lightczx
8d8ec8b05d code style 2023-12-07 09:34:05 +08:00
Lightczx
82ccd59451 sign in website url 2023-12-07 09:16:00 +08:00
Lightczx
3ba3ba55cb adjust propertynames 2023-12-06 17:16:23 +08:00
Lightczx
e6e6e22b9c apply hutao api changes 2023-12-06 16:39:48 +08:00
Lightczx
97842559d7 apply api changes 2023-12-06 15:45:30 +08:00
Lightczx
a97aa26d79 refactor options 2023-12-06 15:41:13 +08:00
DismissedLight
8d7373c6cb Merge pull request #1161 from qhy040404/feat/ip 2023-12-06 13:53:34 +08:00
Lightczx
045c127fb2 code style 2023-12-06 13:53:16 +08:00
qhy040404
4dd6765e35 show ip 2023-12-06 12:47:30 +08:00
Masterain
d374519685 Update issue templates 2023-12-05 01:56:35 -08:00
Lightczx
9993082b86 1.8.4 package 2023-12-05 14:30:04 +08:00
Lightczx
f835178b10 always extract sdk for bili 2023-12-05 14:09:41 +08:00
DismissedLight
0b8b10e2f7 Merge pull request #1156 from DGP-Studio/develop 2023-12-05 13:52:10 +08:00
Masterain
97130156f0 New Crowdin updates (#1142) 2023-12-05 13:38:33 +08:00
Lightczx
06def00e2c ignore non exist files 2023-12-05 13:33:25 +08:00
Lightczx
2679a68785 pre-check filesystem permission before convert 2023-12-05 10:58:41 +08:00
DismissedLight
57e8bc8bdf resource file sharding for client converting 2023-12-04 23:14:41 +08:00
DismissedLight
b6ad96c0cb fix stream copy work totalbytes 2023-12-04 21:35:43 +08:00
Lightczx
b6769b63e3 Add progress report for HttpShardCopyWorker 2023-12-04 17:10:42 +08:00
Lightczx
1c67da607c Add http sharding 2023-12-04 16:09:28 +08:00
DismissedLight
70cb4b8285 Merge pull request #1153 from qhy040404/fix/pwsh
fix #1145 (Part 2)
2023-12-03 23:34:21 +08:00
DismissedLight
56fc4dcbcd code style 2023-12-03 23:34:00 +08:00
DismissedLight
626418680a Merge pull request #1154 from qhy040404/fix/link
fix #1151
2023-12-03 23:16:26 +08:00
qhy040404
8f3e166773 fix #1151 2023-12-03 22:26:55 +08:00
qhy040404
013639f57e fix #1145 (Part 2) 2023-12-03 22:02:48 +08:00
DismissedLight
21ad6be9da metadata service refactor 2023-12-03 21:20:49 +08:00
DismissedLight
ed556c8539 remove wiki avatar page resize blinking 2023-12-03 17:37:03 +08:00
DismissedLight
42e11ec94a add tests 2023-12-02 21:22:26 +08:00
qhy040404
6cdfac6e09 fix #1147 2023-12-02 15:51:15 +08:00
Masterain
b0c23e329b Update README.md 2023-12-02 15:51:15 +08:00
DismissedLight
3ca46d3836 Merge pull request #1149 from qhy040404/fix/culture 2023-12-02 15:39:34 +08:00
qhy040404
5df2d7210b fix #1147 2023-12-02 15:26:52 +08:00
Lightczx
7dece546a5 game running tracker async scope 2023-12-01 16:25:21 +08:00
Lightczx
2b851a5459 activation optimization 2023-12-01 14:58:10 +08:00
Lightczx
98a711da70 fix #1145 2023-12-01 10:50:07 +08:00
Lightczx
176c26df51 sign-in endpoints 2023-12-01 10:43:06 +08:00
Lightczx
af87891a5f add server strings 2023-11-29 17:13:06 +08:00
DismissedLight
b523a2bb2a 1.8.3 package 2023-11-28 22:05:02 +08:00
Masterain
b3f8093c09 Update README.md 2023-11-28 02:39:41 -08:00
Lightczx
b4eb97a6ea fix incorrect resource name 2023-11-28 17:00:26 +08:00
Lightczx
77217d2fc3 fix #1138 2023-11-28 15:44:37 +08:00
Lightczx
8e386c1457 1.8.2 hotfix package 2023-11-28 13:32:09 +08:00
DismissedLight
330154c9ec Merge pull request #1136 from DGP-Studio/develop 2023-11-28 13:18:42 +08:00
Lightczx
f97385089a fix #1134 2023-11-28 13:16:16 +08:00
DismissedLight
8982fcd427 1.8.1 package 2023-11-27 19:57:27 +08:00
DismissedLight
aaa2e09dde Merge pull request #1131 from DGP-Studio/develop 2023-11-27 19:34:55 +08:00
Masterain
58fdcc7804 Update MGMT-publish.yml 2023-11-27 03:23:39 -08:00
DismissedLight
ca352a5262 revoke some static resources version 2023-11-27 19:22:57 +08:00
DismissedLight
0f9e34ffb8 Merge pull request #1118 from DGP-Studio/l10n_develop 2023-11-27 19:17:34 +08:00
DismissedLight
76a60e30fc #1123 repeat notification regression 2023-11-27 19:15:55 +08:00
Lightczx
693566812b add new server localization keys 2023-11-27 17:30:14 +08:00
DismissedLight
8fbe2ee831 Merge pull request #1129 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-0fa0185f23 2023-11-27 17:28:50 +08:00
dependabot[bot]
639a86d11b Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn).

- [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
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 07:12:58 +00:00
Masterain
3f21a5d8fc New translations sh.resx (English) 2023-11-26 08:34:00 -08:00
Masterain
8a62327e30 New translations sh.resx (Chinese Traditional) 2023-11-26 08:33:58 -08:00
Masterain
69b9a0c9dc New translations sh.resx (Korean) 2023-11-26 08:33:57 -08:00
Masterain
af56f3ac30 New translations sh.resx (Japanese) 2023-11-26 08:33:56 -08:00
DismissedLight
ffc899efe5 fix #1123 2023-11-26 20:15:22 +08:00
DismissedLight
80b958d9d3 impl #1121 2023-11-26 19:59:02 +08:00
DismissedLight
b60f7c215e minor ui fix 2023-11-26 19:14:49 +08:00
DismissedLight
7128dddb57 remove skill bottom padding 2023-11-26 16:42:00 +08:00
DismissedLight
9960b6f6b1 fix #1126 2023-11-26 15:55:37 +08:00
DismissedLight
7099ca43b6 impl #1127 2023-11-26 15:04:41 +08:00
DismissedLight
c9627e19e7 fix #1124 2023-11-26 14:41:53 +08:00
DismissedLight
6999103aaa refine wiki avatar page ui 2023-11-26 14:35:33 +08:00
Masterain
e54eef3aa7 New translations sh.resx (English) 2023-11-25 08:02:24 -08:00
DismissedLight
f2ef6ff8ec fix #1099 again 2023-11-25 10:10:16 +08:00
Masterain
35ddaaeb35 New translations sh.resx (English) 2023-11-24 07:47:53 -08:00
Masterain
c833655231 New translations sh.resx (Chinese Traditional) 2023-11-24 07:47:52 -08:00
Masterain
8724784803 New translations sh.resx (Korean) 2023-11-24 07:47:51 -08:00
Masterain
d1ceac0fe9 New translations sh.resx (Japanese) 2023-11-24 07:47:49 -08:00
Lightczx
43415ebd0d IPinnable 2023-11-24 17:31:10 +08:00
Lightczx
a8b697e782 fix #1119 2023-11-24 13:41:58 +08:00
Lightczx
602b31c52d restrict combobox size 2023-11-24 11:51:46 +08:00
Masterain
3b43389049 New translations sh.resx (Chinese Traditional) 2023-11-23 07:49:51 -08:00
Masterain
3a1fc839eb New translations sh.resx (Japanese) 2023-11-23 07:49:50 -08:00
DismissedLight
e0f967341e TCG decrypt [skip ci] 2023-11-23 23:34:31 +08:00
Lightczx
74ac738236 bump static resource version 2023-11-23 13:08:32 +08:00
Masterain
0189c4824b New translations sh.resx (English) 2023-11-22 07:43:58 -08:00
Masterain
1a43841833 New translations sh.resx (Chinese Traditional) 2023-11-22 07:43:56 -08:00
Masterain
29ad939498 New translations sh.resx (Korean) 2023-11-22 07:43:55 -08:00
Masterain
60c8e948e8 New translations sh.resx (Japanese) 2023-11-22 07:43:54 -08:00
DismissedLight
a17f9ca543 optimize discord activity 2023-11-22 22:28:06 +08:00
Lightczx
ee86f12168 impl #1082 2023-11-22 16:59:01 +08:00
DismissedLight
9fb79a9fbd Merge pull request #1117 from qhy040404/develop 2023-11-22 13:51:56 +08:00
Lightczx
467eb13c87 file nesting 2023-11-22 13:48:05 +08:00
Lightczx
40b055d310 lock on get winrt obj disposed 2023-11-22 13:43:35 +08:00
qhy040404
126d19e96a Apply suggestions 2023-11-22 13:39:07 +08:00
Lightczx
485ac1e682 trim unused xml 2023-11-22 13:28:40 +08:00
qhy040404
1117e322a6 Support the coexistence of Snap Hutao and Snap Hutao Dev 2023-11-22 13:10:05 +08:00
Masterain
85b40b71e5 Update PublishDistribution.yml 2023-11-21 17:33:53 -08:00
Masterain
66bbf4335a Update PublishDistribution.yml 2023-11-21 17:30:52 -08:00
DismissedLight
cb20bd0df4 Merge pull request #1116 from qhy040404/develop 2023-11-22 09:05:47 +08:00
qhy040404
cb9c9a0af2 Sync tab when navigate from GachaLog's HutaoCloudView to SpiralAbyssRecordPage 2023-11-21 22:14:27 +08:00
Lightczx
28ea71cf77 Add CollectionsMarshal Test 2023-11-21 16:17:53 +08:00
Lightczx
94744c4bc1 1.8.0 package 2023-11-21 15:03:43 +08:00
DismissedLight
58af255485 Merge pull request #1114 from DGP-Studio/develop 2023-11-21 14:10:59 +08:00
Masterain
c45e6560b8 New Crowdin updates (#1091)
Co-authored-by: DismissedLight <1686188646@qq.com>
2023-11-21 13:58:15 +08:00
Lightczx
29d7d36b66 Frozen Collections 2023-11-21 13:53:32 +08:00
Lightczx
2bbf6f192e fix conditional ci 2023-11-21 10:57:23 +08:00
Lightczx
cc8565428b replace hutao endpoints 2023-11-21 10:47:33 +08:00
DismissedLight
d84e0bf199 fix typo 2023-11-20 21:56:53 +08:00
DismissedLight
9d8c981f5a add profile picture basic support 2023-11-20 21:34:37 +08:00
Lightczx
1f412b289a move localization annotations 2023-11-20 17:25:03 +08:00
Lightczx
07b3e98ac0 add more specifically http message 2023-11-20 16:58:48 +08:00
DismissedLight
8a9f4ced34 Merge pull request #1111 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dfb7e59321 2023-11-20 15:23:19 +08:00
dependabot[bot]
0a0aa3e404 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime).

- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.DependencyInjection
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 07:19:21 +00:00
Lightczx
84861c5e6b update nuget package version 2023-11-20 14:44:57 +08:00
DismissedLight
a23e6f55d4 add basic discord support 2023-11-19 20:51:58 +08:00
DismissedLight
c1cf5aa499 prevent binding failure when achievement page disposed 2023-11-19 16:59:26 +08:00
DismissedLight
91b6e6793c simplify the startup statement 2023-11-19 16:48:43 +08:00
DismissedLight
ac1ef44801 fix startup user role change caused dead lock 2023-11-19 16:16:10 +08:00
DismissedLight
e7b23c8a5a minor bug fixes 2023-11-19 13:16:22 +08:00
DismissedLight
68a3851595 refine #1096 2023-11-18 22:47:09 +08:00
DismissedLight
7861ebf998 #impl 1096 2023-11-18 22:41:25 +08:00
DismissedLight
0cc75ab245 fix #1106 2023-11-17 22:29:17 +08:00
Lightczx
ef09c87835 fix service register type for geetest 2023-11-17 13:06:49 +08:00
Lightczx
b13ec8c12a win32 dialog 2023-11-16 16:58:41 +08:00
Lightczx
29e14111f0 fix azure pipeline 2023-11-16 14:40:52 +08:00
Lightczx
0dd66288cd fix azure pipeline 2023-11-16 14:30:59 +08:00
DismissedLight
bb835ee20e Merge pull request #1103 from DGP-Studio/dotnet8 2023-11-16 14:24:57 +08:00
Lightczx
a1037e1827 fix #1099 2023-11-16 14:13:32 +08:00
Lightczx
ba6ee9bb85 replace format extension 2023-11-16 13:15:37 +08:00
Lightczx
f1c50dc6c3 UnsafeAccessor 2023-11-16 10:47:07 +08:00
Lightczx
8e841b1295 remove using 2023-11-16 09:24:35 +08:00
DismissedLight
ea2000b03d Update DataSignAlgorithm.cs 2023-11-16 00:08:43 +08:00
DismissedLight
d186dd6f98 DynamicSecret -> DataSign 2023-11-16 00:08:01 +08:00
DismissedLight
5c46c92a1a rename constants 2023-11-15 22:36:57 +08:00
DismissedLight
98f2dd13d1 bulk refactor 2023-11-15 22:31:56 +08:00
Lightczx
3e0493be31 collection experssion 2 2023-11-15 17:26:18 +08:00
Lightczx
7025074170 fix collection style 2023-11-15 16:36:15 +08:00
Lightczx
0eade5e81a collection experssion 2023-11-15 16:19:43 +08:00
Lightczx
163d076ed5 Revert "fix shadow crash on announcement page"
This reverts commit cf5b377532.
2023-11-15 16:08:10 +08:00
Lightczx
fa5aac3366 Revert "remove AttachedCardShadow to fix startup crash"
This reverts commit afbc91abe8.
2023-11-15 16:07:57 +08:00
Lightczx
f98a40d080 fix NRE in DbCurrent and change UnsafeDateTimeOffset ns 2023-11-15 13:16:38 +08:00
Lightczx
cf5b377532 fix shadow crash on announcement page 2023-11-15 12:59:32 +08:00
Lightczx
afbc91abe8 remove AttachedCardShadow to fix startup crash 2023-11-15 12:53:20 +08:00
Lightczx
be572fa327 change csproj to net8 2023-11-15 12:52:52 +08:00
Lightczx
fe594864f2 use CoCreateInstance to activate ShellLinkW 2023-11-14 09:24:37 +08:00
DismissedLight
d8b6315e7e capture dispatched exceptions 2023-11-13 23:13:25 +08:00
Lightczx
d4e9c2aa9c localenames 2023-11-13 15:37:06 +08:00
DismissedLight
800c948c38 Merge pull request #1094 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-c2c108d172 2023-11-13 15:35:59 +08:00
dependabot[bot]
32589bc994 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.2...v17.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 07:08:36 +00:00
DismissedLight
afc91b1a29 remove unused using 2023-11-12 13:06:41 +08:00
DismissedLight
40db69825f Merge pull request #1090 from Tangweirui2021/main 2023-11-12 12:49:41 +08:00
DismissedLight
407dd7bac8 code style 2023-11-12 12:48:46 +08:00
Daniel
4708cd5629 fix user log in/register/unregister/reset password info bar localization 2023-11-11 18:42:14 +08:00
DismissedLight
2b93d31788 fix #1087 and improve some null checks 2023-11-11 12:55:47 +08:00
Lightczx
39831b0ae1 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-11-10 16:08:54 +08:00
Lightczx
51c9936018 1.7.17 package 2023-11-10 16:08:35 +08:00
Masterain
3466a98ffb Create MGMT-publish.yml 2023-11-09 23:48:29 -08:00
DismissedLight
a064cc10ee Merge pull request #1085 from DGP-Studio/develop 2023-11-10 15:41:33 +08:00
Masterain
3479a19164 New Crowdin updates (#1078)
Co-authored-by: DismissedLight <1686188646@qq.com>
2023-11-10 15:39:00 +08:00
Lightczx
d4549581c1 fix exception capture 2023-11-10 15:33:11 +08:00
Lightczx
f97bc344d0 fix announcement time incorrectness 2023-11-10 14:20:44 +08:00
Lightczx
26d23fec7f impl #830 in previous commit 2023-11-10 11:39:53 +08:00
Lightczx
7442f7f1ec support UIGF v2.4-preview 2023-11-10 11:37:45 +08:00
DismissedLight
3eb2556393 update gacha info endpoints 2023-11-09 23:39:51 +08:00
DismissedLight
cfff6f39fc adjust server timezone 2023-11-09 23:15:08 +08:00
Lightczx
3005031b39 add basic timezone support for gachaitem 2023-11-09 17:18:56 +08:00
Lightczx
71363f4d8d fix #1081 2023-11-09 15:23:51 +08:00
Lightczx
e833578334 rename jsbridge 2023-11-09 11:51:56 +08:00
Lightczx
d529b3cea6 fix #1079 2023-11-09 11:38:30 +08:00
Lightczx
1c0ce62885 fix gacha item corner radius 2023-11-08 13:34:55 +08:00
DismissedLight
acdf2baa9a improve webviewer & hotkey 2023-11-07 21:02:25 +08:00
DismissedLight
ec007d5d81 add fp to jsbridge 2023-11-07 19:08:48 +08:00
Lightczx
5e734ac689 impl #961 2023-11-07 15:37:53 +08:00
DismissedLight
b0ecd048b6 1.7.16 package 2023-11-06 21:41:04 +08:00
DismissedLight
91010d0d8b Merge pull request #1076 from DGP-Studio/develop 2023-11-06 20:57:23 +08:00
Masterain
fc771eb90a New Crowdin updates (#1063) 2023-11-06 20:56:40 +08:00
DismissedLight
80f2fed722 Merge pull request #1075 from DGP-Studio/develop 2023-11-06 20:56:15 +08:00
DismissedLight
bdb406c451 add copy hint for #1074 2023-11-06 20:53:29 +08:00
DismissedLight
5bc957c6a5 fix spiral abyss crash when using 4.2 metadata 2023-11-06 20:32:29 +08:00
Lightczx
416c6f15a6 partial #1074 2023-11-06 17:07:35 +08:00
Lightczx
9eed633e05 DefaultItemCollectionTransitionProvider 2023-11-06 15:52:50 +08:00
Lightczx
7e30173990 update to 4.2 metadata 2023-11-06 14:57:57 +08:00
Lightczx
2200e2e58e fonticon resources 2023-11-06 14:36:36 +08:00
Lightczx
b8886c5cd3 fix #1072 2023-11-06 13:46:59 +08:00
DismissedLight
43007d8fb4 Merge pull request #1073 from qhy040404/develop 2023-11-06 12:40:53 +08:00
Lightczx
88684bff00 code style 2023-11-06 12:40:19 +08:00
qhy040404
0c7ce7a72f Add files 2023-11-06 12:32:22 +08:00
qhy040404
075d92f754 Set IsEnabled by a new property instead of setting it separately for each SettingsCard 2023-11-06 12:18:05 +08:00
Lightczx
a0cba171cc update feat template 2023-11-06 11:43:57 +08:00
Lightczx
f41185310b adjust wish typename 2023-11-06 11:41:33 +08:00
Lightczx
2a4c93d241 impl #778 all 2023-11-06 11:17:16 +08:00
DismissedLight
c0980fabe8 impl #1071 2023-11-05 16:03:10 +08:00
DismissedLight
f2ba316059 recycle fingerprint 2023-11-04 17:47:45 +08:00
DismissedLight
e4e9dd91f1 impl #1062 2023-11-04 17:21:31 +08:00
DismissedLight
749ef0e138 introducing game service facade 2023-11-04 16:53:08 +08:00
DismissedLight
24086ee4d0 optimize UniformStaggeredColumnLayout 2023-11-03 23:32:52 +08:00
DismissedLight
aeb6962ae4 impl #1015 2023-11-03 22:06:51 +08:00
Lightczx
87e5ede91f impl #1068 2023-11-03 16:20:11 +08:00
Lightczx
91de6d170e add fingerprint fetch & fix #1060 2023-11-03 11:52:52 +08:00
Lightczx
3057673cdb fix #1069 2023-11-03 10:11:47 +08:00
Lightczx
c3ace405ac fix pushpage 2023-11-03 09:26:44 +08:00
DismissedLight
0b48581e65 Merge pull request #1065 from qhy040404/main 2023-11-02 21:16:40 +08:00
DismissedLight
4ab129e4a2 code style 2023-11-02 21:14:39 +08:00
qhy040404
13ad36f5b4 Added a master switch for launchOptions 2023-11-02 18:43:07 +08:00
Lightczx
f026321aa8 1.7.15 package 2023-11-02 15:27:40 +08:00
DismissedLight
d1dfdf107b Merge pull request #1064 from DGP-Studio/develop 2023-11-02 15:10:51 +08:00
Lightczx
59f8895675 MarqueeText 2023-11-02 15:05:06 +08:00
Lightczx
4cb3d5f03f launch scheme renewed 2023-11-02 14:23:34 +08:00
Lightczx
067c7d7c4d fix ci 2023-11-02 12:45:11 +08:00
Lightczx
1cc072ba28 EmailSmtpAddress 2023-11-02 11:35:31 +08:00
Lightczx
0e7afa8efb clear username & password after cancel registration 2023-11-02 11:30:00 +08:00
Lightczx
b753728b7e verify code request set token 2023-11-02 11:12:26 +08:00
Lightczx
df019da891 complete cancel registration 2023-11-02 10:42:55 +08:00
Lightczx
c6435f30eb add verify for cancel registration 2023-11-02 10:26:27 +08:00
Lightczx
3ac0be4220 fix unregister passport 2023-11-02 10:07:20 +08:00
Lightczx
24f6a33256 clear username & password after logout 2023-11-02 09:48:22 +08:00
Lightczx
dc9278eb4f fix verifycode crash 2023-11-02 09:42:08 +08:00
Lightczx
4b2c82db62 fix xaml parsing failed 2023-11-02 09:06:56 +08:00
Lightczx
70f30edd7c xaml style rework 2023-11-01 17:03:00 +08:00
Lightczx
c8e8213df6 code style 2023-11-01 15:56:22 +08:00
Lightczx
7cad996902 process cmdline #1061 2023-11-01 15:28:44 +08:00
Lightczx
eec47b72c7 fix #1061 2023-11-01 15:26:36 +08:00
Lightczx
5943b1a1fb impl #886 2023-11-01 13:46:50 +08:00
Lightczx
9f9a5670bc fix dailynote webhook error 2023-11-01 10:45:47 +08:00
Lightczx
10ba927136 fix #1059 2023-10-31 16:45:24 +08:00
Lightczx
07d42cedd1 page style 2023-10-31 15:19:25 +08:00
Lightczx
07c52019f4 1.7.14 hotfix package 2023-10-31 11:38:51 +08:00
DismissedLight
77cb2fc603 Merge pull request #1056 from DGP-Studio/develop 2023-10-31 11:11:03 +08:00
Masterain
b5c16e2dae New Crowdin updates (#1053) 2023-10-31 11:10:06 +08:00
DismissedLight
29c954b032 fingerprint 2023-10-30 22:03:33 +08:00
DismissedLight
8df5d5d6eb fix #1052 & user account add crash 2023-10-30 19:42:36 +08:00
Lightczx
827d944987 1.7.13 package 2023-10-30 15:38:00 +08:00
DismissedLight
4c47f3c08b Merge pull request #1050 from DGP-Studio/develop 2023-10-30 15:16:10 +08:00
DismissedLight
6540cc4577 Merge pull request #1045 from DGP-Studio/l10n_develop 2023-10-30 15:14:10 +08:00
Lightczx
df22d30a96 ui/ux 2023-10-30 15:12:30 +08:00
Lightczx
5ea9dd533f remove hutao user changed message 2023-10-30 09:14:21 +08:00
DismissedLight
744c1079e1 fix #903 2023-10-29 22:56:10 +08:00
Masterain
1539863415 New translations sh.resx (English) 2023-10-29 07:32:38 -07:00
Masterain
3527e43118 New translations sh.resx (Chinese Traditional) 2023-10-29 07:32:37 -07:00
Masterain
8388def548 New translations sh.resx (Korean) 2023-10-29 07:32:36 -07:00
Masterain
d0bfbfa505 New translations sh.resx (Japanese) 2023-10-29 07:32:35 -07:00
DismissedLight
fa640d27f0 implement #911 2023-10-29 22:08:51 +08:00
DismissedLight
389c1417f7 built in resx generator 2023-10-28 15:00:43 +08:00
Masterain
6dcacb1bf4 New translations sh.resx (Japanese) 2023-10-27 10:45:51 -07:00
Lightczx
4ffd09cce8 roslyn generated resx 2023-10-27 15:50:32 +08:00
Lightczx
0dcbac3ee1 fix #431 input crash 2023-10-27 09:23:05 +08:00
Masterain
b1ea5332fc Merge pull request #1037 from DGP-Studio/l10n_develop
New Crowdin updates
2023-10-26 10:29:22 -07:00
Masterain
b34dab0f99 New translations sh.resx (English) 2023-10-26 10:28:57 -07:00
Masterain
791e517e39 New translations sh.resx (Chinese Traditional) 2023-10-26 10:28:55 -07:00
Masterain
4408e3994e New translations sh.resx (Korean) 2023-10-26 10:28:54 -07:00
Masterain
32cbbefe1a New translations sh.resx (Japanese) 2023-10-26 10:28:52 -07:00
DismissedLight
d754c0d117 ignore designer file 2 2023-10-26 23:04:21 +08:00
DismissedLight
4c75295f2c ignore designer file 1 2023-10-26 23:04:01 +08:00
DismissedLight
34e5312d75 impl #1021 2023-10-26 22:58:40 +08:00
Masterain
465d6b631e Update issue templates 2023-10-26 03:37:04 -07:00
Masterain
0d2d1b8115 New translations sh.resx (English) 2023-10-26 02:34:48 -07:00
Masterain
981949651e New translations sh.resx (English) 2023-10-26 02:28:47 -07:00
Masterain
874dac1119 New translations sh.resx (Chinese Traditional) 2023-10-26 02:28:46 -07:00
Masterain
3f0694b28e New translations sh.resx (Korean) 2023-10-26 02:28:44 -07:00
Masterain
6b166b6aed New translations sh.resx (Japanese) 2023-10-26 02:28:43 -07:00
Lightczx
b351231c84 typo fix 2023-10-26 17:27:55 +08:00
Lightczx
0603b24466 implement #431 2023-10-26 15:31:55 +08:00
Lightczx
f97ad4eac0 fix IsGameRunning 2023-10-26 14:12:45 +08:00
DismissedLight
28fc4558be support server l10n 2023-10-25 23:26:46 +08:00
Masterain
ea3391b112 New translations sh.resx (English) 2023-10-25 02:28:14 -07:00
DismissedLight
ec95e42d7d fix #1041 2023-10-23 19:04:00 +08:00
Masterain
e9f12aeb09 Update .github configurations 2023-10-21 19:52:34 -07:00
Masterain
04850dd136 New translations sh.resx (Japanese) 2023-10-21 11:07:53 -07:00
Masterain
4782d61ed0 New translations sh.resx (English) 2023-10-20 06:16:12 -07:00
Masterain
28ade90926 New translations sh.resx (Chinese Traditional) 2023-10-20 06:16:11 -07:00
Masterain
dde97b6489 New translations sh.resx (Korean) 2023-10-20 06:16:10 -07:00
Masterain
44fe729e1a New translations sh.resx (Japanese) 2023-10-20 06:16:09 -07:00
Lightczx
2a1d814cc5 fix #899 2023-10-20 11:44:42 +08:00
Lightczx
c1ee37bd8f fix #1023 2023-10-20 10:59:30 +08:00
Lightczx
65b81f0ad8 fix #1035 2023-10-20 09:07:40 +08:00
DismissedLight
91fea88623 launch page redo 2023-10-19 22:32:01 +08:00
DismissedLight
026c68229a fix #925 2023-10-19 20:46:29 +08:00
DismissedLight
91b2db886f 1.7.11 hotfix package 2023-10-18 19:53:00 +08:00
DismissedLight
101d316525 Merge pull request #1032 from DGP-Studio/develop 2023-10-18 19:38:01 +08:00
DismissedLight
59d62f931d fix launch args 2023-10-18 19:35:59 +08:00
Lightczx
f0bb19bc07 fix #1028 2023-10-18 17:21:45 +08:00
Masterain
c0b05e2c2f Update FUNDING.yml 2023-10-17 17:15:55 -07:00
Masterain
fc02f833a0 Update FUNDING.yml 2023-10-17 15:36:04 -07:00
DismissedLight
7b8ebd86b1 1.7.10 package 2023-10-17 22:12:58 +08:00
DismissedLight
47b24286b1 Merge pull request #1024 from DGP-Studio/develop 2023-10-17 20:31:47 +08:00
Masterain
0e3e3b9e4a New Crowdin updates (#1019) 2023-10-17 20:31:15 +08:00
DismissedLight
85d7b22e11 auto select existed account when detecting 2023-10-17 20:29:14 +08:00
Lightczx
7caeb17788 use reference in picker factory 2023-10-17 14:51:41 +08:00
Lightczx
b11b90e9f1 re-impl #1020 2023-10-16 11:03:41 +08:00
DismissedLight
58643a60b5 fix bugs 2023-10-15 16:14:22 +08:00
DismissedLight
a29b487c26 impl #1020 2023-10-15 16:14:13 +08:00
DismissedLight
1bd6023e0a fix issues 2023-10-15 12:30:44 +08:00
DismissedLight
579173d464 webview lifetime 2023-10-14 19:29:28 +08:00
DismissedLight
9ed53e8c34 fix spiralabyss damage rank empty crash 2023-10-14 17:26:06 +08:00
DismissedLight
830556a043 fix ICoreWebView2_13 not supported 2023-10-14 00:12:14 +08:00
DismissedLight
7ba27e184f ignore invalid launch schemes 2023-10-13 23:17:08 +08:00
DismissedLight
9aa6a2b57b fix invalid geetest url crash 2023-10-13 22:53:32 +08:00
DismissedLight
5773902f4a fix #1001 2023-10-13 22:36:51 +08:00
DismissedLight
06c5bcad3e fix #1013 #1012 #1011 2023-10-13 21:42:36 +08:00
Lightczx
c0165c57fd support Send on sync context 2023-10-13 15:27:46 +08:00
DismissedLight
4e57520115 Update AvatarIds.cs 2023-10-12 22:55:35 +08:00
Masterain
17c3480dae New Crowdin updates (#1006) 2023-10-12 22:10:54 +08:00
DismissedLight
7d5faadbb5 1.7.9 hotfix package 2023-10-12 22:03:24 +08:00
DismissedLight
b7df968ea7 fix #1008 2023-10-12 21:33:35 +08:00
DismissedLight
5f6cc46774 Merge pull request #1009 from DGP-Studio/develop 2023-10-12 21:04:39 +08:00
Lightczx
a34b5a5101 style 2023-10-12 16:58:29 +08:00
Lightczx
457e3c4af2 LoadImageSurfaceAsync fix 2023-10-12 16:45:59 +08:00
Lightczx
26143079b3 app resource moved 2023-10-12 15:59:42 +08:00
DismissedLight
20ad9aec60 fix 1.7.7 log upload issue 2023-10-11 22:28:10 +08:00
DismissedLight
61dd098d95 fix #1003 2023-10-11 22:12:07 +08:00
DismissedLight
e7233fbf2a fix #1007 2023-10-11 20:50:21 +08:00
DismissedLight
8de526274a fix #1005 2023-10-10 22:28:53 +08:00
DismissedLight
5b1fb6e1dd move resources 2023-10-10 22:27:53 +08:00
DismissedLight
1584fd1428 fix mutiple display window init size incorrectness 2023-10-10 20:23:39 +08:00
Masterain
65179a340f Update README.md 2023-10-09 04:02:38 -07:00
DismissedLight
9f984b4a9b 1.7.7 package 2023-10-08 22:59:36 +08:00
DismissedLight
8a36bfb62c Merge pull request #978 from DGP-Studio/l10n_develop 2023-10-08 22:34:22 +08:00
DismissedLight
c9c8f6a5cf Merge pull request #998 from DGP-Studio/develop 2023-10-08 22:28:58 +08:00
DismissedLight
f9e1ef7ab5 fix #990 2023-10-08 22:26:40 +08:00
DismissedLight
be06a91e0f code style 2023-10-08 22:08:17 +08:00
DismissedLight
174757f94d adjust nullability annotation 2023-10-08 21:51:24 +08:00
Lightczx
1cd3ec0cdb experiment 2023-10-08 17:30:35 +08:00
Lightczx
7b17bb5a01 reset window size 2023-10-08 14:15:27 +08:00
Masterain
c3bab3c677 New translations sh.resx (Japanese) 2023-10-07 15:35:50 -07:00
DismissedLight
17f1c873ae shadow complete 2023-10-07 23:49:23 +08:00
Lightczx
313a17b7eb code style 2023-10-07 11:25:06 +08:00
Lightczx
9ad321e10d fix ci build 2023-10-07 11:15:49 +08:00
Masterain
f18dfc0baf New translations sh.resx (Japanese) 2023-10-06 15:28:34 -07:00
DismissedLight
16946c58fe fix minor request bug 2023-10-06 21:48:10 +08:00
DismissedLight
c7b931956c import reqrest 2023-10-06 21:33:09 +08:00
Masterain
6c0c5b4669 New translations sh.resx (English) 2023-10-05 15:28:01 -07:00
Masterain
0342805293 New translations sh.resx (Chinese Traditional) 2023-10-05 15:28:00 -07:00
Masterain
e0b1896336 New translations sh.resx (Korean) 2023-10-05 15:27:59 -07:00
Masterain
6d1958af4c New translations sh.resx (Japanese) 2023-10-05 15:27:58 -07:00
DismissedLight
42ab2c3000 Merge pull request #989 from Xhichn/develop 2023-10-05 12:29:01 +08:00
Xhichn
d0fb00dccd Impl GetInfoAsync 2023-10-05 10:39:00 +08:00
DismissedLight
5bc484db78 signIn improvement 2023-10-05 10:02:25 +08:00
DismissedLight
c665834f77 Merge pull request #988 from DGP-Studio/main 2023-10-05 09:41:00 +08:00
DismissedLight
81d73568a7 Merge pull request #965 from Xhichn/DailyReward 2023-10-05 09:37:54 +08:00
DismissedLight
c940ec7006 dailynote & spiralabyss adjust 2023-10-05 09:32:17 +08:00
Masterain
71e5e6f190 New translations sh.resx (English) 2023-10-04 15:21:20 -07:00
Masterain
3983eaec0f New translations sh.resx (Chinese Traditional) 2023-10-04 15:21:19 -07:00
Masterain
9392911547 New translations sh.resx (Korean) 2023-10-04 15:21:18 -07:00
Masterain
f3d1fda053 New translations sh.resx (Japanese) 2023-10-04 15:21:16 -07:00
DismissedLight
14c3a14c2d impl #902 2023-10-04 23:15:32 +08:00
DismissedLight
31568ffba9 correct naming 2023-10-04 22:20:44 +08:00
DismissedLight
3d3d004441 rename signin interface 2023-10-04 22:20:06 +08:00
Masterain
a40e5f2a80 Update Qodana configuration 2023-10-03 14:47:34 -07:00
Masterain
186da74704 Merge pull request #985 from Masterain98/main
Create CONTRIBUTING.md
2023-10-03 03:53:59 -07:00
Masterain
c532799eb1 Update Qodana configuration 2023-10-03 03:34:54 -07:00
Masterain
595e19a1df Update CONTRIBUTING.md 2023-10-03 03:24:59 -07:00
Masterain
c376c4e0e8 Create CONTRIBUTING.md
resolve #909
2023-10-03 02:46:18 -07:00
DismissedLight
07f0c37b2c attempt to fix desktop icon creation 2023-10-03 17:39:30 +08:00
DismissedLight
ecdaeccc42 view style 2023-10-03 15:14:44 +08:00
Masterain
64bbcdfc80 New translations sh.resx (English) 2023-10-02 20:29:13 -07:00
Masterain
59fccf0cba New translations sh.resx (Chinese Traditional) 2023-10-02 20:29:12 -07:00
Masterain
f860211632 New translations sh.resx (Korean) 2023-10-02 20:29:11 -07:00
Masterain
a15a8cfe2d New translations sh.resx (Japanese) 2023-10-02 20:29:10 -07:00
DismissedLight
38bed7318f fix #982 2023-10-02 15:42:56 +08:00
DismissedLight
e513b8b00b add string 2023-10-02 13:24:51 +08:00
DismissedLight
1e711172af fix #980 2023-10-02 13:23:45 +08:00
Masterain
8beb47fd2c New translations sh.resx (English) 2023-10-01 20:29:31 -07:00
DismissedLight
41ab80511e separate win32 to increate analyzer response speed 2023-10-01 16:25:39 +08:00
DismissedLight
318b6190d2 fix game convert config not save 2023-10-01 11:29:23 +08:00
Masterain
6e3dbd41c7 New translations sh.resx (English) 2023-09-30 20:10:41 -07:00
Masterain
911f667e98 New translations sh.resx (Chinese Traditional) 2023-09-30 20:10:40 -07:00
Masterain
db6537bbe4 New translations sh.resx (Korean) 2023-09-30 20:10:39 -07:00
Masterain
6b640b5e15 New translations sh.resx (Japanese) 2023-09-30 20:10:38 -07:00
DismissedLight
bfe9837b36 fix several issues 2023-10-01 11:08:24 +08:00
Xhichn
b8255e77a3 Typo fix 2023-09-28 11:42:48 +08:00
DismissedLight
500888c2d1 launch scheme fix 2023-09-27 23:54:05 +08:00
Xhichn
5141d4a567 HoYolab Daily Reward Support 2023-09-27 18:51:41 +08:00
Lightczx
a86f5e1b59 add more compatonly scheme 2023-09-27 17:20:27 +08:00
Lightczx
390b89fc82 fix multiple channel/subchannel crash & setting clear invalid cache path crash 2023-09-27 17:12:32 +08:00
DismissedLight
6d6d97c59a fix mihoyo login no login_ticket crash 2023-09-26 23:40:30 +08:00
DismissedLight
df23fe54d6 fix manual input gacha url no auth_appid crash 2023-09-26 23:31:55 +08:00
DismissedLight
8caef9af0c MultiChannel: [ChannelType:Bili] [SubChannel:Official] [IsOversea: False] 2023-09-26 23:28:58 +08:00
DismissedLight
aac0e268b0 fix hoyoverse login crash 2023-09-26 23:23:02 +08:00
DismissedLight
bafee4ba18 fix detect game account crash 2023-09-26 22:26:01 +08:00
DismissedLight
4a1dccd7ac update intrinsics 2023-09-26 21:49:18 +08:00
Lightczx
1f8e73f255 disable concurrent launch game 2023-09-26 14:16:03 +08:00
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
772 changed files with 32123 additions and 24217 deletions

12
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"cake.tool": {
"version": "4.0.0",
"commands": [
"dotnet-cake"
]
}
}
}

4
.github/FUNDING.yml vendored
View File

@@ -1,8 +1,8 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [DGP-Studio]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
open_collective: snaphutao
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry

View File

@@ -1,12 +1,12 @@
name: BUG Report 问题反馈
description: 告诉我们你的问题
name: 问题反馈
description: 通过这个议题向开发团队反馈你发现的程序中的问题
title: "[Bug]: 在这里填写一个合适的标题"
labels: ["BUG"]
labels: ["BUG", "priority:none"]
body:
- type: markdown
attributes:
value: |
> **请在上方设置一个合适的工单标题**
> **请在上方以一句话简短地概括你的问题作为标题**
> 请按下方的要求填写完整的问题表单,以便我们更快的定位问题。
- type: checkboxes
@@ -14,17 +14,16 @@ body:
attributes:
label: 检查清单
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%E5%AE%8C%E6%88%90)的问题也不是一个别人已发布的**重复的**问题
required: true
- type: input
id: winver
@@ -32,7 +31,7 @@ body:
label: Windows 版本
description: |
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
placeholder: 22621.1105
placeholder: 22000.556
validations:
required: true
@@ -52,6 +51,7 @@ body:
description: |
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
如果你的程序已经无法启动,请下载并运行[此工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
validations:
required: false
@@ -66,13 +66,14 @@ body:
- 角色信息面板
- 游戏启动器
- 实时便笺
- 养成计算
- 用户面板
- 养成计算
- 文件缓存
- 祈愿记录
- 玩家查询
- 胡桃数据库
- 用户界面
- 胡桃云
- 胡桃帐号
- 签到
- Wiki
- 公告
@@ -84,7 +85,9 @@ body:
id: what-happened
attributes:
label: 发生了什么?
description: 详细的描述问题发生前后的行为,以便我们解决问题
description: |
详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
如果你无法找到该日志,请下载并运行[此工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将转储问题日志至工具运行目录中的 `Snap.Hutao Error Log.txt`
validations:
required: true
@@ -95,4 +98,13 @@ body:
description: 详细的描述你期望发生的行为,突出与目前(可能不正确的)行为的不同
validations:
required: false
- type: checkboxes
id: checklist-final
attributes:
label: 最后一步
description: 回顾你的回答
options:
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
required: true

View File

@@ -0,0 +1,27 @@
name: 功能请求
description: 通过这个议题来向开发团队分享你的想法
title: "[Feat]: 在这里填写一个合适的标题"
labels: ["功能", "needs-triage", "priority:none"]
assignees:
- Lightczx
body:
- type: markdown
attributes:
value: |
请按下方的要求填写完整的问题表单。
- type: textarea
id: back
attributes:
label: 背景与动机
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的议题
validations:
required: true
- type: textarea
id: req
attributes:
label: 想要实现或优化的功能
description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
validations:
required: true

View File

@@ -1,5 +1,5 @@
name: 网络问题
description: 当网络问题影响到你的程序使用时
description: 通过这个议题来反馈网络问题
title: "[Network]: 在这里填写一个合适的标题"
labels: ["area-Network"]
assignees:
@@ -19,10 +19,10 @@ body:
description: |
停下!
**在填写下面的问题之前请先使用我们的网络诊断工具**
**这个工具将会生成一份报告,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
**这个工具将会生成一份报告并加密压缩,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
- 你可以点击下面的链接以下载网络诊断工具:
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-hutao.exe)
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/network-diagnosis-hutao.exe)
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/SH-Network-Diagnosis.exe)
- [极狐 GitLab](https://jihulab.com/DGP-Studio/network-diagnosis-tool/-/jobs/11144011/artifacts/raw/SH-Network-Diagnosis.exe?inline=false)
validations:
required: true
@@ -60,7 +60,6 @@ body:
- 完全无法连接服务器
- 连接速度慢
- 获取到了不正确的页面或数据
- 客户端提示 429 Error
- 客户端图片下载错误
- 客户端图片预下载错误
- 其它
@@ -74,5 +73,12 @@ body:
description: 如果你在上一项中选择了`其它`或者你有更多信息需要提供,请在这里写下来
validations:
required: false
- type: checkboxes
id: checklist-final
attributes:
label: 最后一步
description: 检查你提交的议题
options:
- label: 我已经在该议题中上传了包含网络诊断报告的加密压缩包
required: true

View File

@@ -0,0 +1,110 @@
name: BUG Report [English Form]
description: Tell us what issue you get
title: "[ENG][Bug]: Place your Issue Title Here"
labels: ["BUG", "priority:none"]
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 [finished issue](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E5%AE%8C%E6%88%90), 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
If your program cannot startup, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
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**If your program cannot startup, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1), it will shows your device ID.
If you cannot find it, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will dump the error log to `Snap.Hutao Error Log.txt` in the working directory of the tool.
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

@@ -0,0 +1,27 @@
name: Feature Request [English Form]
description: Tell us about your thought
title: "[Feat]: Place your title here"
labels: ["功能", "needs-triage", "priority:none"]
assignees:
- Lightczx
body:
- type: markdown
attributes:
value: |
Please fill the form below
- type: textarea
id: back
attributes:
label: Background & Motivation
description: Reason why this feature is needed. If multiple features is requested, please open multiple issues for each of them.
validations:
required: true
- type: textarea
id: req
attributes:
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

@@ -0,0 +1,79 @@
name: Network Issue [English Form]
description: Submit this issue form when network issue affect your client experience
title: "[Network]: Place your title here"
labels: ["area-Network"]
assignees:
- Lightczx
- Masterain98
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: textarea
id: network-diagnosis-report
attributes:
label: Submit Your Network Diagnosis Report
description: |
STOP HERE!
**Please run our network diagnosis tool before filling this form**
**The diagnosis tool will generate a report and add it into a password-protected archive. Drag the `.zip` archive to the box below so it can be uploaded.**
- Use the following link to download the Network Diagnosis Tool:
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/SH-Network-Diagnosis.exe)
- [JIHu GitLab](https://jihulab.com/DGP-Studio/network-diagnosis-tool/-/jobs/11144011/artifacts/raw/SH-Network-Diagnosis.exe?inline=false)
validations:
required: true
- type: input
id: user-geo-location
attributes:
label: Your Geographical Location
description: |
Description accurate to country
placeholder: USA
validations:
required: true
- type: input
id: user-isp
attributes:
label: Your ISP Name
description: |
Name of your Internet service provider
placeholder: AT&T
validations:
required: true
- type: dropdown
id: user-issue-category
attributes:
label: Issue Category
description: Select an issue category
options:
- Cannot connect to server completely
- Slow spped
- Fetched wrong page or data
- Image download error in the client
- Image set pre-download error (client welcome wizard process)
- Other
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: Your Issue (cont.)
description: If you selected `Other` in previous dropdown, please explain your issue in detail here.
validations:
required: false
- type: checkboxes
id: checklist-final
attributes:
label: One Last Step
description: Check your issue form
options:
- label: I confirm I have attached the network diagnosis report archive in the issue
required: true

43
.github/ISSUE_TEMPLATE/MGMT-publish.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Publish Process
description: FOR ADMIN USE ONLY. WILL CAUSE A BAN IF NO PERMISSION.
title: "[Publish]: Version 1.9.98"
labels: ["Publish"]
assignees:
- Lightczx
body:
- type: textarea
id: main-body
attributes:
label: Publish Process
value: |
## 创建版本
- [ ] 同步一次 [Crowdin](https://crowdin.com/project/snap-hutao) 翻译
- [ ] 发布 RC 版本Optional
- [ ] 合并入主分支
- [ ] 整理更新内容,等待翻译
- [ ] 在 [Snap.Hutao.Docs@next-patch](https://github.com/DGP-Studio/Snap.Hutao.Docs/tree/next-patch) 分支更新文档并直接开 PR
- [ ] 更新日志
- [ ] 功能文档更新
## 发布版本 [半自动]
- [ ] 在 GitHub 个人设置中更新 [Publish-Automate Beta PAT](https://github.com/settings/tokens?type=beta),有效期需小于预计发版需要天数
- [ ] 将更新的 PAT 更新至 Publish-Automate 库的 [Actions Secrets](https://github.com/DGP-Automation/Publish-Automate/settings/secrets/actions) 中
***
- [ ] 主分支合并入 release 分支
- [ ] 等待 Release 自动发布
- [ ] 检查极狐是否同步完成 Release
- [ ] 通知用户
- type: checkboxes
id: checklist-final
attributes:
label: Final Check
description: Understand what you are doing
options:
- label: I understand that I will get banned from repository if I don't have permission to use this template
required: true

View File

@@ -1,65 +0,0 @@
name: 圣遗物评分细则建议
description: 为圣遗物评分规则提供你的想法
title: "[Artifact Rating] 请在这里填写角色名称"
labels: area-AvatarInfo
assignees: Lightczx
body:
- type: markdown
attributes:
value: |
请按下方的要求填写完整的问题表单
- type: textarea
id: your-suggested-rule
attributes:
label: 评分细则
description: |
请修改下方表格中的**角色名称**和**各属性权重**,并在表格后添加合适的说明
你可以点击预览按钮preview来查看表格最终会显示出的内容
value: |
|项目|评分权重(0-100)|
|-----|-----|
|角色名称| 旅行者 |
|生命值| 10 |
|攻击力| 10 |
|防御力| 10 |
|暴击率| 10 |
|暴击伤害| 10 |
|元素精通| 10 |
|充能效率| 10 |
|治疗加成| 10 |
|元素伤害| 10 |
validations:
required: true
- type: dropdown
id: no-duplicated-dropdown
attributes:
label: 我确认当前没有其它的该角色的圣遗物评分细则建议
description: 如果有,你应该在已有的工单内回复以提出你的建议
options:
-
-
validations:
required: true
- type: dropdown
id: title-filled-dropdown
attributes:
label: 我确认已设置合适的标题
options:
-
-
validations:
required: true
- type: dropdown
id: all-filled-dropdown
attributes:
label: 我确认已完整填写表格
options:
-
-
validations:
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,28 +0,0 @@
name: Feature Request 功能请求
description: Tell us about your thought 告诉我们你的想法
title: "[Feat]: Place your title here 在这里填写一个合适的标题"
labels: ["功能"]
assignees:
- Lightczx
body:
- type: markdown
attributes:
value: |
Please fill the form below
请按下方的要求填写完整的问题表单。
- type: textarea
id: back
attributes:
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: 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

@@ -12,40 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout Repo
uses: actions/checkout@v3
# Download Assets
- name: Download Release
timeout-minutes: 5
uses: robinraju/release-downloader@v1.7
with:
repository: "DGP-Studio/Snap.Hutao"
latest: true
fileName: "*.msix"
out-file-path: ./release-download
# Upload to Drive
- name: Upload Drive
timeout-minutes: 15
env:
RCCONF: ${{ secrets.RCCONF }}
run: |
curl https://rclone.org/install.sh | sudo bash
mkdir -p ~/.config/rclone/
cat << EOF > ~/.config/rclone/rclone.conf
$RCCONF
EOF
rclone copy ./release-download/* dgpODCN:/releases/
# Purge Patch System Cache
- name: Purge Patch
env:
PATCH_HOSTS: ${{ secrets.PATCH_HOSTS }}
PURGE_TOKEN: ${{ secrets.PURGE_TOKEN }}
PURGE_URL: ${{ secrets.PURGE_URL }}
run: |
sudo echo "$PATCH_HOSTS" | sudo tee -a /etc/hosts
curl --header "Authorization: token $PURGE_TOKEN" $PURGE_URL
curl -X PATCH $PURGE_URL

65
.github/workflows/alpha.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Snap Hutao Alpha
on:
workflow_dispatch:
push:
branches:
- main
- develop
paths-ignore:
- '.gitattributes'
- '.github/**'
- '.gitignore'
- '.gitmodules'
- '**.md'
- 'LICENSE'
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
- name: Setup .NET
uses: actions/setup-dotnet@v4.0.0
with:
dotnet-version: 8.0
- name: Cake
id: cake
shell: pwsh
run: dotnet tool restore && dotnet cake
env:
VERSION_API_TOKEN: ${{ secrets.VERSION_API_TOKEN }}
- name: Sign Msix
shell: pwsh
run: |
[System.Convert]::FromBase64String("${{ secrets.CERTIFICATE }}") | Set-Content -AsByteStream temp.pfx
signtool.exe sign /debug /v /a /fd SHA256 /f temp.pfx /p ${{ secrets.PW }} ${{ github.workspace }}\src\output\Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
- name: Upload signed msix
if: success()
uses: actions/upload-artifact@v3
with:
name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}
path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
- name: Add summary
if: success()
shell: pwsh
run: |
$summary = "
> [!WARNING]
> 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
> [!TIP]
> 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
> [!IMPORTANT]
> 请注意,从 Snap Hutao Alpha 2023.12.21.3 开始,我们将使用全新的 CI 证书,原有的 Snap.Hutao.CI.cer 将在几天后过期停止使用。
>
> 请安装 [DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt) 以安装测试版安装包
"
echo $summary >> $Env:GITHUB_STEP_SUMMARY

14
.gitignore vendored
View File

@@ -1,7 +1,6 @@
desktop.ini
*.csproj.user
*.pubxml
*.DotSettings.user
.vs/
@@ -10,14 +9,13 @@ src/Snap.Hutao/_ReSharper.Caches
src/Snap.Hutao/Snap.Hutao/bin/
src/Snap.Hutao/Snap.Hutao/obj/
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
src/Snap.Hutao/Snap.Hutao/Snap.Hutao_TemporaryKey.pfx
src/Snap.Hutao/Snap.Hutao.Installer/bin/
src/Snap.Hutao/Snap.Hutao.Installer/obj/
src/Snap.Hutao/Snap.Hutao.Installer/Properties/PublishProfiles/FolderProfile.pubxml.user
src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/
src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/
src/Snap.Hutao/Snap.Hutao.Win32/bin/
src/Snap.Hutao/Snap.Hutao.Win32/obj/
src/Snap.Hutao/Snap.Hutao.Test/bin/
src/Snap.Hutao/Snap.Hutao.Test/obj/
src/Snap.Hutao/Snap.Hutao.Test/obj/
src/Snap.Hutao/Snap.Hutao/Properties/PublishProfiles/FolderProfile.pubxml.user

62
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,62 @@
stages:
- fetch
- release
Fetch:
stage: fetch
rules:
- if: $CI_COMMIT_TAG
tags:
- us3
script:
- apt-get update -qy
- apt-get install -y curl jq
- RELEASE_INFO=$(curl -sSL "https://api.github.com/repos/$CI_PROJECT_PATH/releases/latest")
- ASSET_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name | endswith(".msix")) | .browser_download_url')
- SHA256SUMS_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name == "SHA256SUMS") | .browser_download_url')
- curl -LJO "$ASSET_URL"
- curl -LJO "$SHA256SUMS_URL"
- FILE_NAME=$(basename "$ASSET_URL")
- SHA256SUMS_NAME=$(basename "$SHA256SUMS_URL")
- echo "File name at script stage is $FILE_NAME"
- echo "SHA256SUMS name at script stage is $SHA256SUMS_NAME"
- echo "THIS_FILE_NAME=$FILE_NAME" >> next.env
- echo "THIS_SHA256SUMS_NAME=$SHA256SUMS_NAME" >> next.env
after_script:
- echo "Current Job ID is $CI_JOB_ID"
- echo "THIS_JOB_ID=$CI_JOB_ID" >> next.env
artifacts:
paths:
- "*.msix"
- "SHA256SUMS"
expire_in: 180 days
reports:
dotenv: next.env
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG
needs:
- job: Fetch
artifacts: true
variables:
TAG: '$CI_COMMIT_TAG'
script:
- echo "Create Release $TAG"
- echo "$THIS_JOB_ID"
- echo "$THIS_FILE_NAME"
release:
name: '$TAG'
tag_name: '$TAG'
ref: '$TAG'
description: 'Release $TAG by CI'
assets:
links:
- name: "$THIS_FILE_NAME"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_FILE_NAME?inline=false"
link_type: package
- name: "$THIS_SHA256SUMS_NAME"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_SHA256SUMS_NAME?inline=false"
link_type: other

72
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,72 @@
# Contribution Guide
## Contribute Your Code
### Setup Snap.Hutao Project
1. Download and install [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)
2. Open Visual Studio Installer to complete Visual Studio installation
- You need to install `.NET desktop development`, `Desktop development with C++` and `Universal Windows Platform development` components
3. Install `Single-project MSIX Packaging Tools for VS 2022` provided by Microsoft in Visual Studio marketplace
4. Use git to clone the project `https://github.com/DGP-Studio/Snap.Hutao.git` to your local device
5. Switch git branch to `develop`
6. Open project solution with your Visual Studio and then you are ready to go
### Start Pull Request
- All code-related changes from authors' own branches are only allowed be merged to `develop` branch
- Please use [keywords](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests) to link your PR or commits with issues, so issues can be automatically closed once commits are merged into `main` branch.
### Test Binary Package
Once the code in updated in `develop` and `main` branches, an Azure Pipeline CI script will build the latest code to `Snap Hutao Alpha` package. Once the package is built, it will be released on [GitHub Release page](https://github.com/DGP-Studio/Snap.Hutao/releases) as a pre-released package.
You need to install [Snap.Hutao.CI.cer](https://github.com/DGP-Studio/Snap.Hutao/releases/download/2023.10.3.1/Snap.Hutao.CI.cer) certificate to your local machine, and then install the msix package in the release.
*If the latest release does not contains attached file, that means package is still in uploading process.
## Start New Issue
To help users solve problems faster and increase developers' efficiency in solving problems, Snap Hutao provides detailed documentation to explain common problems and issue templates to guide users to report program problems by submitting issues.
Before submitting a new issue, you should check the following pages:
- [FAQ](https://hut.ao/advanced/FAQ.html) Document
- [Common Program Exceptions ](https://hut.ao/en/advanced/exceptions.html)Document
- [Current Opened BUG Report Issues](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aissue+is%3Aopen+label%3ABUG)
When starting a new issue, please use the issue templates:
- Describe your issue in details to help developers to reproduce the issue
- Your description of reproduction should be a step-by-step story
- If your issue is about program crash
- Remember to provide your Device ID
- Check Windows Event Viewer, and attach associated `.NET Error` details in the issue body
## Document Modification
Snap Hutao Document site is stored in repository [DGP-Studio/Snap.Hutao.Docs](https://github.com/DGP-Studio/Snap.Hutao.Docs), you can process the following steps to test the site in your local device:
1. Download and install [NodeJS 18](https://nodejs.org/en/download/)
2. Clone the repository
3. Run `npm install` in the root directory of the document project
4. Run `npm run docs:dev` to start test on 8080 port
### Localization
Snap.Hutao.Docs project structure is designed as multiple languages site. Each language has its independent folder under `docs` directory.
**If you wish to add another language document, you can [start an issue in document repository](https://github.com/DGP-Studio/Snap.Hutao.Docs/issues) to ask developer to setup an environment for you, or you can process the following steps by yourself:**
1. make a copy of `zh` folder, rename the new folder as the new language's code
2. Start your translation work in the new language folder
3. In `docs/.vuepress/sidebar` folder, duplicate `zh.ts` file
1. Rename the file to `{language_code}.ts`
2. In the line 4, change `/zh/` to `/{language_code}/`
3. Translate all `text` field
4. In `docs/.vuepress/navbar` folder, duplicate `zh.ts` file
1. Rename the file to `{language_code}.ts`
2. Replace all `/zh/` to `/{language_code}/`
3. Translate all `text` field
5. In `docs/.vuepress/config.ts`file, add your language information in `locales` and `plugins/docsearchPlugin/locales` dictionary
6. In `docs/.vuepress/theme.ts`file, add your language information in `locales` dictionary

View File

@@ -1,8 +1,9 @@
![](res/HutaoRepoBanner2.png)
![Banner2-large](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/742129f4-f903-4d16-bf1e-3cbfad873ee4)
胡桃工具箱是一个 Windows 平台的开源的原神工具箱,旨在帮助玩家获得更好的游戏体验; 它是对官方移动端工具的一种非破坏性功能扩展,为不习惯在移动端进行原神游戏的 PC 玩家提供一个在 Windows 平台下获得接近移动端功能权利的途径
Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to provide a better gaming experience for players. It's an nondestructive feature extension from Genshin Impact's official mobile application, to provide similar feature on desktop, to allow PC gamers gain deserved benefits from mobile platforms.
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed for modern Windows platform to improve the gaming experience for desktop players. By combining existing official resources with new features designed by the development team, it provides a complete and useful set of tools without the need to rely on mobile devices. Snap Hutao does not take any destructive modification to the game client to ensure the security of the toolkit.
## 下载使用 / Download
@@ -44,7 +45,29 @@ 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)
## 赞助商 / Sponsorship
Snap Hutao is currently using sponsored software from the following service providers.
| [![](https://www.netlify.com/v3/img/components/netlify-light.svg)](https://www.netlify.com/) | [![](https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg)](https://crowdin.com/) | [![](https://gitlab.cn/images/icons/logos/logo-121-75.svg)](https://gitlab.cn/) |
|:----------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|
| [![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/73ae8b90-f3c7-4033-b2b7-f4126331ce66)](https://about.signpath.io) | [![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/49aed8ee-9f19-4a8a-998c-7b93ee286d65)](https://1password.com/) | [![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/ad121220-d2d3-4f49-b215-b6d063dc229d)](https://about.signpath.io) |
- Netlify provides document and home page hosting service for Snap Hutao
- Crowdin provides its SaaS platform to help Snap Hutao's localization
- Jihu GitLab (极狐) provides Git repository and CI/CD SaaS service for Snap Hutao in China
- Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
- 1Password provides Snap Hutao development team with their amazing password management software
- DigitalOcean provides reliable cloud database for Snap Hutao database backup
## 开发 / 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)
[![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)

20
appveyor.yml Normal file
View File

@@ -0,0 +1,20 @@
version: 1.0.{build}
branches:
only:
- "release"
build_cloud: HUTAO-SERVER
image: Visual Studio 2022
clone_depth: 3
clone_folder: D:\appveyor\project\Snap.Hutao.Project
install:
- pwsh: dotnet tool restore
build_script:
- pwsh: dotnet cake
artifacts:
- path: src/output/*.msix
type: file
deploy:
- provider: Webhook
url: https://app.signpath.io/API/v1/7a941fa3-64d8-4c45-bd03-92a02bcd4964/Integrations/AppVeyor?ProjectSlug=Snap.Hutao&SigningPolicySlug=release-signing&ArtifactConfigurationSlug=msix
authorization:
secure: j8srQ5/UYWhI+jlm3Vo3D3QfXoRyQ9hOn3ynJGtwusKui4+uDi4gykdUFYCITZxK+C/fOCAZNJ+YaKSm/OaiXw==

View File

@@ -7,30 +7,32 @@
# 5. Connect the GitHub in project settings
# 6. Run
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- README.md
- azure-pipelines.yml
- .github/ISSUE_TEMPLATE/*.yml
- .github/workflows/*.yml
- src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
pr:
branches:
include:
- main
paths:
exclude:
- README.md
- azure-pipelines.yml
- .github/ISSUE_TEMPLATE/*.yml
- .github/workflows/*.yml
- src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
trigger: none
pr: none
# trigger:
# branches:
# include:
# - main
# - develop
# paths:
# exclude:
# - README.md
# - azure-pipelines.yml
# - .github/ISSUE_TEMPLATE/*.yml
# - .github/workflows/*.yml
# - src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
# pr:
# branches:
# include:
# - main
# paths:
# exclude:
# - README.md
# - azure-pipelines.yml
# - .github/ISSUE_TEMPLATE/*.yml
# - .github/workflows/*.yml
# - src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
pool:
name: Default
@@ -42,150 +44,76 @@ variables:
project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
buildPlatform: 'x64'
buildConfiguration: 'Release'
build_date: $[ format('{0:yyyy}.{0:M}.{0:d}', pipeline.startTime) ]
steps:
- task: GetRevision@1
displayName: get Pipelines revision number
inputs:
VariableName: 'rev_number'
- task: UseDotNet@2
displayName: Install dotNet
inputs:
packageType: 'sdk'
version: '7.x'
version: '8.x'
includePreviewVersions: true
- task: NuGetToolInstaller@1
name: 'NuGetToolInstaller'
displayName: 'NuGet Installer'
- task: NuGetCommand@2
displayName: NuGet restore
inputs:
command: 'restore'
restoreSolution: '$(solution)'
feedsToUse: 'config'
nugetConfigPath: '$(Build.SourcesDirectory)/NuGet.Config'
- task: MsixPackaging@1
displayName: Build binary package
inputs:
outputPath: '$(Build.ArtifactStagingDirectory)/'
solution: '$(solution)'
clean: false
generateBundle: false
buildConfiguration: 'Release'
buildPlatform: 'x64'
updateAppVersion: false
appPackageDistributionMode: 'SideloadOnly'
msbuildLocationMethod: 'location'
msbuildLocation: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\MSBuild.exe'
- task: MagicChunks@2
inputs:
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\AppxManifest.xml'
fileType: 'Xml'
targetPathType: 'source'
transformationType: 'json'
transformations: |
{
"Package/Identity/@Name": "7f0db578-026f-4e0b-a75b-d5d06bb0a74c",
"Package/Identity/@Publisher": "CN=DGP Studio CI",
"Package/Identity/@Version": "$(build_date).$(rev_number)",
"Package/Properties/DisplayName": "胡桃 Alpha",
"Package/Properties/PublisherDisplayName":"DGP Studio CI",
"Package/Applications/Application/uap:VisualElements/@DisplayName": "胡桃 Alpha"
}
- task: CmdLine@2
displayName: Create resources folder
displayName: dotnet cake
inputs:
script: |
mkdir Assets
mkdir Resource
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64'
- task: CopyFiles@2
displayName: Copy Assets Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Assets'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\Assets'
- task: CopyFiles@2
displayName: Copy Resource Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Resource'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\Resource'
- task: CmdLine@2
displayName: Build MSIX
inputs:
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
script: dotnet tool restore && dotnet cake
- task: MsixSigning@1
name: signMsix
displayName: Sign MSIX package
inputs:
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(version).msix'
certificate: 'DGP_Studio_CI.pfx'
passwordVariable: 'pw'
condition: succeeded()
#- task: PublishPipelineArtifact@1
# displayName: 'Upload Output'
# inputs:
# targetPath: '$(Build.ArtifactStagingDirectory)/'
# artifact: 'Output'
# publishLocation: 'pipeline'
- task: DownloadSecureFile@1
name: cerFile
displayName: Download Root CA
inputs:
secureFile: 'Snap.Hutao.CI.cer'
- task: GitHubRelease@1
- task: PublishPipelineArtifact@1
inputs:
gitHubConnection: 'github.com_Masterain'
repositoryName: 'DGP-Studio/Snap.Hutao'
action: 'create'
target: '$(Build.SourceVersion)'
tagSource: 'userSpecifiedTag'
tag: '$(build_date).$(rev_number)'
title: '$(build_date).$(rev_number)'
releaseNotesSource: 'inline'
releaseNotesInline: |
## 普通用户请勿下载
该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'Snap.Hutao.Alpha-$(version).msix'
publishLocation: 'pipeline'
普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
assets: |
$(Build.ArtifactStagingDirectory)/*
$(cerFile.secureFilePath)
isPreRelease: true
changeLogCompareToRelease: 'lastFullRelease'
changeLogType: 'commitBased'
#- task: GitHubRelease@1
# inputs:
# gitHubConnection: 'github.com_Masterain'
# repositoryName: 'DGP-Automation/Hutao-Auto-Release'
# action: 'create'
# target: '$(Build.SourceVersion)'
# tagSource: 'userSpecifiedTag'
# tag: '$(version)'
# title: '$(version)'
# releaseNotesSource: 'inline'
# releaseNotesInline: |
# ## 普通用户请勿下载
# 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
#
# 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
#
# assets: |
# $(Build.ArtifactStagingDirectory)/*
# $(cerFile.secureFilePath)
# isPreRelease: true
# changeLogCompareToRelease: 'lastFullRelease'
# changeLogType: 'commitBased'
- task: rclone@1
displayName: Upload CI via Rclone
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
inputs:
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix downloadDGPCN:/releases/Alpha/'
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(version).msix downloadDGPCN:/releases/Alpha/'
configPath: 'C:\agent\_work\_tasks\rclone.conf'
- task: rclone@1
displayName: Upload PR CI via Rclone
condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
inputs:
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix downloadDGPCN:/releases/PR/'
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(version).msix downloadDGPCN:/releases/PR/'
configPath: 'C:\agent\_work\_tasks\rclone.conf'

199
build.cake Normal file
View File

@@ -0,0 +1,199 @@
#tool "nuget:?package=nuget.commandline&version=6.5.0"
#addin nuget:?package=Cake.Http&version=3.0.2
var target = Argument("target", "Build");
var configuration = Argument("configuration", "Release");
// Pre-define
var version = "version";
var repoDir = "repoDir";
var outputPath = "outputPath";
string solution
{
get => System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao.sln");
}
string project
{
get => System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao", "Snap.Hutao.csproj");
}
string binPath
{
get => System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao", "bin", "x64", "Release", "net8.0-windows10.0.22621.0", "win-x64");
}
string manifest
{
get => System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao", "Package.appxmanifest");
}
if (AzurePipelines.IsRunningOnAzurePipelines)
{
repoDir = AzurePipelines.Environment.Build.SourcesDirectory.FullPath;
outputPath = AzurePipelines.Environment.Build.ArtifactStagingDirectory.FullPath;
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
version = HttpGet(
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
new HttpSettings
{
Headers = new Dictionary<string, string>
{
{ "Authorization", versionAuth }
}
}
);
Information($"Version: {version}");
AzurePipelines.Commands.SetVariable("version", version);
}
else if (GitHubActions.IsRunningOnGitHubActions)
{
repoDir = GitHubActions.Environment.Workflow.Workspace.FullPath;
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
version = HttpGet(
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
new HttpSettings
{
Headers = new Dictionary<string, string>
{
{ "Authorization", versionAuth }
}
}
);
Information($"Version: {version}");
GitHubActions.Commands.SetOutputParameter("version", version);
}
else if (AppVeyor.IsRunningOnAppVeyor)
{
repoDir = AppVeyor.Environment.Build.Folder;
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
version = XmlPeek(manifest, "appx:Package/appx:Identity/@Version", new XmlPeekSettings
{
Namespaces = new Dictionary<string, string> { { "appx", "http://schemas.microsoft.com/appx/manifest/foundation/windows10" } }
})[..^2];
Information($"Version: {version}");
}
Task("Build")
.IsDependentOn("Build binary package")
.IsDependentOn("Copy files")
.IsDependentOn("Build MSIX");
Task("NuGet Restore")
.Does(() =>
{
Information("Restoring packages...");
var nugetConfig = System.IO.Path.Combine(repoDir, "NuGet.Config");
DotNetRestore(project, new DotNetRestoreSettings
{
Verbosity = DotNetVerbosity.Detailed,
Interactive = false,
ConfigFile = nugetConfig
});
});
Task("Generate AppxManifest")
.Does(() =>
{
Information("Generating AppxManifest...");
var content = System.IO.File.ReadAllText(manifest);
if (AzurePipelines.IsRunningOnAzurePipelines || GitHubActions.IsRunningOnGitHubActions)
{
Information("Using CI configuraion");
content = content
.Replace("Snap Hutao", "Snap Hutao Alpha")
.Replace("胡桃", "胡桃 Alpha")
.Replace("DGP Studio", "DGP Studio CI");
content = System.Text.RegularExpressions.Regex.Replace(content, " Name=\"([^\"]*)\"", " Name=\"7f0db578-026f-4e0b-a75b-d5d06bb0a74c\"");
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"E=admin@dgp-studio.cn, CN=DGP Studio CI, OU=CI, O=DGP-Studio, L=San Jose, S=CA, C=US\"");
content = System.Text.RegularExpressions.Regex.Replace(content, " Version=\"([0-9\\.]+)\"", $" Version=\"{version}\"");
}
else if (AppVeyor.IsRunningOnAppVeyor)
{
Information("Using Release configuration");
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US\"");
}
System.IO.File.WriteAllText(manifest, content);
Information("Generated.");
});
Task("Build binary package")
.IsDependentOn("NuGet Restore")
.IsDependentOn("Generate AppxManifest")
.Does(() =>
{
Information("Building binary package...");
var settings = new DotNetBuildSettings
{
Configuration = configuration
};
settings.MSBuildSettings = new DotNetMSBuildSettings
{
ArgumentCustomization = args => args.Append("/p:Platform=x64")
.Append("/p:UapAppxPackageBuildMode=SideloadOnly")
.Append("/p:AppxPackageSigningEnabled=false")
.Append("/p:AppxBundle=Never")
.Append("/p:AppxPackageOutput=" + outputPath)
};
DotNetBuild(project, settings);
});
Task("Copy files")
.IsDependentOn("Build binary package")
.Does(() =>
{
Information("Copying assets...");
CopyDirectory(
System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao", "Assets"),
System.IO.Path.Combine(binPath, "Assets")
);
Information("Copying resource...");
CopyDirectory(
System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao", "Resource"),
System.IO.Path.Combine(binPath, "Resource")
);
});
Task("Build MSIX")
.IsDependentOn("Build binary package")
.IsDependentOn("Copy files")
.Does(() =>
{
var arguments = "arguments";
if (AzurePipelines.IsRunningOnAzurePipelines || GitHubActions.IsRunningOnGitHubActions)
{
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao.Alpha-{version}.msix");
}
else if (AppVeyor.IsRunningOnAppVeyor)
{
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao-{version}.msix");
}
var p = StartProcess(
"makeappx.exe",
new ProcessSettings
{
Arguments = arguments
}
);
if (p != 0)
{
throw new InvalidOperationException("Build failed with exit code " + p);
}
});
RunTarget(target);

View File

@@ -108,7 +108,9 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_diagnostic.SA1629.severity = none
dotnet_diagnostic.SA1642.severity = none
dotnet_diagnostic.IDE0005.severity = warning
dotnet_diagnostic.IDE0060.severity = none
dotnet_diagnostic.IDE0290.severity = none
# SA1208: System using directives should be placed before other using directives
dotnet_diagnostic.SA1208.severity = none
@@ -322,6 +324,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
csharp_style_prefer_primary_constructors = true:suggestion
dotnet_diagnostic.SA1010.severity = none
[*.vb]
#### 命名样式 ####

View File

@@ -1,147 +0,0 @@
// 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

@@ -1,98 +0,0 @@
// 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.Immutable;
using System.Linq;
using System.Threading;
namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class CommandGenerator : IIncrementalGenerator
{
public const string AttributeName = "Snap.Hutao.Core.Annotation.CommandAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2<IMethodSymbol>>> commands =
context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedMethods, CommandMethod)
.Where(GeneratorSyntaxContext2<IMethodSymbol>.NotNull)
.Collect();
context.RegisterImplementationSourceOutput(commands, GenerateCommandImplementations);
}
private static bool FilterAttributedMethods(SyntaxNode node, CancellationToken token)
{
return node is MethodDeclarationSyntax methodDeclarationSyntax
&& methodDeclarationSyntax.Parent is ClassDeclarationSyntax classDeclarationSyntax
&& classDeclarationSyntax.Modifiers.Count > 1
&& methodDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2<IMethodSymbol> CommandMethod(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.TryGetDeclaredSymbol(token, out IMethodSymbol? methodSymbol))
{
ImmutableArray<AttributeData> attributes = methodSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, methodSymbol, attributes);
}
}
return default;
}
private static void GenerateCommandImplementations(SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2<IMethodSymbol>> context2s)
{
foreach (GeneratorSyntaxContext2<IMethodSymbol> context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString()))
{
GenerateCommandImplementation(production, context2);
}
}
private static void GenerateCommandImplementation(SourceProductionContext production, GeneratorSyntaxContext2<IMethodSymbol> context2)
{
INamedTypeSymbol classSymbol = context2.Symbol.ContainingType;
AttributeData commandInfo = context2.SingleAttribute(AttributeName);
string commandName = (string)commandInfo.ConstructorArguments[0].Value!;
string commandType = context2.Symbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task")
? "AsyncRelayCommand"
: "RelayCommand";
string genericParameter = context2.Symbol.Parameters.ElementAtOrDefault(0) is IParameterSymbol parameter
? $"<{parameter.Type.ToDisplayString(SymbolDisplayFormats.FullyQualifiedNonNullableFormat)}>"
: string.Empty;
string concurrentExecution = commandInfo.HasNamedArgumentWith<bool>("AllowConcurrentExecutions", value => value)
? ", AsyncRelayCommandOptions.AllowConcurrentExecutions"
: string.Empty;
string className = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
string code = $$"""
using CommunityToolkit.Mvvm.Input;
namespace {{classSymbol.ContainingNamespace}};
partial class {{className}}
{
private ICommand _{{commandName}};
public ICommand {{commandName}}
{
get => _{{commandName}} ??= new {{commandType}}{{genericParameter}}({{context2.Symbol.Name}}{{concurrentExecution}});
}
}
""";
string normalizedClassName = classSymbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.{commandName}.g.cs", code);
}
}

View File

@@ -1,193 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
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)
{
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> injectionClasses =
context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, ConstructorGeneratedClass)
.Where(GeneratorSyntaxContext2.NotNull)
.Collect();
context.RegisterSourceOutput(injectionClasses, GenerateConstructorImplementations);
}
private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
return node is ClassDeclarationSyntax classDeclarationSyntax
&& classDeclarationSyntax.Modifiers.Count > 1
&& classDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2 ConstructorGeneratedClass(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? classSymbol))
{
ImmutableArray<AttributeData> attributes = classSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, classSymbol, attributes);
}
}
return default;
}
private static void GenerateConstructorImplementations(SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> context2s)
{
foreach (GeneratorSyntaxContext2 context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString()))
{
GenerateConstructorImplementation(production, context2);
}
}
private static void GenerateConstructorImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2)
{
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, 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.ToDisplayString(SymbolDisplayFormats.QualifiedNonNullableFormat)}}
{
public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}}){{(options.CallBaseConstructor ? " : base(serviceProvider)" : string.Empty)}}
{
""");
FillUpWithFieldValueAssignment(sourceBuilder, context2, options);
sourceBuilder.Append("""
}
}
""");
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)
{
IEnumerable<IFieldSymbol> fields = context2.Symbol.GetMembers()
.Where(m => m.Kind == SymbolKind.Field)
.OfType<IFieldSymbol>();
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())
{
case "System.IServiceProvider":
builder
.Append(" this.")
.Append(fieldSymbol.Name)
.AppendLine(" = serviceProvider;");
break;
case "System.Net.Http.HttpClient":
if (options.ResolveHttpClient)
{
builder
.Append(" this.")
.Append(fieldSymbol.Name)
.AppendLine(" = httpClient;");
}
else
{
builder
.Append(" this.")
.Append(fieldSymbol.Name)
.Append(" = serviceProvider.GetRequiredService<System.Net.Http.IHttpClientFactory>().CreateClient(nameof(")
.Append(context2.Symbol.Name)
.AppendLine("));");
}
break;
default:
builder
.Append(" this.")
.Append(fieldSymbol.Name)
.Append(" = serviceProvider.GetRequiredService<")
.Append(fieldSymbol.Type)
.AppendLine(">();");
break;
}
}
}
foreach (INamedTypeSymbol interfaceSymbol in context2.Symbol.Interfaces)
{
if (interfaceSymbol.Name == "IRecipient")
{
builder
.Append(" CommunityToolkit.Mvvm.Messaging.IMessengerExtensions.Register<")
.Append(interfaceSymbol.TypeArguments[0])
.AppendLine(">(serviceProvider.GetRequiredService<CommunityToolkit.Mvvm.Messaging.IMessenger>(), this);");
}
}
}
private readonly struct FieldValueAssignmentOptions
{
public readonly bool ResolveHttpClient;
public readonly bool CallBaseConstructor;
public FieldValueAssignmentOptions(bool resolveHttpClient, bool callBaseConstructor)
{
ResolveHttpClient = resolveHttpClient;
CallBaseConstructor = callBaseConstructor;
}
}
}

View File

@@ -1,149 +0,0 @@
// 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

@@ -1,61 +0,0 @@
// 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

@@ -1,18 +0,0 @@
namespace System.Diagnostics.CodeAnalysis;
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <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;
}
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}

View File

@@ -1,152 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// 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.DependencyInjection;
[Generator(LanguageNames.CSharp)]
internal sealed class HttpClientGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute";
private const string HttpClientConfiguration = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.";
private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute";
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
.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
.Where(GeneratorSyntaxContext2.NotNull)
.Collect();
context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddHttpClientsImplementation);
}
private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
return node is ClassDeclarationSyntax classDeclarationSyntax
&& classDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2 HttpClientClass(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? classSymbol))
{
ImmutableArray<AttributeData> attributes = classSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, classSymbol, attributes);
}
}
return default;
}
private static void GenerateAddHttpClientsImplementation(SourceProductionContext context, ImmutableArray<GeneratorSyntaxContext2> context2s)
{
StringBuilder sourceBuilder = new StringBuilder().Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Net.Http;
namespace Snap.Hutao.Core.DependencyInjection;
internal static partial class IocHttpClientConfiguration
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(HttpClientGenerator)}}", "1.0.0.0")]
public static partial IServiceCollection AddHttpClients(this IServiceCollection services)
{
""");
FillUpWithAddHttpClient(sourceBuilder, context, context2s);
sourceBuilder.Append("""
return services;
}
}
""");
context.AddSource("IocHttpClientConfiguration.g.cs", sourceBuilder.ToString());
}
private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
{
List<string> lines = new();
StringBuilder lineBuilder = new();
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<");
AttributeData httpClientData = context.SingleAttribute(AttributeName);
ImmutableArray<TypedConstant> arguments = httpClientData.ConstructorArguments;
if (arguments.Length == 2)
{
lineBuilder.Append($"{arguments[1].Value}, ");
}
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>(");
lineBuilder.Append(arguments[0].ToCSharpString().Substring(HttpClientConfiguration.Length)).Append("Configuration)");
if (context.SingleOrDefaultAttribute(PrimaryHttpMessageHandlerAttributeName) is AttributeData handlerData)
{
ImmutableArray<KeyValuePair<string, TypedConstant>> properties = handlerData.NamedArguments;
lineBuilder.Append(@".ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() {");
foreach (KeyValuePair<string, TypedConstant> property in properties)
{
lineBuilder.Append(' ');
lineBuilder.Append(property.Key);
lineBuilder.Append(" = ");
lineBuilder.Append(property.Value.ToCSharpString());
lineBuilder.Append(',');
}
lineBuilder.Append(" })");
}
if (context.HasAttributeWithName(UseDynamicSecretAttributeName))
{
lineBuilder.Append(".AddHttpMessageHandler<DynamicSecretHandler>()");
}
lineBuilder.Append(';');
lines.Add(lineBuilder.ToString());
}
foreach (string line in lines.OrderBy(x => x))
{
sourceBuilder.Append(line);
}
}
}

View File

@@ -1,126 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// 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.DependencyInjection;
[Generator(LanguageNames.CSharp)]
internal sealed class InjectionGenerator : IIncrementalGenerator
{
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);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> injectionClasses = context.SyntaxProvider
.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
.Where(GeneratorSyntaxContext2.NotNull)
.Collect();
context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddInjectionsImplementation);
}
private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
return node is ClassDeclarationSyntax classDeclarationSyntax
&& classDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2 HttpClientClass(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? classSymbol))
{
ImmutableArray<AttributeData> attributes = classSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, classSymbol, attributes);
}
}
return default;
}
private static void GenerateAddInjectionsImplementation(SourceProductionContext context, ImmutableArray<GeneratorSyntaxContext2> context2s)
{
StringBuilder sourceBuilder = new StringBuilder().Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.DependencyInjection;
internal static partial class ServiceCollectionExtension
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(InjectionGenerator)}}", "1.0.0.0")]
public static partial IServiceCollection AddInjections(this IServiceCollection services)
{
""");
FillUpWithAddServices(sourceBuilder, context, context2s);
sourceBuilder.Append("""
return services;
}
}
""");
context.AddSource("ServiceCollectionExtension.g.cs", sourceBuilder.ToString());
}
private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
{
List<string> lines = new();
StringBuilder lineBuilder = new();
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
{
lineBuilder.Clear().AppendLine();
AttributeData injectionInfo = context.SingleAttribute(AttributeName);
ImmutableArray<TypedConstant> arguments = injectionInfo.ConstructorArguments;
string injectAsName = arguments[0].ToCSharpString();
switch (injectAsName)
{
case InjectAsSingletonName:
lineBuilder.Append(" services.AddSingleton<");
break;
case InjectAsTransientName:
lineBuilder.Append(" services.AddTransient<");
break;
case InjectAsScopedName:
lineBuilder.Append(" services.AddScoped<");
break;
default:
production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, context.Context.Node.GetLocation(), injectAsName));
break;
}
if (arguments.Length == 2)
{
lineBuilder.Append($"{arguments[1].Value}, ");
}
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();");
lines.Add(lineBuilder.ToString());
}
foreach (string line in lines.OrderBy(x => x))
{
sourceBuilder.Append(line);
}
}
}

View File

@@ -1,78 +0,0 @@
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

@@ -1,134 +0,0 @@
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.Text;
using System.Threading;
namespace Snap.Hutao.SourceGeneration.Enum;
[Generator(LanguageNames.CSharp)]
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
.CreateSyntaxProvider(FilterAttributedEnums, LocalizationEnum)
.Where(GeneratorSyntaxContext2.NotNull);
context.RegisterSourceOutput(localizationEnums, GenerateGetLocalizedDescriptionImplementation);
}
private static bool FilterAttributedEnums(SyntaxNode node, CancellationToken token)
{
return node is EnumDeclarationSyntax enumDeclarationSyntax
&& enumDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2 LocalizationEnum(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.SemanticModel.GetDeclaredSymbol(context.Node, token) is INamedTypeSymbol enumSymbol)
{
ImmutableArray<AttributeData> attributes = enumSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, enumSymbol, attributes);
}
}
return default;
}
private static void GenerateGetLocalizedDescriptionImplementation(SourceProductionContext context, GeneratorSyntaxContext2 context2)
{
StringBuilder sourceBuilder = new StringBuilder().Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Resource.Localization;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(LocalizedEnumGenerator)}}", "1.0.0.0")]
internal static class {{context2.Symbol.Name}}Extension
{
/// <summary>
/// 获取本地化的描述
/// </summary>
/// <param name="value">枚举值</param>
/// <returns>本地化的描述</returns>
public static string GetLocalizedDescription(this {{context2.Symbol}} value)
{
string key = value switch
{
""");
FillUpWithSwitchBranches(sourceBuilder, context2);
sourceBuilder.Append($$"""
_ => string.Empty,
};
if (string.IsNullOrEmpty(key))
{
return Enum.GetName(value);
}
else
{
return SH.ResourceManager.GetString(key);
}
}
/// <summary>
/// 获取本地化的描述
/// </summary>
/// <param name="value">枚举值</param>
/// <returns>本地化的描述</returns>
[return:MaybeNull]
public static string GetLocalizedDescriptionOrDefault(this {{context2.Symbol}} value)
{
string key = value switch
{
""");
FillUpWithSwitchBranches(sourceBuilder, context2);
sourceBuilder.Append($$"""
_ => string.Empty,
};
return SH.ResourceManager.GetString(key);
}
}
""");
context.AddSource($"{context2.Symbol.Name}Extension.g.cs", sourceBuilder.ToString());
}
private static void FillUpWithSwitchBranches(StringBuilder sourceBuilder, GeneratorSyntaxContext2 context)
{
IEnumerable<IFieldSymbol> fields = context.Symbol.GetMembers()
.Where(m => m.Kind == SymbolKind.Field)
.Cast<IFieldSymbol>();
foreach (IFieldSymbol fieldSymbol in fields)
{
AttributeData? localizationKeyInfo = fieldSymbol.GetAttributes()
.SingleOrDefault(data => data.AttributeClass!.ToDisplayString() == LocalizationKeyName);
if (localizationKeyInfo != null)
{
sourceBuilder
.Append(" ")
.Append(fieldSymbol)
.Append(" => \"")
.Append(localizationKeyInfo.ConstructorArguments[0].Value)
.AppendLine("\",");
}
}
}
}

View File

@@ -1,8 +0,0 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("", "RS2008")]

View File

@@ -1,202 +0,0 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
namespace Snap.Hutao.SourceGeneration.Identity;
[Generator(LanguageNames.CSharp)]
internal sealed class IdentityGenerator : IIncrementalGenerator
{
private const string FileName = "IdentityStructs.json";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<AdditionalText>> provider = context.AdditionalTextsProvider.Where(MatchFileName).Collect();
context.RegisterImplementationSourceOutput(provider, GenerateIdentityStructs);
}
private static bool MatchFileName(AdditionalText text)
{
return Path.GetFileName(text.Path) == FileName;
}
private static void GenerateIdentityStructs(SourceProductionContext context, ImmutableArray<AdditionalText> texts)
{
AdditionalText jsonFile = texts.Single();
string identityJson = jsonFile.GetText(context.CancellationToken)!.ToString();
List<IdentityStructMetadata> identities = identityJson.FromJson<List<IdentityStructMetadata>>()!;
if (identities.Any())
{
foreach (IdentityStructMetadata identityStruct in identities)
{
GenerateIdentityStruct(context, identityStruct);
}
}
}
private static void GenerateIdentityStruct(SourceProductionContext context, IdentityStructMetadata metadata)
{
string name = metadata.Name;
StringBuilder sourceBuilder = new StringBuilder().AppendLine($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive.Converter;
using System.Numerics;
namespace Snap.Hutao.Model.Primitive;
/// <summary>
/// {{metadata.Documentation}}
/// </summary>
[JsonConverter(typeof(IdentityConverter<{{name}}>))]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(IdentityGenerator)}}","1.0.0.0")]
internal readonly partial struct {{name}}
{
/// <summary>
/// 值
/// </summary>
public readonly uint Value;
/// <summary>
/// Initializes a new instance of the <see cref="{{name}}"/> struct.
/// </summary>
/// <param name="value">value</param>
public {{name}}(uint value)
{
Value = value;
}
public static implicit operator uint({{name}} value)
{
return value.Value;
}
public static implicit operator {{name}}(uint value)
{
return new(value);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return Value.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
return Value.ToString();
}
}
""");
if (metadata.Equatable)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IEquatable<{{name}}>
{
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is {{name}} other && Equals(other);
}
/// <inheritdoc/>
public bool Equals({{name}} other)
{
return Value == other.Value;
}
}
""");
}
if (metadata.EqualityOperators)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IEqualityOperators<{{name}}, {{name}}, bool>, IEqualityOperators<{{name}}, uint, bool>
{
public static bool operator ==({{name}} left, {{name}} right)
{
return left.Value == right.Value;
}
public static bool operator ==({{name}} left, uint right)
{
return left.Value == right;
}
public static bool operator !=({{name}} left, {{name}} right)
{
return !(left == right);
}
public static bool operator !=({{name}} left, uint right)
{
return !(left == right);
}
}
""");
}
if (metadata.AdditionOperators)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IAdditionOperators<{{name}}, {{name}}, {{name}}>, IAdditionOperators<{{name}}, uint, {{name}}>
{
public static {{name}} operator +({{name}} left, {{name}} right)
{
return left.Value + right.Value;
}
public static {{name}} operator +({{name}} left, uint right)
{
return left.Value + right;
}
}
""");
}
if (metadata.IncrementOperators)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IIncrementOperators<{{name}}>
{
public static unsafe {{name}} operator ++({{name}} value)
{
++*(uint*)&value;
return value;
}
}
""");
}
context.AddSource($"{name}.g.cs", sourceBuilder.ToString());
}
private sealed class IdentityStructMetadata
{
public string Name { get; set; } = default!;
public string? Documentation { get; set; }
public bool Equatable { get; set; }
public bool EqualityOperators { get; set; }
public bool AdditionOperators { get; set; }
public bool IncrementOperators { get; set; }
}
}

View File

@@ -1,439 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
namespace Snap.Hutao.SourceGeneration;
// Really simple JSON parser in ~300 lines
// - Attempts to parse JSON files with minimal GC allocation
// - Nice and simple "[1,2,3]".FromJson<List<int>>() API
// - Classes and structs can be parsed too!
// class Foo { public int Value; }
// "{\"Value\":10}".FromJson<Foo>()
// - Can parse JSON without type information into Dictionary<string,object> and List<object> e.g.
// "[1,2,3]".FromJson<object>().GetType() == typeof(List<object>)
// "{\"Value\":10}".FromJson<object>().GetType() == typeof(Dictionary<string,object>)
// - No JIT Emit support to support AOT compilation on iOS
// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead.
// - Only public fields and property setters on classes/structs will be written to
//
// Limitations:
// - No JIT Emit support to parse structures quickly
// - Limited to parsing <2GB JSON files (due to int.MaxValue)
// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception.
public static class JsonParser
{
[ThreadStatic]
private static Stack<List<string>>? splitArrayPool;
[ThreadStatic]
private static StringBuilder? stringBuilder;
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, FieldInfo>>? fieldInfoCache;
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, PropertyInfo>>? propertyInfoCache;
public static T? FromJson<T>(this string json)
{
// Initialize, if needed, the ThreadStatic variables
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
stringBuilder ??= new StringBuilder();
splitArrayPool ??= new Stack<List<string>>();
// Remove all whitespace not within strings to make parsing simpler
stringBuilder.Length = 0;
for (int i = 0; i < json.Length; i++)
{
char c = json[i];
if (c == '"')
{
i = AppendUntilStringEnd(true, i, json);
continue;
}
if (char.IsWhiteSpace(c))
{
continue;
}
stringBuilder.Append(c);
}
// Parse the thing!
return (T?)ParseValue(typeof(T), stringBuilder.ToString());
}
private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
{
stringBuilder!.Append(json[startIdx]);
for (int i = startIdx + 1; i < json.Length; i++)
{
if (json[i] == '\\')
{
if (appendEscapeCharacter)
{
stringBuilder.Append(json[i]);
}
stringBuilder.Append(json[i + 1]);
i++;//Skip next character as it is escaped
}
else if (json[i] == '"')
{
stringBuilder.Append(json[i]);
return i;
}
else
{
stringBuilder.Append(json[i]);
}
}
return json.Length - 1;
}
// Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
private static List<string> Split(string json)
{
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List<string>();
splitArray.Clear();
if (json.Length == 2)
{
return splitArray;
}
int parseDepth = 0;
stringBuilder!.Length = 0;
for (int i = 1; i < json.Length - 1; i++)
{
switch (json[i])
{
case '[':
case '{':
parseDepth++;
break;
case ']':
case '}':
parseDepth--;
break;
case '"':
i = AppendUntilStringEnd(true, i, json);
continue;
case ',':
case ':':
if (parseDepth == 0)
{
splitArray.Add(stringBuilder.ToString());
stringBuilder.Length = 0;
continue;
}
break;
}
stringBuilder.Append(json[i]);
}
splitArray.Add(stringBuilder.ToString());
return splitArray;
}
internal static object? ParseValue(Type type, string json)
{
if (type == typeof(string))
{
if (json.Length <= 2)
{
return string.Empty;
}
StringBuilder parseStringBuilder = new(json.Length);
for (int i = 1; i < json.Length - 1; ++i)
{
if (json[i] == '\\' && i + 1 < json.Length - 1)
{
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
if (j >= 0)
{
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
++i;
continue;
}
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
{
if (uint.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c))
{
parseStringBuilder.Append((char)c);
i += 5;
continue;
}
}
}
parseStringBuilder.Append(json[i]);
}
return parseStringBuilder.ToString();
}
if (type.IsPrimitive)
{
object result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
return result;
}
if (type == typeof(decimal))
{
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result);
return result;
}
if (type == typeof(DateTime))
{
DateTime.TryParse(json.Replace("\"", ""), System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime result);
return result;
}
if (json == "null")
{
return null;
}
if (type.IsEnum)
{
if (json[0] == '"')
{
json = json.Substring(1, json.Length - 2);
}
try
{
return System.Enum.Parse(type, json, false);
}
catch
{
return 0;
}
}
if (type.IsArray)
{
Type arrayType = type.GetElementType();
if (json[0] != '[' || json[json.Length - 1] != ']')
{
return null;
}
List<string> elems = Split(json);
Array newArray = Array.CreateInstance(arrayType, elems.Count);
for (int i = 0; i < elems.Count; i++)
{
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
}
splitArrayPool!.Push(elems);
return newArray;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
Type listType = type.GetGenericArguments()[0];
if (json[0] != '[' || json[json.Length - 1] != ']')
{
return null;
}
List<string> elems = Split(json);
IList list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
for (int i = 0; i < elems.Count; i++)
{
list.Add(ParseValue(listType, elems[i]));
}
splitArrayPool!.Push(elems);
return list;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
Type keyType, valueType;
{
Type[] args = type.GetGenericArguments();
keyType = args[0];
valueType = args[1];
}
//Refuse to parse dictionary keys that aren't of type string
if (keyType != typeof(string))
{
return null;
}
//Must be a valid dictionary element
if (json[0] != '{' || json[json.Length - 1] != '}')
{
return null;
}
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return null;
}
IDictionary dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
{
continue;
}
string keyValue = elems[i].Substring(1, elems[i].Length - 2);
object? val = ParseValue(valueType, elems[i + 1]);
dictionary[keyValue] = val;
}
return dictionary;
}
if (type == typeof(object))
{
return ParseAnonymousValue(json);
}
if (json[0] == '{' && json[json.Length - 1] == '}')
{
return ParseObject(type, json);
}
return null;
}
private static object? ParseAnonymousValue(string json)
{
if (json.Length == 0)
{
return null;
}
if (json[0] == '{' && json[json.Length - 1] == '}')
{
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return null;
}
Dictionary<string, object?> dict = new(elems.Count / 2);
for (int i = 0; i < elems.Count; i += 2)
{
dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]);
}
return dict;
}
if (json[0] == '[' && json[json.Length - 1] == ']')
{
List<string> items = Split(json);
List<object?> finalList = new(items.Count);
for (int i = 0; i < items.Count; i++)
{
finalList.Add(ParseAnonymousValue(items[i]));
}
return finalList;
}
if (json[0] == '"' && json[json.Length - 1] == '"')
{
string str = json.Substring(1, json.Length - 2);
return str.Replace("\\", string.Empty);
}
if (char.IsDigit(json[0]) || json[0] == '-')
{
if (json.Contains("."))
{
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result);
return result;
}
else
{
int.TryParse(json, out int result);
return result;
}
}
if (json == "true")
{
return true;
}
if (json == "false")
{
return false;
}
// handles json == "null" as well as invalid JSON
return null;
}
private static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
{
Dictionary<string, T> nameToMember = new(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < members.Length; i++)
{
T member = members[i];
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
{
continue;
}
string name = member.Name;
if (member.IsDefined(typeof(DataMemberAttribute), true))
{
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
{
name = dataMemberAttribute.Name;
}
}
nameToMember.Add(name, member);
}
return nameToMember;
}
private static object ParseObject(Type type, string json)
{
object instance = FormatterServices.GetUninitializedObject(type);
// The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return instance;
}
if (!fieldInfoCache!.TryGetValue(type, out Dictionary<string, FieldInfo> nameToField))
{
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
fieldInfoCache.Add(type, nameToField);
}
if (!propertyInfoCache!.TryGetValue(type, out Dictionary<string, PropertyInfo> nameToProperty))
{
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
propertyInfoCache.Add(type, nameToProperty);
}
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
{
continue;
}
string key = elems[i].Substring(1, elems[i].Length - 2);
string value = elems[i + 1];
if (nameToField.TryGetValue(key, out FieldInfo fieldInfo))
{
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
}
else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
}
}
return instance;
}
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using System;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.Primitive;
internal static class AttributeDataExtension
{
public static bool HasNamedArgumentWith<TValue>(this AttributeData data, string key, Func<TValue, bool> predicate)
{
return data.NamedArguments.Any(a => a.Key == key && predicate((TValue)a.Value.Value!));
}
}

View File

@@ -1,41 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.Primitive;
internal static class EnumerableExtension
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return DistinctByIterator(source, keySelector);
}
private static IEnumerable<TSource> DistinctByIterator<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
using IEnumerator<TSource> enumerator = source.GetEnumerator();
if (enumerator.MoveNext())
{
HashSet<TKey> set = new();
do
{
TSource element = enumerator.Current;
if (set.Add(keySelector(element)))
{
yield return element;
}
}
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

@@ -1,77 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.Primitive;
internal readonly struct GeneratorSyntaxContext2
{
public readonly GeneratorSyntaxContext Context;
public readonly INamedTypeSymbol Symbol;
public readonly ImmutableArray<AttributeData> Attributes;
public readonly bool HasValue = false;
public GeneratorSyntaxContext2(GeneratorSyntaxContext context, INamedTypeSymbol symbol, ImmutableArray<AttributeData> attributes)
{
Context = context;
Symbol = symbol;
Attributes = attributes;
HasValue = true;
}
public static bool NotNull(GeneratorSyntaxContext2 context)
{
return context.HasValue;
}
public bool HasAttributeWithName(string name)
{
return Attributes.Any(attr => attr.AttributeClass!.ToDisplayString() == name);
}
public AttributeData SingleAttribute(string name)
{
return Attributes.Single(attribute => attribute.AttributeClass!.ToDisplayString() == name);
}
public AttributeData? SingleOrDefaultAttribute(string name)
{
return Attributes.SingleOrDefault(attribute => attribute.AttributeClass!.ToDisplayString() == name);
}
public TSyntaxNode Node<TSyntaxNode>()
where TSyntaxNode : SyntaxNode
{
return (TSyntaxNode)Context.Node;
}
}
internal readonly struct GeneratorSyntaxContext2<TSymbol>
where TSymbol : ISymbol
{
public readonly GeneratorSyntaxContext Context;
public readonly TSymbol Symbol;
public readonly ImmutableArray<AttributeData> Attributes;
public readonly bool HasValue = false;
public GeneratorSyntaxContext2(GeneratorSyntaxContext context, TSymbol symbol, ImmutableArray<AttributeData> attributes)
{
Context = context;
Symbol = symbol;
Attributes = attributes;
HasValue = true;
}
public static bool NotNull(GeneratorSyntaxContext2<TSymbol> context)
{
return context.HasValue;
}
public AttributeData SingleAttribute(string name)
{
return Attributes.Single(attribute => attribute.AttributeClass!.ToDisplayString() == name);
}
}

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Diagnostics.CodeAnalysis;
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)
where TSymbol : class, ISymbol
{
symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, token) as TSymbol;
return symbol != null;
}
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
namespace Snap.Hutao.SourceGeneration.Primitive;
internal static class SymbolDisplayFormats
{
public static SymbolDisplayFormat FullyQualifiedNonNullableFormat { get; } = new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
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,20 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Snap.Hutao.SourceGeneration.Primitive;
internal static class SyntaxExtension
{
/// <summary>
/// Checks whether a given <see cref="MemberDeclarationSyntax"/> has or could potentially have any attribute lists.
/// </summary>
/// <param name="declaration">The input <see cref="MemberDeclarationSyntax"/> to check.</param>
/// <returns>Whether <paramref name="declaration"/> has or potentially has any attribute lists.</returns>
public static bool HasAttributeLists<TSyntax>(this TSyntax declaration)
where TSyntax : MemberDeclarationSyntax
{
return declaration.AttributeLists.Count > 0;
}
}

View File

@@ -1,28 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
namespace Snap.Hutao.SourceGeneration.Primitive;
internal static class TypeSymbolExtension
{
/// <summary>
/// Checks whether or not a given <see cref="ITypeSymbol"/> has or inherits from a specified type.
/// </summary>
/// <param name="typeSymbol">The target <see cref="ITypeSymbol"/> instance to check.</param>
/// <param name="name">The full name of the type to check for inheritance.</param>
/// <returns>Whether or not <paramref name="typeSymbol"/> is or inherits from <paramref name="name"/>.</returns>
public static bool IsOrInheritsFrom(this ITypeSymbol typeSymbol, string name)
{
for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType)
{
if (currentType.ToDisplayString() == name)
{
return true;
}
}
return false;
}
}

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>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
</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.7.0" />
</ItemGroup>
</Project>

View File

@@ -1,312 +0,0 @@
using Microsoft.CodeAnalysis;
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;
namespace Snap.Hutao.SourceGeneration;
/// <summary>
/// 通用分析器
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
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,
useValueTaskIfPossibleDescriptor,
useIsNotNullPatternMatchingDescriptor,
useIsNullPatternMatchingDescriptor,
useIsPatternRecursiveMatchingDescriptor,
useArgumentNullExceptionThrowIfNullDescriptor
}.ToImmutableArray();
}
}
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(CompilationStart);
}
private static void CompilationStart(CompilationStartAnalysisContext context)
{
SyntaxKind[] types =
{
SyntaxKind.ClassDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.EnumDeclaration,
};
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
SyntaxKind[] expressions =
{
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression);
// TODO add analyzer for unnecessary IServiceProvider registration
// TODO add analyzer for Singlton service use Scoped or Transient services
}
private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context)
{
BaseTypeDeclarationSyntax syntax = (BaseTypeDeclarationSyntax)context.Node;
bool privateExists = false;
bool internalExists = false;
bool fileExists = false;
foreach (SyntaxToken token in syntax.Modifiers)
{
if (token.IsKind(SyntaxKind.PrivateKeyword))
{
privateExists = true;
}
if (token.IsKind(SyntaxKind.InternalKeyword))
{
internalExists = true;
}
if (token.IsKind(SyntaxKind.FileKeyword))
{
fileExists = true;
}
}
if (!privateExists && !internalExists && !fileExists)
{
Location location = syntax.Identifier.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(typeInternalDescriptor, location, syntax.Identifier);
context.ReportDiagnostic(diagnostic);
}
}
private static void HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask(SyntaxNodeAnalysisContext context)
{
MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node;
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!;
// 跳过重载方法
if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.OverrideKeyword)))
{
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)
{
if (IsBuiltInType(symbol.Type))
{
continue;
}
if (RefLikeKeySkipTypes.Contains(symbol.Type.ToDisplayString()))
{
continue;
}
if (symbol.Type.IsReadOnly && symbol.RefKind == RefKind.None)
{
Location location = parameter.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(readOnlyStructRefDescriptor, location, symbol.Type);
context.ReportDiagnostic(diagnostic);
}
}
}
}
private static void HandleConstructorParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context)
{
ConstructorDeclarationSyntax constructorSyntax = (ConstructorDeclarationSyntax)context.Node;
foreach (ParameterSyntax parameter in constructorSyntax.ParameterList.Parameters)
{
if (context.SemanticModel.GetDeclaredSymbol(parameter) is IParameterSymbol symbol)
{
if (IsBuiltInType(symbol.Type))
{
continue;
}
// 跳过 CancellationToken
if (symbol.Type.ToDisplayString() == "System.Threading.CancellationToken")
{
continue;
}
if (symbol.Type.IsReadOnly && symbol.RefKind == RefKind.None)
{
Location location = parameter.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(readOnlyStructRefDescriptor, location, symbol.Type);
context.ReportDiagnostic(diagnostic);
}
}
}
}
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
{
SpecialType.System_Boolean => true,
SpecialType.System_Char => true,
SpecialType.System_SByte => true,
SpecialType.System_Byte => true,
SpecialType.System_Int16 => true,
SpecialType.System_UInt16 => true,
SpecialType.System_Int32 => true,
SpecialType.System_UInt32 => true,
SpecialType.System_Int64 => true,
SpecialType.System_UInt64 => true,
SpecialType.System_Decimal => true,
SpecialType.System_Single => true,
SpecialType.System_Double => true,
SpecialType.System_IntPtr => true,
SpecialType.System_UIntPtr => true,
_ => false,
};
}
}

View File

@@ -1,23 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "DGP Studio",
"copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license.",
"xmlHeader": false,
"variables": {
"licenseName": "MIT"
}
},
"orderingRules": {
"elementOrder": [
"kind",
"accessibility",
"constant",
"static",
"readonly"
],
"usingDirectivesPlacement": "outsideNamespace"
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass]
public class CollectionsMarshalTest
{
[TestMethod]
public void DictionaryMarshalGetValueRefOrNullRefIsNullRef()
{
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
Dictionary<string, string> dictionaryRefKeyRefValue = [];
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyRefValue, 1U)));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyValueValue, 1U)));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryRefKeyValueValue, "no such key")));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryRefKeyRefValue, "no such key")));
}
[TestMethod]
public void DictionaryMarshalGetValueRefOrAddDefaultIsDefault()
{
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
Dictionary<string, string> dictionaryRefKeyRefValue = [];
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyRefValue, 1U, out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyValueValue, 1U, out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryRefKeyValueValue, "no such key", out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryRefKeyRefValue, "no such key", out _) == default);
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass]
public sealed class JsonSerializeTest
{
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
private const string SmapleObjectJson = """
{
"A" :1
}
""";
private const string SmapleEmptyStringObjectJson = """
{
"A" : ""
}
""";
private const string SmapleNumberKeyDictionaryJson = """
{
"111" : "12",
"222" : "34"
}
""";
[TestMethod]
public void DelegatePropertyCanSerialize()
{
SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SmapleObjectJson)!;
Assert.AreEqual(sample.B, 1);
}
[TestMethod]
[ExpectedException(typeof(JsonException))]
public void EmptyStringCannotSerializeAsNumber()
{
SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SmapleEmptyStringObjectJson)!;
Assert.AreEqual(sample.A, 0);
}
[TestMethod]
public void NumberStringKeyCanSerializeAsKey()
{
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
Assert.AreEqual(sample[111], "12");
}
[TestMethod]
public void ByteArraySerializeAsBase64()
{
SampleByteArrayPropertyClass sample = new()
{
Array = [1, 2, 3, 4, 5],
};
string result = JsonSerializer.Serialize(sample);
Assert.AreEqual(result, """{"Array":"AQIDBAU="}""");
}
[TestMethod]
public void InterfaceDefaultMethodCanSerializeActualInstanceMember()
{
ISampleInterface sample = new SampleClassImplementedInterface()
{
A = 1,
B = 2,
};
string result = sample.ToJson();
Console.WriteLine(result);
Assert.AreEqual(result, """{"A":1,"B":2}""");
}
private sealed class SampleDelegatePropertyClass
{
public int A { get => B; set => B = value; }
public int B { get; set; }
}
private sealed class SampleStringReadWriteNumberPropertyClass
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int A { get; set; }
}
private sealed class SampleByteArrayPropertyClass
{
public byte[]? Array { get; set; }
}
private sealed class SampleClassImplementedInterface : ISampleInterface
{
public int A { get; set; }
public int B { get; set; }
}
[JsonDerivedType(typeof(SampleClassImplementedInterface))]
private interface ISampleInterface
{
int A { get; set; }
string ToJson()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass]
public sealed class LinqTest
{
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void LinqOrderByWithWrapperStructThrow()
{
List<MyUInt32> list = [1, 5, 2, 6, 3, 7, 4, 8];
string result = string.Join(", ", list.OrderBy(i => i).Select(i => i.Value));
Console.WriteLine(result);
}
private readonly struct MyUInt32
{
public readonly uint Value;
public MyUInt32(uint value)
{
Value = value;
}
public static implicit operator MyUInt32(uint value)
{
return new(value);
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass]
public sealed class TypeReflectionTest
{
[TestMethod]
public void TypeCodeOfEnumIsUserlyingTypeTypeCode()
{
Assert.AreEqual(Type.GetTypeCode(typeof(TestEnum)), TypeCode.Int32);
}
private enum TestEnum
{
A,
B,
}
}

View File

@@ -0,0 +1,209 @@
using System;
using System.Buffers.Binary;
using System.Globalization;
using System.Text;
namespace Snap.Hutao.Test.IncomingFeature;
[TestClass]
public sealed class GeniusInvokationDecoding
{
public TestContext? TestContext { get; set; }
/// <summary>
/// https://www.bilibili.com/video/av278125720
/// </summary>
[TestMethod]
public unsafe void GeniusInvokationShareCodeDecoding()
{
// 51 bytes obfuscated data
byte[] bytes = Convert.FromBase64String("BCHBwxQNAYERyVANCJGBynkOCZER2pgOCrFx8poQChGR9bYQDEGB9rkQDFKRD7oRDeEB");
// ---------------------------------------------
// | Data | Caesar Cipher Key |
// |----------|-------------------|
// | 50 Bytes | 1 Byte |
// ---------------------------------------------
// Data:
// 00000100 00100001 11000001 11000011 00010100
// 00001101 00000001 10000001 00010001 11001001
// 01010000 00001101 00001000 10010001 10000001
// 11001010 01111001 00001110 00001001 10010001
// 00010001 11011010 10011000 00001110 00001010
// 10110001 01110001 11110010 10011010 00010000
// 00001010 00010001 10010001 11110101 10110110
// 00010000 00001100 01000001 10000001 11110110
// 10111001 00010000 00001100 01010010 10010001
// 00001111 10111010 00010001 00001101 11100001
// ---------------------------------------------
// Caesar Cipher Key:
// 00000001
// ---------------------------------------------
fixed (byte* ptr = bytes)
{
// Reinterpret as 50 byte actual data and 1 deobfuscate key byte
EncryptedDataAndKey* data = (EncryptedDataAndKey*)ptr;
byte* dataPtr = data->Data;
// ----------------------------------------------------------
// | First | Second | Padding |
// |-----------|----------|---------|
// | 25 Bytes | 25 Bytes | 1 Byte |
// ----------------------------------------------------------
// We are doing two things here:
// 1. Retrieve actual data by subtracting key
// 2. Store data into two halves by alternating between them
// ----------------------------------------------------------
// What we will get after this step:
// ----------------------------------------------------------
// First:
// 00000011 11000000 00010011 00000000 00010000
// 01001111 00000111 10000000 01111000 00001000
// 00010000 10010111 00001001 01110000 10011001
// 00001001 10010000 10110101 00001011 10000000
// 10111000 00001011 10010000 10111001 00001100
// ----------------------------------------------------------
// Second:
// 00100000 11000010 00001100 10000000 11001000
// 00001100 10010000 11001001 00001101 10010000
// 11011001 00001101 10110000 11110001 00001111
// 00010000 11110100 00001111 01000000 11110101
// 00001111 01010001 00001110 00010000 11100000
// ----------------------------------------------------------
RearrangeBuffer rearranged = default;
byte* pFirst = rearranged.First;
byte* pSecond = rearranged.Second;
for (int i = 0; i < 50; i++)
{
// Determine which half are we going to insert
byte** ppTarget = i % 2 == 0 ? &pFirst : &pSecond;
// (actual data = data - key) and store it directly to the target half
**ppTarget = unchecked((byte)(dataPtr[i] - data->Key));
(*ppTarget)++;
}
// Prepare decoded data result storage
DecryptedData decoded = default;
ushort* pDecoded = decoded.Data;
// ----------------------------------------------------------
// | Data |
// |----------| x 17 = 51 Bytes
// | 3 Bytes |
// ----------------------------------------------------------
// Grouping each 3 bytes and read out as 2 ushort with
// 12 bits each (Big Endian)
// ----------------------------------------------------------
// 00000011 1100·0000 00010011|
// 00000000 0001·0000 01001111|
// 00000111 1000·0000 01111000|
// 00001000 0001·0000 10010111|
// 00001001 0111·0000 10011001|
// 00001001 1001·0000 10110101|
// 00001011 1000·0000 10111000|
// 00001011 1001·0000 10111001|
// 00001100 0010·0000 11000010|
// 00001100 1000·0000 11001000|
// 00001100 1001·0000 11001001|
// 00001101 1001·0000 11011001|
// 00001101 1011·0000 11110001|
// 00001111 0001·0000 11110100|
// 00001111 0100·0000 11110101|
// 00001111 0101·0001 00001110|
// 00010000 1110·0000 -padding|[padding32]
// ----------------------------------------------------------
// reinterpret as DecodeGroupingHelper for each 3 bytes
DecodeGroupingHelper* pGroup = (DecodeGroupingHelper*)&rearranged;
for (int i = 0; i < 17; i++)
{
(ushort first, ushort second) = pGroup->GetData();
*pDecoded = first;
*(pDecoded + 1) = second;
pDecoded += 2;
pGroup++;
}
// Now we get
// 60, 19, 1,
// 79,120,120,
// 129,151,151,
// 153,153,181,
// 184,184,185,
// 185,194,194,
// 200,200,201,
// 201,217,217,
// 219,241,241,
// 244,244,245,
// 245,270,270,
StringBuilder stringBuilder = new();
for (int i = 0; i < 33; i++)
{
stringBuilder
.AppendFormat(CultureInfo.InvariantCulture, "{0,3}", decoded.Data[i])
.Append(',');
if (i % 11 == 10)
{
stringBuilder.Append('\n');
}
}
TestContext?.WriteLine(stringBuilder.ToString(0, stringBuilder.Length - 1));
ushort[] resultArray = new ushort[33];
Span<ushort> result = new((ushort*)&decoded, 33);
result.CopyTo(resultArray);
ushort[] testKnownResult =
[
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
];
CollectionAssert.AreEqual(resultArray, testKnownResult);
}
}
private struct EncryptedDataAndKey
{
public unsafe fixed byte Data[50];
public byte Key;
}
private struct RearrangeBuffer
{
public unsafe fixed byte First[25];
public unsafe fixed byte Second[25];
// Make it 51 bytes
// allow to be group as 17 DecodeGroupingHelper later
public byte padding;
// prevent accidently int32 cast access violation
public byte paddingTo32;
}
private struct DecodeGroupingHelper
{
public unsafe fixed byte Data[3];
public unsafe (ushort First, ushort Second) GetData()
{
fixed (byte* ptr = Data)
{
uint value = BinaryPrimitives.ReverseEndianness((*(uint*)ptr) & 0x00FFFFFF) >> 8; // keep low 24 bits only
return ((ushort)((value >> 12) & 0x0FFF), (ushort)(value & 0x0FFF));
}
}
}
private struct DecryptedData
{
public unsafe fixed ushort Data[33];
}
}

View File

@@ -0,0 +1,43 @@
using System;
namespace Snap.Hutao.Test.IncomingFeature;
[TestClass]
public class SpiralAbyssScheduleIdTest
{
private static readonly TimeSpan Utc8 = new(8, 0, 0);
[TestMethod]
public void Test()
{
Console.WriteLine($"当前第 {GetForDateTimeOffset(DateTimeOffset.Now)} 期");
DateTimeOffset dateTimeOffset = new(2020, 7, 1, 4, 0, 0, Utc8);
Console.WriteLine($"2020-07-01 04:00:00 为第 {GetForDateTimeOffset(dateTimeOffset)} 期");
}
public static int GetForDateTimeOffset(DateTimeOffset dateTimeOffset)
{
// Force time in UTC+08
dateTimeOffset = dateTimeOffset.ToOffset(Utc8);
((int year, int mouth, int day), (int hour, _), _) = dateTimeOffset;
// 2020-07-01 04:00:00 为第 1 期
int periodNum = (((year - 2020) * 12) + (mouth - 6)) * 2;
// 上半月1-15 日, 以及 16 日 00:00-04:00
if (day < 16 || (day == 16 && hour < 4))
{
periodNum--;
}
// 上个月1 日 00:00-04:00
if (day is 1 && hour < 4)
{
periodNum--;
}
return periodNum;
}
}

View File

@@ -1,67 +0,0 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Test;
[TestClass]
public class JsonSerializeTest
{
private const string SmapleObjectJson = """
{
"A" :1
}
""";
private const string SmapleEmptyStringObjectJson = """
{
"A" : ""
}
""";
private const string SmapleNumberKeyDictionaryJson = """
{
"111" : "12",
"222" : "34"
}
""";
[TestMethod]
public void DelegatePropertyCanSerialize()
{
Sample sample = JsonSerializer.Deserialize<Sample>(SmapleObjectJson)!;
Assert.AreEqual(sample.B, 1);
}
[TestMethod]
[ExpectedException(typeof(JsonException))]
public void EmptyStringCannotSerializeAsNumber()
{
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleEmptyStringObjectJson)!;
Assert.AreEqual(sample.A, 0);
}
[TestMethod]
public void NumberStringKeyCanSerializeAsKey()
{
JsonSerializerOptions options = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
Assert.AreEqual(sample[111], "12");
}
private sealed class Sample
{
public int A { get => B; set => B = value; }
public int B { get; set; }
}
private sealed class StringNumberSample
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int A { get; set; }
}
}

View File

@@ -1,7 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Snap.Hutao.Test;
namespace Snap.Hutao.Test.PlatformExtensions;
[TestClass]
public sealed class DependencyInjectionTest

View File

@@ -9,11 +9,12 @@ public sealed class ForEachRuntimeBehaviorTest
[TestMethod]
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()
{
List<string> strings = new()
{
"a", "b", "c"
};
List<string> strings =
#if NET8_0_OR_GREATER
["a", "b", "c"];
#else
new() { "a", "b", "c" };
#endif
int count = 0;
foreach (ReadOnlySpan<char> chars in strings)
{

View File

@@ -8,8 +8,13 @@ public sealed class RangeRuntimeBehaviorTest
[TestMethod]
public void RangeTrimLastOne()
{
#if NET8_0_OR_GREATER
int[] array = [1, 2, 3, 4];
int[] test = [1, 2, 3];
#else
int[] array = { 1, 2, 3, 4 };
int[] test = { 1, 2, 3 };
#endif
int[] result = array[..^1];
Assert.AreEqual(3, result.Length);
Assert.IsTrue(MemoryExtensions.SequenceEqual<int>(test, result));

View File

@@ -1,16 +1,71 @@
namespace Snap.Hutao.Test.RuntimeBehavior;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Test.RuntimeBehavior;
[TestClass]
public sealed class UnsafeRuntimeBehaviorTest
{
[TestMethod]
public unsafe void UInt32AllSetIs()
public unsafe void UInt32AllSetIsUInt32MaxValue()
{
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, };
byte[] bytes =
#if NET8_0_OR_GREATER
[0xFF, 0xFF, 0xFF, 0xFF];
#else
{ 0xFF, 0xFF, 0xFF, 0xFF, };
#endif
fixed (byte* pBytes = bytes)
{
Assert.AreEqual(uint.MaxValue, *(uint*)pBytes);
}
}
[TestMethod]
public unsafe void UInt32LayoutIsLittleEndian()
{
ulong testValue = 0x1234567887654321;
ref BuildVersion version = ref Unsafe.As<ulong, BuildVersion>(ref testValue);
Assert.AreEqual(0x1234, version.Major);
Assert.AreEqual(0x5678, version.Minor);
Assert.AreEqual(0x8765, version.Patch);
Assert.AreEqual(0x4321, version.Build);
}
[TestMethod]
public unsafe void ReadOnlyStructCanBeModifiedInCtor()
{
TestStruct testStruct = new([4444, 7878, 5656, 1212]);
Assert.AreEqual(4444, testStruct.Value1);
Assert.AreEqual(7878, testStruct.Value2);
Assert.AreEqual(5656, testStruct.Value3);
Assert.AreEqual(1212, testStruct.Value4);
}
private readonly struct TestStruct
{
public readonly int Value1;
public readonly int Value2;
public readonly int Value3;
public readonly int Value4;
public TestStruct(List<int> list)
{
CollectionsMarshal.AsSpan(list).CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.As<TestStruct, int>(ref this), 4));
}
}
private readonly struct BuildVersion
{
public readonly ushort Build;
public readonly ushort Patch;
public readonly ushort Minor;
public readonly ushort Major;
}
}

View File

@@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFrameworks>net8.0</TargetFrameworks>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">

View File

@@ -2,5 +2,10 @@
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
"allowMarshaling": true,
"useSafeHandles": false,
"emitSingleFile": true
"comInterop": {
"preserveSigMethods": [
"IFileOpenDialog.Show",
"IFileSaveDialog.Show"
]
}
}

View File

@@ -10,15 +10,21 @@ DwmSetWindowAttribute
GetDeviceCaps
// KERNEL32
AllocConsole
CloseHandle
CreateEventW
CreateRemoteThread
FreeConsole
GetConsoleMode
GetModuleHandleW
GetProcAddress
GetStdHandle
K32EnumProcessModules
K32GetModuleBaseNameW
K32GetModuleInformation
ReadProcessMemory
SetConsoleMode
SetConsoleTitle
SetEvent
VirtualAlloc
VirtualAllocEx
@@ -28,20 +34,32 @@ WaitForSingleObject
WriteProcessMemory
// OLE32
CoCreateInstance
CoWaitForMultipleObjects
// SHELL32
SHCreateItemFromParsingName
// USER32
AttachThreadInput
FindWindowExW
GetCursorPos
GetDC
GetDpiForWindow
GetForegroundWindow
GetWindowPlacement
GetWindowThreadProcessId
ReleaseDC
RegisterHotKey
SendInput
SetForegroundWindow
UnregisterHotKey
// COM
FileOpenDialog
FileSaveDialog
IFileOpenDialog
IFileSaveDialog
IPersistFile
IShellLinkW
ShellLink
@@ -52,12 +70,17 @@ IMemoryBufferByteAccess
// Const value
INFINITE
RPC_E_WRONG_THREAD
MAX_PATH
WM_GETMINMAXINFO
WM_HOTKEY
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP
WM_NULL
// Type & Enum definition
HRESULT_FROM_WIN32
SLGP_FLAGS
// System.Threading
LPTHREAD_START_ROUTINE

View File

@@ -0,0 +1,17 @@
using System;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
namespace Windows.Win32;
internal static partial class PInvoke
{
/// <inheritdoc cref="CoCreateInstance(Guid*, object, CLSCTX, Guid*, out object)"/>
internal static unsafe HRESULT CoCreateInstance<TClass, TInterface>(object? pUnkOuter, CLSCTX dwClsContext, out TInterface ppv)
where TInterface : class
{
HRESULT hr = CoCreateInstance(typeof(TClass).GUID, pUnkOuter, dwClsContext, typeof(TInterface).GUID, out object o);
ppv = (TInterface)o;
return hr;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="NativeMethods.json" />
<None Remove="NativeMethods.txt" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="NativeMethods.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
using Windows.Win32.UI.WindowsAndMessaging;
[assembly: InternalsVisibleTo("Snap.Hutao")]
namespace Snap.Hutao.Win32;
/// <summary>
/// 结构体封送
/// </summary>
internal static class StructMarshal
{
/// <summary>
/// 构造一个新的 <see cref="Windows.Win32.UI.WindowsAndMessaging.WINDOWPLACEMENT"/>
/// </summary>
/// <returns>新的实例</returns>
public static unsafe WINDOWPLACEMENT WINDOWPLACEMENT()
{
return new() { length = unchecked((uint)sizeof(WINDOWPLACEMENT)) };
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Windows.Win32.CsWin32.InteropServices;
internal class WinRTCustomMarshaler : ICustomMarshaler
{
private static readonly string? AssemblyFullName = typeof(Windows.Foundation.IMemoryBuffer).Assembly.FullName;
private readonly string className;
private bool lookedForFromAbi;
private MethodInfo? fromAbiMethod;
private WinRTCustomMarshaler(string className)
{
this.className = className;
}
public static ICustomMarshaler GetInstance(string cookie)
{
return new WinRTCustomMarshaler(cookie);
}
public void CleanUpManagedData(object ManagedObj)
{
}
public void CleanUpNativeData(nint pNativeData)
{
Marshal.Release(pNativeData);
}
public int GetNativeDataSize()
{
throw new NotSupportedException();
}
public nint MarshalManagedToNative(object ManagedObj)
{
throw new NotSupportedException();
}
public object MarshalNativeToManaged(nint thisPtr)
{
return className switch
{
"Windows.System.DispatcherQueueController" => Windows.System.DispatcherQueueController.FromAbi(thisPtr),
_ => MarshalNativeToManagedSlow(thisPtr),
};
}
private object MarshalNativeToManagedSlow(nint pNativeData)
{
if (!lookedForFromAbi)
{
Type? type = Type.GetType($"{className}, {AssemblyFullName}");
fromAbiMethod = type?.GetMethod("FromAbi");
lookedForFromAbi = true;
}
if (fromAbiMethod is not null)
{
return fromAbiMethod.Invoke(default, new object[] { pNativeData })!;
}
else
{
return Marshal.GetObjectForIUnknown(pNativeData);
}
}
}

View File

@@ -10,16 +10,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{0F7ABEB2-5107-4037-B9DC-84D288FB0801}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug As Fake Elevated|Any CPU = Debug As Fake Elevated|Any CPU
Debug As Fake Elevated|arm64 = Debug As Fake Elevated|arm64
Debug As Fake Elevated|x64 = Debug As Fake Elevated|x64
Debug As Fake Elevated|x86 = Debug As Fake Elevated|x86
Debug|Any CPU = Debug|Any CPU
Debug|arm64 = Debug|arm64
Debug|x64 = Debug|x64
@@ -30,18 +26,6 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.ActiveCfg = Debug|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.Build.0 = Debug|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.Deploy.0 = Debug|x64
@@ -66,38 +50,6 @@ Global
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.ActiveCfg = Release|x86
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Build.0 = Release|x86
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Deploy.0 = Release|x86
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.Build.0 = Debug|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.ActiveCfg = Debug|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.Build.0 = Debug|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.Build.0 = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.Build.0 = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.ActiveCfg = Release|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -114,16 +66,32 @@ Global
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x64.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.ActiveCfg = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
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
RESX_NeutralResourcesLanguage = zh-CN
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
RESX_SortFileContentOnSave = True
RESX_ShowErrorsInErrorList = False
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]}
EndGlobalSection
EndGlobal

View File

@@ -4,12 +4,18 @@
"add": {
"extensionToExtension": {
"add": {
".json": [ ".txt" ]
".json": [
".txt"
]
}
},
"pathSegment": {
"add": {
".*": [ ".cs", ".resx" ]
".*": [
".cs",
".resx",
".appxmanifest"
]
}
},
"fileSuffixToExtension": {
@@ -19,12 +25,24 @@
},
"fileToFile": {
"add": {
".filenesting.json": [ "App.xaml.cs" ],
"app.manifest": [ "App.xaml.cs" ],
"Package.appxmanifest": [ "App.xaml" ],
"Package.StoreAssociation.xml": [ "App.xaml" ],
".editorconfig": [ "Program.cs" ],
"GlobalUsing.cs": [ "Program.cs" ]
".filenesting.json": [
"App.xaml.cs"
],
"app.manifest": [
"App.xaml.cs"
],
"Package.appxmanifest": [
"App.xaml"
],
"Package.StoreAssociation.xml": [
"App.xaml"
],
".editorconfig": [
"Program.cs"
],
"GlobalUsing.cs": [
"Program.cs"
]
}
}
}

View File

@@ -1,153 +1,33 @@
<Application
x:Class="Snap.Hutao.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwc="using:CommunityToolkit.WinUI.Converters"
xmlns:cwcw="using:CommunityToolkit.WinUI.Controls"
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">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<muxc:XamlControlsResources/>
<ResourceDictionary Source="Control/Theme/FontStyle.xaml"/>
<ResourceDictionary Source="Control/Loading.xaml"/>
<XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Color.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ComboBox.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Converter.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/CornerRadius.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/FlyoutStyle.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/FontStyle.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Glyph.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/InfoBarOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ItemsPanelTemplate.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/NumericValue.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Color x:Key="AvatarPropertyAddValueColor">#FF74BF00</Color>
<Color x:Key="CompatBackgroundColor">#FFF4F4F4</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="AvatarPropertyAddValueColor">#FF90E800</Color>
<Color x:Key="CompatBackgroundColor">#FF242424</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- Modify Window title bar color -->
<StaticResource x:Key="WindowCaptionBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- Page Transparent Background -->
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- InfoBar Resource -->
<Thickness x:Key="InfoBarIconMargin">19,16,19,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">0,0,0,0</Thickness>
<x:Double x:Key="InfoBarIconFontSize">20</x:Double>
<!-- Pivot Resource -->
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
<Thickness x:Key="PivotItemMargin">0</Thickness>
<!-- CornerRadius -->
<CornerRadius x:Key="CompatCornerRadius">6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusTop">6,6,0,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!-- 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>
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
<Style
x:Key="SettingsSectionHeaderTextBlockStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
TargetType="TextBlock">
<Style.Setters>
<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>
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
<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>
<x:String x:Key="UI_EmotionIcon71">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon71.png</x:String>
<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 -->
<cwc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<cwc:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
<shmmc:DescriptionsParametersDescriptor x:Key="DescParamDescriptor"/>
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
<shmmc:EmotionIconConverter x:Key="EmotionIconConverter"/>
<shmmc:EquipIconConverter x:Key="EquipIconConverter"/>
<shmmc:GachaAvatarImgConverter x:Key="GachaAvatarImgConverter"/>
<shmmc:GachaAvatarIconConverter x:Key="GachaAvatarIconConverter"/>
<shmmc:GachaEquipIconConverter x:Key="GachaEquipIconConverter"/>
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
<shmmc:MonsterIconConverter x:Key="MonsterIconConverter"/>
<shmmc:PropertiesParametersDescriptor x:Key="PropertyDescriptor"/>
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
<shvc:EmptyObjectToBoolConverter x:Key="EmptyObjectToBoolConverter"/>
<shvc:EmptyObjectToBoolRevertConverter x:Key="EmptyObjectToBoolRevertConverter"/>
<shvc:EmptyObjectToVisibilityConverter x:Key="EmptyObjectToVisibilityConverter"/>
<shvc:EmptyObjectToVisibilityRevertConverter x:Key="EmptyObjectToVisibilityRevertConverter"/>
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
<shvc:StringBoolConverter x:Key="StringBoolConverter"/>
<!-- Styles -->
<Style
x:Key="LargeGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
@@ -155,118 +35,19 @@
<Setter Property="Margin" Value="0,0,12,12"/>
</Style>
<Style
x:Key="SettingButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Padding" Value="16,6,16,6"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
x:Name="NoneSelectionListViewItemStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,4,0,0"/>
</Style>
<Style x:Key="BorderCardStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
<Style
x:Name="NoneSelectionGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
TargetType="GridViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,0,2,4"/>
</Style>
<Style x:Key="BorderGridStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
</Style>
<Style TargetType="shci:CachedImage">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="LazyLoadingThreshold" Value="300"/>
<Setter Property="Template">
<Setter.Value>
<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="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="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>
<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>
<VisualState x:Name="Unloaded"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ItemsPanelTemplate -->
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
<ItemsStackPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapPanelTemplate">
<cwcw:WrapPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
<!-- Transitions -->
<TransitionCollection x:Key="ReorderThemeTransitions">
<ReorderThemeTransition/>
</TransitionCollection>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -20,7 +20,24 @@ namespace Snap.Hutao;
[SuppressMessage("", "SH001")]
public sealed partial class App : Application
{
private const string ConsoleBanner = """
----------------------------------------------------------------
_____ _ _ _
/ ____| | | | | | |
| (___ _ __ __ _ _ __ | |__| | _ _ | |_ __ _ ___
\___ \ | '_ \ / _` || '_ \ | __ || | | || __|/ _` | / _ \
____) || | | || (_| || |_) |_ | | | || |_| || |_| (_| || (_) |
|_____/ |_| |_| \__,_|| .__/(_)|_| |_| \__,_| \__|\__,_| \___/
| |
|_|
Snap.Hutao is a open source software developed by DGP Studio.
Copyright (C) 2022 - 2024 DGP Studio, All Rights Reserved.
----------------------------------------------------------------
""";
private const string AppInstanceKey = "main";
private readonly IServiceProvider serviceProvider;
private readonly IActivation activation;
private readonly ILogger<App> logger;
@@ -51,6 +68,8 @@ public sealed partial class App : Application
if (firstInstance.IsCurrent)
{
logger.LogInformation(ConsoleBanner);
// manually invoke
activation.NonRedirectToActivate(firstInstance, activatedEventArgs);
activation.InitializeWith(firstInstance);

View File

@@ -3,12 +3,18 @@
namespace Snap.Hutao.Control.Animation;
/// <summary>
/// 动画时长
/// </summary>
[HighQuality]
internal static class AnimationDurations
internal static class ControlAnimationConstants
{
/// <summary>
/// 1
/// </summary>
public const string One = "1";
/// <summary>
/// 1.1
/// </summary>
public const string OnePointOne = "1.1";
/// <summary>
/// 图片缩放动画
/// </summary>

View File

@@ -19,10 +19,10 @@ internal sealed class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
/// </summary>
public ImageZoomInAnimation()
{
Duration = AnimationDurations.ImageZoom;
Duration = ControlAnimationConstants.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
To = Core.StringLiterals.OnePointOne;
To = ControlAnimationConstants.OnePointOne;
}
/// <inheritdoc/>

View File

@@ -19,10 +19,10 @@ internal sealed class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
/// </summary>
public ImageZoomOutAnimation()
{
Duration = AnimationDurations.ImageZoom;
Duration = ControlAnimationConstants.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
To = Core.StringLiterals.One;
To = ControlAnimationConstants.One;
}
/// <inheritdoc/>

View File

@@ -1,48 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 按给定比例自动调整高度的行为
/// </summary>
[HighQuality]
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoHeightBehavior : BehaviorBase<FrameworkElement>
{
private readonly SizeChangedEventHandler sizeChangedEventHandler;
public AutoHeightBehavior()
{
sizeChangedEventHandler = OnSizeChanged;
}
/// <inheritdoc/>
protected override bool Initialize()
{
UpdateElement();
AssociatedObject.SizeChanged += sizeChangedEventHandler;
return true;
}
/// <inheritdoc/>
protected override bool Uninitialize()
{
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
return true;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateElement();
}
private void UpdateElement()
{
AssociatedObject.Height = AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
}
}

View File

@@ -1,48 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 按给定比例自动调整高度的行为
/// </summary>
[HighQuality]
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoWidthBehavior : BehaviorBase<FrameworkElement>
{
private readonly SizeChangedEventHandler sizeChangedEventHandler;
public AutoWidthBehavior()
{
sizeChangedEventHandler = OnSizeChanged;
}
/// <inheritdoc/>
protected override bool Initialize()
{
UpdateElement();
AssociatedObject.SizeChanged += sizeChangedEventHandler;
return true;
}
/// <inheritdoc/>
protected override bool Uninitialize()
{
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
return true;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateElement();
}
private void UpdateElement()
{
AssociatedObject.Width = AssociatedObject.Height * (TargetWidth / TargetHeight);
}
}

View File

@@ -35,6 +35,11 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
private void TryExecuteCommand()
{
if (AssociatedObject is null)
{
return;
}
if (executed)
{
return;

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Labs.WinUI.MarqueeTextRns;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml.Input;
namespace Snap.Hutao.Control.Behavior;
internal sealed class MarqueeTextBehavior : BehaviorBase<MarqueeText>
{
private readonly PointerEventHandler pointerEnteredEventHandler;
private readonly PointerEventHandler pointerExitedEventHandler;
public MarqueeTextBehavior()
{
pointerEnteredEventHandler = OnPointerEntered;
pointerExitedEventHandler = OnPointerExited;
}
protected override bool Initialize()
{
AssociatedObject.PointerEntered += pointerEnteredEventHandler;
AssociatedObject.PointerExited += pointerExitedEventHandler;
return true;
}
protected override bool Uninitialize()
{
AssociatedObject.PointerEntered -= pointerEnteredEventHandler;
AssociatedObject.PointerExited -= pointerExitedEventHandler;
return true;
}
private void OnPointerEntered(object sender, PointerRoutedEventArgs e)
{
AssociatedObject.StartMarquee();
}
private void OnPointerExited(object sender, PointerRoutedEventArgs e)
{
AssociatedObject.StopMarquee();
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml.Controls;
@@ -12,7 +13,7 @@ internal sealed class SelectedItemInViewBehavior : BehaviorBase<ListViewBase>
{
if (AssociatedObject.SelectedItem is { } item)
{
AssociatedObject.ScrollIntoView(item);
AssociatedObject.SmoothScrollIntoViewWithItemAsync(item, ScrollItemPlacement.Center).SafeForget();
}
return true;

View File

@@ -11,12 +11,17 @@ namespace Snap.Hutao.Control.Behavior;
/// 打开附着的浮出控件操作
/// </summary>
[HighQuality]
internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction
internal sealed class ShowAttachedFlyoutAction : DependencyObject, IAction
{
/// <inheritdoc/>
public object Execute(object sender, object parameter)
public object? Execute(object sender, object parameter)
{
if (sender is null)
{
return default;
}
FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
return default!;
return default;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Xaml;
using Microsoft.Xaml.Interactivity;
namespace Snap.Hutao.Control.Behavior;
[DependencyProperty("Animation", typeof(AnimationSet))]
[DependencyProperty("TargetObject", typeof(UIElement))]
internal sealed partial class StartAnimationActionNoThrow : DependencyObject, IAction
{
/// <inheritdoc/>
public object Execute(object sender, object parameter)
{
if (Animation is not null)
{
if (TargetObject is not null)
{
Animation.Start(TargetObject);
}
else
{
Animation.Start(sender as UIElement);
}
}
return default!;
}
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
/// <summary>
/// 封装的值
/// </summary>
[HighQuality]
internal static class BoxedValues
{
/// <summary>
/// <see cref="true"/>
/// </summary>
public static readonly object True = true;
/// <summary>
/// <see cref="false"/>
/// </summary>
public static readonly object False = false;
}

View File

@@ -3,7 +3,7 @@
using Windows.UI;
namespace Snap.Hutao.Control;
namespace Snap.Hutao.Control.Brush;
internal sealed class ColorSegment : IColorSegment
{

View File

@@ -3,7 +3,7 @@
using Windows.UI;
namespace Snap.Hutao.Control;
namespace Snap.Hutao.Control.Brush;
internal interface IColorSegment
{

View File

@@ -7,7 +7,7 @@ using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Control;
namespace Snap.Hutao.Control.Brush;
[DependencyProperty("Source", typeof(List<IColorSegment>), default!, nameof(OnSourceChanged))]
internal sealed partial class SegmentedBar : ContentControl
@@ -35,9 +35,9 @@ internal sealed partial class SegmentedBar : ContentControl
double offset = 0;
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(list))
{
collection.Add(new GradientStop() { Color = segment.Color, Offset = offset, });
collection.Add(new() { Color = segment.Color, Offset = offset, });
offset += segment.Value / total;
collection.Add(new GradientStop() { Color = segment.Color, Offset = offset, });
collection.Add(new() { Color = segment.Color, Offset = offset, });
}
}
}

View File

@@ -2,12 +2,11 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.Foundation.Collections;
namespace Snap.Hutao.Control.Alternating;
namespace Snap.Hutao.Control.Collection.Alternating;
[DependencyProperty("ItemAlternateBackground", typeof(Brush))]
[DependencyProperty("ItemAlternateBackground", typeof(Microsoft.UI.Xaml.Media.Brush))]
internal sealed partial class AlternatingItemsControl : ItemsControl
{
private readonly VectorChangedEventHandler<object> itemsVectorChangedEventHandler;

View File

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

View File

@@ -0,0 +1,23 @@
// 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.Data;
namespace Snap.Hutao.Control.Collection.Selector;
[DependencyProperty("EnableMemberPath", typeof(string))]
internal sealed partial class ComboBox2 : ComboBox
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
if (element is ComboBoxItem comboBoxItem)
{
Binding binding = new() { Path = new(EnableMemberPath) };
comboBoxItem.SetBinding(IsEnabledProperty, binding);
}
base.PrepareContainerForItemOverride(element, item);
}
}

View File

@@ -16,22 +16,7 @@ internal abstract class DependencyValueConverter<TFrom, TTo> : DependencyObject,
/// <inheritdoc/>
public object? Convert(object value, Type targetType, object parameter, string language)
{
#if DEBUG
try
{
return Convert((TFrom)value);
}
catch (Exception ex)
{
Ioc.Default
.GetRequiredService<ILogger<ValueConverter<TFrom, TTo>>>()
.LogError(ex, "值转换器异常");
}
return null;
#else
return Convert((TFrom)value);
#endif
}
/// <inheritdoc/>

View File

@@ -10,8 +10,8 @@ internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
private bool disposed = false;
private bool disposing = false;
private bool disposed = false;
public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext)
{
@@ -24,7 +24,7 @@ internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable
if (!disposed && !disposing)
{
disposing = true;
taskContext.InvokeOnMainThread(contentDialog.Hide); // Hide() must be called on main thread.
taskContext.InvokeOnMainThread(contentDialog.Hide);
disposing = false;
disposed = true;
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using System.Diagnostics;
namespace Snap.Hutao.Control.Extension;
/// <summary>
/// Bridge 拓展
/// </summary>
[HighQuality]
internal static class WebView2Extension
{
[Conditional("RELEASE")]
public static void DisableDevToolsForReleaseBuild(this CoreWebView2 webView)
{
CoreWebView2Settings settings = webView.Settings;
settings.AreBrowserAcceleratorKeysEnabled = false;
settings.AreDefaultContextMenusEnabled = false;
settings.AreDevToolsEnabled = false;
}
public static void DisableAutoCompletion(this CoreWebView2 webView)
{
CoreWebView2Settings settings = webView.Settings;
settings.IsGeneralAutofillEnabled = false;
settings.IsPasswordAutosaveEnabled = false;
}
public static async ValueTask DeleteCookiesAsync(this CoreWebView2 webView, string url)
{
CoreWebView2CookieManager manager = webView.CookieManager;
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync(url);
foreach (CoreWebView2Cookie item in cookies)
{
manager.DeleteCookie(item);
}
}
public static bool IsDisposed(this WebView2 webView2)
{
return WinRTExtension.IsDisposed(webView2);
}
}

View File

@@ -0,0 +1,14 @@
// 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("LeftPanelMaxWidth", typeof(double), IsAttached = true, AttachedType = typeof(ScrollViewer))]
[DependencyProperty("RightPanel", typeof(UIElement), IsAttached = true, AttachedType = typeof(ScrollViewer))]
public sealed partial class ScrollViewerHelper
{
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("IsItemsEnabled", typeof(bool), true, nameof(OnIsItemsEnabledChanged), IsAttached = true, AttachedType = typeof(SettingsExpander))]
public sealed partial class SettingsExpanderHelper
{
private static void OnIsItemsEnabledChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
foreach (object item in ((SettingsExpander)dp).Items)
{
if (item is Microsoft.UI.Xaml.Controls.Control control)
{
control.IsEnabled = (bool)e.NewValue;
}
}
}
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
internal interface IScopedPageScopeReferenceTracker : IDisposable
{
IServiceScope CreateScope();
}

View File

@@ -20,7 +20,7 @@ internal sealed class CachedImage : Implementation.ImageEx
public CachedImage()
{
IsCacheEnabled = true;
EnableLazyLoading = true;
EnableLazyLoading = false;
}
/// <inheritdoc/>

View File

@@ -0,0 +1,80 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shci="using:Snap.Hutao.Control.Image">
<Style TargetType="shci:CachedImage">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="LazyLoadingThreshold" Value="256"/>
<Setter Property="Template">
<Setter.Value>
<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}"
Source="{TemplateBinding PlaceholderSource}"
Stretch="{TemplateBinding PlaceholderStretch}"/>
<Image
Name="Image"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
NineGrid="{TemplateBinding NineGrid}"
Stretch="{TemplateBinding Stretch}"/>
<VisualStateManager.VisualStateGroups>
<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="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>
<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>
<VisualState x:Name="Unloaded"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -30,7 +30,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
private readonly IServiceProvider serviceProvider;
private readonly RoutedEventHandler unloadEventHandler;
private readonly SizeChangedEventHandler sizeChangedEventHandler;
private readonly TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs> loadedImageSourceLoadCompletedEventHandler;
@@ -46,9 +45,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
serviceProvider = this.ServiceProvider();
this.DisableInteraction();
unloadEventHandler = OnUnload;
Unloaded += unloadEventHandler;
sizeChangedEventHandler = OnSizeChanged;
SizeChanged += sizeChangedEventHandler;
@@ -67,10 +63,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
}
protected virtual void Unloading()
{
}
/// <summary>
/// 更新视觉对象
/// </summary>
@@ -88,19 +80,22 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>();
// source is valid
if (arg.NewValue is Uri inner && !string.IsNullOrEmpty(inner.OriginalString))
if (arg.NewValue is Uri inner)
{
// value is different from old one
if (inner != (arg.OldValue as Uri))
if (!string.IsNullOrEmpty(inner.OriginalString))
{
image
.ApplyImageAsync(inner, token)
.SafeForget(logger, ex => OnApplyImageFailed(serviceProvider, inner, ex));
// value is different from old one
if (inner != (arg.OldValue as Uri))
{
image
.ApplyImageAsync(inner, token)
.SafeForget(logger, ex => OnApplyImageFailed(serviceProvider, inner, ex));
}
}
else
{
image.HideAsync(token).SafeForget(logger);
}
}
else
{
image.HideAsync(token).SafeForget(logger);
}
}
@@ -110,7 +105,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
if (exception is HttpRequestException httpRequestException)
{
infoBarService.Error(httpRequestException, SH.ControlImageCompositionImageHttpRequest.Format(uri));
infoBarService.Error(httpRequestException, SH.FormatControlImageCompositionImageHttpRequest(uri));
}
else
{
@@ -138,8 +133,9 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
imageSurface = await LoadImageSurfaceAsync(file, token).ConfigureAwait(true);
}
catch (COMException)
catch (COMException ex)
{
_ = ex;
imageCache.Remove(uri);
}
catch (IOException)
@@ -164,11 +160,26 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
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;
LoadedImageSurface? surface = default;
try
{
surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
surface.LoadCompleted += loadedImageSourceLoadCompletedEventHandler;
if (surface.DecodedPhysicalSize.Size() <= 0D)
{
await Task.WhenAny(surfaceLoadTaskCompletionSource.Task, Task.Delay(5000, token)).ConfigureAwait(true);
}
LoadImageSurfaceCompleted(surface);
return surface;
}
finally
{
if (surface is not null)
{
surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler;
}
}
}
private async ValueTask ShowAsync(CancellationToken token)
@@ -181,7 +192,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 0D, to: 1D, duration: AnimationDurations.ImageFadeIn)
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
.StartAsync(this, token)
.ConfigureAwait(true);
}
@@ -202,7 +213,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 1D, to: 0D, duration: AnimationDurations.ImageFadeOut)
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
.StartAsync(this, token)
.ConfigureAwait(true);
}
@@ -216,7 +227,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
private void OnLoadImageSurfaceLoadCompleted(LoadedImageSurface surface, LoadedImageSourceLoadCompletedEventArgs e)
{
surfaceLoadTaskCompletionSource?.TrySetResult();
surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
@@ -226,14 +236,4 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
UpdateVisual(spriteVisual);
}
}
private void OnUnload(object sender, RoutedEventArgs e)
{
Unloading();
spriteVisual?.Dispose();
spriteVisual = null;
SizeChanged -= sizeChangedEventHandler;
Unloaded -= unloadEventHandler;
}
}

View File

@@ -5,7 +5,6 @@ using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Win32;
namespace Snap.Hutao.Control.Image;

View File

@@ -45,16 +45,6 @@ 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 is not null)

View File

@@ -0,0 +1,151 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using System.Numerics;
using Windows.Foundation;
namespace Snap.Hutao.Control.Layout;
internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTransitionProvider
{
private const double DefaultAnimationDurationInMs = 300.0;
static DefaultItemCollectionTransitionProvider()
{
AnimationSlowdownFactor = 1.0;
}
public static double AnimationSlowdownFactor { get; set; }
protected override bool ShouldAnimateCore(ItemCollectionTransition transition)
{
return true;
}
protected override void StartTransitions(IList<ItemCollectionTransition> transitions)
{
List<ItemCollectionTransition> addTransitions = [];
List<ItemCollectionTransition> removeTransitions = [];
List<ItemCollectionTransition> moveTransitions = [];
foreach (ItemCollectionTransition transition in addTransitions)
{
switch (transition.Operation)
{
case ItemCollectionTransitionOperation.Add:
addTransitions.Add(transition);
break;
case ItemCollectionTransitionOperation.Remove:
removeTransitions.Add(transition);
break;
case ItemCollectionTransitionOperation.Move:
moveTransitions.Add(transition);
break;
}
}
StartAddTransitions(addTransitions, removeTransitions.Count > 0, moveTransitions.Count > 0);
StartRemoveTransitions(removeTransitions);
StartMoveTransitions(moveTransitions, removeTransitions.Count > 0);
}
private static void StartAddTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveTransitions, bool hasMoveTransitions)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
ScalarKeyFrameAnimation fadeInAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeInAnimation.InsertKeyFrame(0.0f, 0.0f);
if (hasMoveTransitions && hasRemoveTransitions)
{
fadeInAnimation.InsertKeyFrame(0.66f, 0.0f);
}
else if (hasMoveTransitions || hasRemoveTransitions)
{
fadeInAnimation.InsertKeyFrame(0.5f, 0.0f);
}
fadeInAnimation.InsertKeyFrame(1.0f, 1.0f);
fadeInAnimation.Duration = TimeSpan.FromMilliseconds(
DefaultAnimationDurationInMs * ((hasRemoveTransitions ? 1 : 0) + (hasMoveTransitions ? 1 : 0) + 1) * AnimationSlowdownFactor);
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
visual.StartAnimation("Opacity", fadeInAnimation);
batch.End();
batch.Completed += (_, _) => progress.Complete();
}
}
private static void StartRemoveTransitions(IList<ItemCollectionTransition> transitions)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
ScalarKeyFrameAnimation fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeOutAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue");
fadeOutAnimation.InsertKeyFrame(1.0f, 0.0f);
fadeOutAnimation.Duration = TimeSpan.FromMilliseconds(DefaultAnimationDurationInMs * AnimationSlowdownFactor);
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
visual.StartAnimation(nameof(Visual.Opacity), fadeOutAnimation);
batch.End();
batch.Completed += (_, _) =>
{
visual.Opacity = 1.0f;
progress.Complete();
};
}
}
private static void StartMoveTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveAnimations)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
// Animate offset.
if (transition.OldBounds.X != transition.NewBounds.X ||
transition.OldBounds.Y != transition.NewBounds.Y)
{
AnimateOffset(visual, compositor, transition.OldBounds, transition.NewBounds, hasRemoveAnimations);
}
batch.End();
batch.Completed += (_, _) => progress.Complete();
}
}
private static void AnimateOffset(Visual visual, Compositor compositor, Rect oldBounds, Rect newBounds, bool hasRemoveAnimations)
{
Vector2KeyFrameAnimation offsetAnimation = compositor.CreateVector2KeyFrameAnimation();
offsetAnimation.SetVector2Parameter("delta", new Vector2(
(float)(oldBounds.X - newBounds.X),
(float)(oldBounds.Y - newBounds.Y)));
offsetAnimation.SetVector2Parameter("final", default);
offsetAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue + delta");
if (hasRemoveAnimations)
{
offsetAnimation.InsertExpressionKeyFrame(0.5f, "delta");
}
offsetAnimation.InsertExpressionKeyFrame(1.0f, "final");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(
DefaultAnimationDurationInMs * ((hasRemoveAnimations ? 1 : 0) + 1) * AnimationSlowdownFactor);
visual.StartAnimation("TransformMatrix._41_42", offsetAnimation);
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics;
namespace Snap.Hutao.Control.Layout;
[DebuggerDisplay("Count = {Count}, Height = {Height}")]
internal class UniformStaggeredColumnLayout : List<UniformStaggeredItem>
{
public double Height { get; private set; }
public new void Add(UniformStaggeredItem item)
{
Height = item.Top + item.Height;
base.Add(item);
}
public new void Clear()
{
Height = 0;
base.Clear();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Layout;
internal sealed class UniformStaggeredItem
{
public UniformStaggeredItem(int index)
{
Index = index;
}
public double Top { get; internal set; }
public double Height { get; internal set; }
public int Index { get; }
public UIElement? Element { get; internal set; }
}

View File

@@ -0,0 +1,272 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using Windows.Foundation;
namespace Snap.Hutao.Control.Layout;
[DependencyProperty("MinItemWidth", typeof(double), 0D, nameof(OnMinItemWidthChanged))]
[DependencyProperty("MinColumnSpacing", typeof(double), 0D, nameof(OnSpacingChanged))]
[DependencyProperty("MinRowSpacing", typeof(double), 0D, nameof(OnSpacingChanged))]
internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
{
/// <inheritdoc/>
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
{
context.LayoutState = new UniformStaggeredLayoutState(context);
base.InitializeForContextCore(context);
}
/// <inheritdoc/>
protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
{
context.LayoutState = null;
base.UninitializeForContextCore(context);
}
/// <inheritdoc/>
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args)
{
UniformStaggeredLayoutState state = (UniformStaggeredLayoutState)context.LayoutState;
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
state.RemoveFromIndex(args.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
state.RemoveFromIndex(args.NewStartingIndex);
state.RecycleElementAt(args.NewStartingIndex); // We must recycle the element to ensure that it gets the correct context
break;
case NotifyCollectionChangedAction.Move:
int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex);
int maxIndex = Math.Max(args.NewStartingIndex, args.OldStartingIndex);
state.RemoveRange(minIndex, maxIndex);
break;
case NotifyCollectionChangedAction.Remove:
state.RemoveFromIndex(args.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
state.Clear();
break;
}
base.OnItemsChangedCore(context, source, args);
}
/// <inheritdoc/>
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
{
if (context.ItemCount == 0)
{
return new Size(availableSize.Width, 0);
}
if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0))
{
return new Size(availableSize.Width, 0.0f);
}
UniformStaggeredLayoutState state = (UniformStaggeredLayoutState)context.LayoutState;
double availableWidth = availableSize.Width;
double availableHeight = availableSize.Height;
(int numberOfColumns, double columnWidth) = GetNumberOfColumnsAndWidth(availableWidth, MinItemWidth, MinColumnSpacing);
if (columnWidth != state.ColumnWidth)
{
// The items will need to be remeasured
state.Clear();
}
state.ColumnWidth = columnWidth;
// adjust for column spacing on all columns expect the first
double totalWidth = state.ColumnWidth + ((numberOfColumns - 1) * (state.ColumnWidth + MinColumnSpacing));
if (totalWidth > availableWidth)
{
numberOfColumns--;
}
else if (double.IsInfinity(availableWidth))
{
availableWidth = totalWidth;
}
if (numberOfColumns != state.NumberOfColumns)
{
// The items will not need to be remeasured, but they will need to go into new columns
state.ClearColumns();
}
if (MinRowSpacing != state.RowSpacing)
{
// If the RowSpacing changes the height of the rows will be different.
// The columns stores the height so we'll want to clear them out to
// get the proper height
state.ClearColumns();
state.RowSpacing = MinRowSpacing;
}
Span<double> columnHeights = new double[numberOfColumns];
Span<int> itemsPerColumn = new int[numberOfColumns];
HashSet<int> deadColumns = [];
for (int i = 0; i < context.ItemCount; i++)
{
int columnIndex = GetLowestColumnIndex(columnHeights);
bool measured = false;
UniformStaggeredItem item = state.GetItemAt(i);
if (item.Height == 0)
{
// Item has not been measured yet. Get the element and store the values
UIElement element = context.GetOrCreateElementAt(i);
element.Measure(new Size(state.ColumnWidth, availableHeight));
item.Height = element.DesiredSize.Height;
item.Element = element;
measured = true;
}
double spacing = itemsPerColumn[columnIndex] > 0 ? MinRowSpacing : 0;
item.Top = columnHeights[columnIndex] + spacing;
double bottom = item.Top + item.Height;
columnHeights[columnIndex] = bottom;
itemsPerColumn[columnIndex]++;
state.AddItemToColumn(item, columnIndex);
if (bottom < context.RealizationRect.Top)
{
// The bottom of the element is above the realization area
if (item.Element is not null)
{
context.RecycleElement(item.Element);
item.Element = null;
}
}
else if (item.Top > context.RealizationRect.Bottom)
{
// The top of the element is below the realization area
if (item.Element is not null)
{
context.RecycleElement(item.Element);
item.Element = null;
}
deadColumns.Add(columnIndex);
}
else if (measured == false)
{
// We ALWAYS want to measure an item that will be in the bounds
item.Element = context.GetOrCreateElementAt(i);
item.Element.Measure(new Size(state.ColumnWidth, availableHeight));
if (item.Height != item.Element.DesiredSize.Height)
{
// this item changed size; we need to recalculate layout for everything after this
state.RemoveFromIndex(i + 1);
item.Height = item.Element.DesiredSize.Height;
columnHeights[columnIndex] = item.Top + item.Height;
}
}
if (deadColumns.Count == numberOfColumns)
{
break;
}
}
double desiredHeight = state.GetHeight();
return new Size(availableWidth, desiredHeight);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
{
if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0))
{
return finalSize;
}
UniformStaggeredLayoutState state = (UniformStaggeredLayoutState)context.LayoutState;
// Cycle through each column and arrange the items that are within the realization bounds
for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++)
{
UniformStaggeredColumnLayout layout = state.GetColumnLayout(columnIndex);
foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(layout))
{
double bottom = item.Top + item.Height;
if (bottom < context.RealizationRect.Top)
{
// element is above the realization bounds
continue;
}
if (item.Top <= context.RealizationRect.Bottom)
{
double itemHorizontalOffset = (state.ColumnWidth * columnIndex) + (MinColumnSpacing * columnIndex);
Rect bounds = new(itemHorizontalOffset, item.Top, state.ColumnWidth, item.Height);
UIElement element = context.GetOrCreateElementAt(item.Index);
element.Arrange(bounds);
}
else
{
break;
}
}
}
return finalSize;
}
private static (int NumberOfColumns, double ColumnWidth) GetNumberOfColumnsAndWidth(double availableWidth, double minItemWidth, double minColumnSpacing)
{
// test if the width can fit in 2 items
if ((2 * minItemWidth) + minColumnSpacing > availableWidth)
{
return (1, availableWidth);
}
int columnCount = Math.Max(1, (int)((availableWidth + minColumnSpacing) / (minItemWidth + minColumnSpacing)));
double columnWidthAddSpacing = (availableWidth + minColumnSpacing) / columnCount;
return (columnCount, columnWidthAddSpacing - minColumnSpacing);
}
private static int GetLowestColumnIndex(in ReadOnlySpan<double> columnHeights)
{
int columnIndex = 0;
double height = columnHeights[0];
for (int j = 1; j < columnHeights.Length; j++)
{
if (columnHeights[j] < height)
{
columnIndex = j;
height = columnHeights[j];
}
}
return columnIndex;
}
private static void OnMinItemWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UniformStaggeredLayout panel = (UniformStaggeredLayout)d;
panel.InvalidateMeasure();
}
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UniformStaggeredLayout panel = (UniformStaggeredLayout)d;
panel.InvalidateMeasure();
}
}

View File

@@ -0,0 +1,205 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Layout;
internal sealed class UniformStaggeredLayoutState
{
private readonly List<UniformStaggeredItem> items = [];
private readonly VirtualizingLayoutContext context;
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = [];
private double lastAverageHeight;
public UniformStaggeredLayoutState(VirtualizingLayoutContext context)
{
this.context = context;
}
public double ColumnWidth { get; internal set; }
public int NumberOfColumns
{
get => columnLayout.Count;
}
public double RowSpacing { get; internal set; }
internal void AddItemToColumn(UniformStaggeredItem item, int columnIndex)
{
if (!this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout))
{
columnLayout = [];
this.columnLayout[columnIndex] = columnLayout;
}
if (!columnLayout.Contains(item))
{
columnLayout.Add(item);
}
}
[SuppressMessage("", "CA2201")]
internal UniformStaggeredItem GetItemAt(int index)
{
if (index < 0)
{
throw new IndexOutOfRangeException();
}
if (index <= (items.Count - 1))
{
return items[index];
}
else
{
UniformStaggeredItem item = new(index);
items.Add(item);
return item;
}
}
internal UniformStaggeredColumnLayout GetColumnLayout(int columnIndex)
{
return columnLayout[columnIndex];
}
/// <summary>
/// Clear everything that has been calculated.
/// </summary>
internal void Clear()
{
// https://github.com/DGP-Studio/Snap.Hutao/issues/1079
// The first element must be force refreshed otherwise
// it will use the old one realized
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
// Now we need to refresh the first element of each column
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
// Finally we need to refresh the whole layout when we reset
if (context.ItemCount > 0)
{
for (int i = 0; i < context.ItemCount; i++)
{
RecycleElementAt(i);
}
}
columnLayout.Clear();
items.Clear();
}
/// <summary>
/// Clear the layout columns so they will be recalculated.
/// </summary>
internal void ClearColumns()
{
columnLayout.Clear();
}
/// <summary>
/// Gets the estimated height of the layout.
/// </summary>
/// <returns>The estimated height of the layout.</returns>
/// <remarks>
/// If all of the items have been calculated then the actual height will be returned.
/// If all of the items have not been calculated then an estimated height will be calculated based on the average height of the items.
/// </remarks>
internal double GetHeight()
{
double desiredHeight = columnLayout.Values.Max(c => c.Height);
int itemCount = columnLayout.Values.Sum(c => c.Count);
if (itemCount == context.ItemCount)
{
return desiredHeight;
}
double averageHeight = 0;
foreach ((_, UniformStaggeredColumnLayout layout) in columnLayout)
{
averageHeight += layout.Height / layout.Count;
}
averageHeight /= columnLayout.Count;
double estimatedHeight = (averageHeight * context.ItemCount) / columnLayout.Count;
if (estimatedHeight > desiredHeight)
{
desiredHeight = estimatedHeight;
}
if (Math.Abs(desiredHeight - lastAverageHeight) < 5)
{
return lastAverageHeight;
}
lastAverageHeight = desiredHeight;
return desiredHeight;
}
internal void RecycleElementAt(int index)
{
UIElement element = context.GetOrCreateElementAt(index);
context.RecycleElement(element);
}
internal void RemoveFromIndex(int index)
{
if (index >= items.Count)
{
// Item was added/removed but we haven't realized that far yet
return;
}
int numToRemove = items.Count - index;
items.RemoveRange(index, numToRemove);
foreach ((_, UniformStaggeredColumnLayout layout) in columnLayout)
{
Span<UniformStaggeredItem> layoutSpan = CollectionsMarshal.AsSpan(layout);
for (int i = 0; i < layoutSpan.Length; i++)
{
if (layoutSpan[i].Index >= index)
{
numToRemove = layoutSpan.Length - i;
layout.RemoveRange(i, numToRemove);
break;
}
}
}
}
internal void RemoveRange(int startIndex, int endIndex)
{
for (int i = startIndex; i <= endIndex; i++)
{
if (i > items.Count)
{
break;
}
ref readonly UniformStaggeredItem item = ref CollectionsMarshal.AsSpan(items)[i];
item.Height = 0;
item.Top = 0;
// We must recycle all elements to ensure that it gets the correct context
RecycleElementAt(i);
}
foreach ((_, UniformStaggeredColumnLayout layout) in columnLayout)
{
Span<UniformStaggeredItem> layoutSpan = CollectionsMarshal.AsSpan(layout);
for (int i = 0; i < layoutSpan.Length; i++)
{
if ((startIndex <= layoutSpan[i].Index) && (layoutSpan[i].Index <= endIndex))
{
int numToRemove = layoutSpan.Length - i;
layout.RemoveRange(i, numToRemove);
break;
}
}
}
}
}

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