Compare commits

..

154 Commits

Author SHA1 Message Date
DismissedLight
e50c1b9184 monster wiki fullfilled 2023-02-25 19:12:38 +08:00
DismissedLight
54535cd822 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-02-24 13:54:47 +08:00
DismissedLight
ece2737633 avatar & weapon level slider 2023-02-24 13:54:43 +08:00
Masterain
4b012424b9 Update bug-report.yml 2023-02-23 11:22:24 -08:00
DismissedLight
6d66af6c84 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-02-20 19:39:21 +08:00
DismissedLight
027874d4cf adjust avatar reliquary score weight 2023-02-20 19:38:48 +08:00
Masterain
3001936ab3 Update README.md
- optimze image size
- update i18n URL
2023-02-20 01:03:29 -08:00
DismissedLight
400e097fa7 support language switch 2023-02-20 16:04:23 +08:00
DismissedLight
ffce055d75 Merge pull request #539 from DGP-Studio/l10n_main
New Crowdin updates
2023-02-20 14:40:11 +08:00
Masterain
a820c41ad7 New translations SH.resx (Korean) 2023-02-19 22:34:43 -08:00
Masterain
485010c895 New translations SH.resx (English) 2023-02-19 22:34:42 -08:00
Masterain
5feddf566e New translations SH.resx (Chinese Traditional) 2023-02-19 22:34:41 -08:00
Masterain
cdfe306b16 New translations SH.resx (Russian) 2023-02-19 22:34:40 -08:00
Masterain
189c61ddea New translations SH.resx (Japanese) 2023-02-19 22:34:39 -08:00
Masterain
0ac7d6e94d Update PublishDistribution.yml 2023-02-18 02:18:40 -08:00
DismissedLight
042e3b5747 fix #527 2023-02-18 12:51:42 +08:00
DismissedLight
0372f1a8e3 fix #525 2023-02-18 12:50:10 +08:00
DismissedLight
08a630fd43 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-02-17 13:50:07 +08:00
DismissedLight
3781cad896 make all type internal 2023-02-17 13:49:37 +08:00
Masterain
78bc2f09d5 Create SECURITY.md 2023-02-15 02:17:43 -08:00
DismissedLight
8c52921b3e refactor codebase 2023-02-14 14:30:59 +08:00
DismissedLight
605aecb216 remove strings 2023-02-13 14:51:18 +08:00
DismissedLight
e3e124d52f Merge pull request #500 from DGP-Studio/l10n_main
New Crowdin updates
2023-02-13 14:48:40 +08:00
Masterain
0866e1947b New translations SH.resx (English) 2023-02-12 22:47:53 -08:00
Masterain
dbcb7dd879 New translations SH.resx (Chinese Traditional) 2023-02-12 22:47:52 -08:00
Masterain
44687dd87b New translations SH.resx (English) 2023-02-12 21:59:02 -08:00
Masterain
1a209f6c8d New translations SH.resx (Chinese Traditional) 2023-02-12 21:59:01 -08:00
Masterain
8633b78725 New translations SH.resx (Russian) 2023-02-12 21:59:00 -08:00
Masterain
2e20701c6c New translations SH.resx (Japanese) 2023-02-12 21:58:59 -08:00
DismissedLight
9c4d4cda1e fix localization 2023-02-13 13:51:15 +08:00
DismissedLight
a32481980b code style [skip ci] 2023-02-12 16:18:17 +08:00
DismissedLight
b5577e76a5 add hint for #493 2023-02-12 16:15:30 +08:00
DismissedLight
6c2ff9b3c9 fix process name detection 2023-02-11 18:32:00 +08:00
DismissedLight
818365b816 fix #486 2023-02-11 12:32:00 +08:00
DismissedLight
d7dd8c6f0d code style 2023-02-10 16:07:01 +08:00
DismissedLight
faad104e0e fix import thread issue 2023-02-10 11:57:55 +08:00
DismissedLight
2f6ee75f80 Merge pull request #479 from DGP-Studio/l10n_main
New Crowdin updates
2023-02-09 20:32:35 +08:00
Masterain
34f319bdac New translations SH.resx (English) 2023-02-09 04:32:02 -08:00
Masterain
f242808768 New translations SH.resx (Chinese Traditional) 2023-02-09 04:32:01 -08:00
DismissedLight
98f18f91d8 use WScript.Shell to run scheduled tasks 2023-02-09 19:23:24 +08:00
Masterain
0fd1f6959a New translations SH.resx (English) 2023-02-08 20:27:42 -08:00
Masterain
ba46ed64db New translations SH.resx (Chinese Traditional) 2023-02-08 20:27:41 -08:00
Masterain
0fb8312605 New translations SH.resx (Russian) 2023-02-08 20:27:40 -08:00
Masterain
b722554950 New translations SH.resx (Japanese) 2023-02-08 20:27:39 -08:00
DismissedLight
165c33ef2c fix translation 2023-02-09 12:26:42 +08:00
Masterain
54bb3d634b New translations SH.resx (English) 2023-02-08 20:26:02 -08:00
Masterain
629975480a New translations SH.resx (Chinese Traditional) 2023-02-08 20:26:01 -08:00
Masterain
5a36448c23 New translations SH.resx (Russian) 2023-02-08 20:26:00 -08:00
Masterain
80a6aaab46 New translations SH.resx (Japanese) 2023-02-08 20:25:58 -08:00
Masterain
6c83cd3da5 Update azure-pipelines.yml for Azure Pipelines 2023-02-07 21:54:23 -08:00
DismissedLight
e60a04a2bc impl #117 2023-02-08 12:28:31 +08:00
DismissedLight
aec483510f fix #460 2023-02-08 10:07:10 +08:00
DismissedLight
c245fe654e add gacha import validation 2023-02-07 16:57:53 +08:00
DismissedLight
898d95bb1d add more globalization strings 2023-02-07 15:36:50 +08:00
DismissedLight
1df22e5b75 fix L10n issues 2023-02-07 14:44:36 +08:00
DismissedLight
332e09fef0 fix #439 2023-02-07 13:41:53 +08:00
DismissedLight
2a77daf2ca Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-02-07 10:21:51 +08:00
DismissedLight
8a47ea8727 fix enka api 2023-02-07 10:21:36 +08:00
DismissedLight
b3937ac810 Merge pull request #453 from DGP-Studio/l10n_main
New Crowdin updates
2023-02-06 16:49:06 +08:00
Masterain
ed5c52dc63 New translations SH.resx (English) 2023-02-06 00:44:03 -08:00
Masterain
461d139602 New translations SH.resx (English) 2023-02-05 22:49:55 -08:00
Masterain
164ec2af33 New translations SH.resx (Chinese Traditional) 2023-02-05 22:49:54 -08:00
Masterain
e30523c621 New translations SH.resx (Russian) 2023-02-05 22:49:53 -08:00
Masterain
11d0405102 New translations SH.resx (Japanese) 2023-02-05 22:49:52 -08:00
DismissedLight
a1c0b4f830 fix zh-cn showcase 2 [skip ci] 2023-02-06 14:46:04 +08:00
Masterain
e476ed5960 New translations SH.resx (English) 2023-02-05 22:21:50 -08:00
Masterain
ffc999360d New translations SH.resx (Chinese Traditional) 2023-02-05 22:21:49 -08:00
Masterain
84058011c7 New translations SH.resx (Russian) 2023-02-05 22:21:48 -08:00
Masterain
c18e0c40c5 New translations SH.resx (Japanese) 2023-02-05 22:21:48 -08:00
DismissedLight
ad78515094 fix zh-cn task [skip ci] 2023-02-06 14:20:10 +08:00
Masterain
38367a090d New translations SH.resx (English) 2023-02-05 22:12:29 -08:00
Masterain
ce30f609fb New translations SH.resx (Chinese Traditional) 2023-02-05 22:12:28 -08:00
Masterain
f4b9cc7c48 New translations SH.resx (Russian) 2023-02-05 22:12:27 -08:00
Masterain
7c2212f44c New translations SH.resx (Japanese) 2023-02-05 22:12:26 -08:00
DismissedLight
95eddef457 fix zh-cn showcase 2023-02-06 14:11:43 +08:00
Masterain
02447bc966 New translations SH.resx (English) 2023-02-05 21:49:52 -08:00
Masterain
fb88e33d16 New translations SH.resx (Chinese Traditional) 2023-02-05 21:49:51 -08:00
Masterain
5fa36416ef New translations SH.resx (Russian) 2023-02-05 21:49:50 -08:00
Masterain
7076caaa5d New translations SH.resx (Japanese) 2023-02-05 21:49:49 -08:00
DismissedLight
b7b1155cfc adjust achievement UI 2023-02-06 13:45:41 +08:00
Masterain
6351f2b460 Update SH.resx
- fix typo in based language
2023-02-05 20:09:28 -08:00
DismissedLight
35ac2f33ba Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-02-05 21:06:45 +08:00
DismissedLight
f8ff1988bb update readme 2023-02-05 21:06:41 +08:00
Masterain
907d70ba71 Update azure-pipelines.yml for Azure Pipelines
[skip ci]
2023-02-05 05:04:47 -08:00
Masterain
a5bdc17712 Update azure-pipelines.yml
[skip ci]
2023-02-05 04:50:00 -08:00
Masterain
f078d92f33 Update Crowdin configuration file 2023-02-05 04:46:19 -08:00
DismissedLight
f2d4f0f1d3 locale start 2023-02-05 19:52:00 +08:00
DismissedLight
fcde9b21ae fix #442 2023-02-03 20:05:32 +08:00
DismissedLight
24f09861fd locale zh-cn phase 2 2023-02-02 20:48:48 +08:00
DismissedLight
47708adc83 update readme 2023-02-02 16:35:48 +08:00
DismissedLight
79a254235a Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-02-01 20:28:40 +08:00
DismissedLight
d9bcb3b16b locale zh-cn 2023-02-01 20:28:32 +08:00
Masterain
cf7dd548a2 Update network-issue.yml 2023-01-30 17:34:07 -08:00
DismissedLight
04deeb7086 Create FUNDING.yml 2023-01-30 19:26:25 +08:00
DismissedLight
9fb2da41cd store migration 2023-01-30 16:22:54 +08:00
DismissedLight
bb01f3a3cb fix package convert issue 2023-01-30 10:43:05 +08:00
DismissedLight
f7f2d9c867 fix #406 2023-01-28 20:03:37 +08:00
DismissedLight
01b7e58b3e fix convert cache 2023-01-27 16:51:43 +08:00
DismissedLight
2518ae0b90 package convert impl 2023-01-27 11:22:25 +08:00
DismissedLight
7d4a8cdcd9 fix empty statistics [skip ci] 2023-01-23 13:06:56 +08:00
DismissedLight
623893e00e remove visual transition gap in gacha log initialization 2023-01-23 12:58:00 +08:00
Masterain
0d34c81bcf Merge pull request #388 from wordlesswind/patch-1
Update version information and fix broken links
2023-01-22 01:07:49 -08:00
清靈語
5f3d0126b3 Update version information 2023-01-22 12:51:57 +08:00
DismissedLight
5d1fe3f38a move dispatcher queue to thread helper 2023-01-21 13:14:54 +08:00
DismissedLight
c810ffa625 remove unnecessary converters 2023-01-20 17:30:16 +08:00
DismissedLight
ee70205245 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-01-20 15:47:18 +08:00
DismissedLight
06c8b347d3 Announcement Viewer 2023-01-20 15:47:05 +08:00
Masterain
5c6ab1dee9 Update azure-pipelines.yml for Azure Pipelines 2023-01-19 15:23:50 -08:00
DismissedLight
ad440e0561 fix #377 2023-01-19 14:55:53 +08:00
DismissedLight
ca56d8c636 remove async relay command factory 2023-01-18 15:29:22 +08:00
Masterain
da0ee0cca6 Update PublishDistribution.yml
[skip ci]
2023-01-16 13:35:57 -08:00
Masterain
5d00d9cc0d Update azure-pipelines.yml for Azure Pipelines
[force ci]
2023-01-16 13:15:05 -08:00
Masterain
e8b27e6655 Update azure-pipelines.yml for Azure Pipelines 2023-01-16 13:05:55 -08:00
DismissedLight
0ac79012d1 fix #368 2023-01-16 18:12:12 +08:00
DismissedLight
bb2665b75e Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-01-16 14:27:45 +08:00
DismissedLight
d22ac39c1d fix dupe download items [skip ci] 2023-01-16 14:27:31 +08:00
Masterain
a312603d61 Update azure-pipelines.yml for Azure Pipelines 2023-01-15 22:22:34 -08:00
DismissedLight
0732ea0e06 replace font 2023-01-16 14:10:28 +08:00
Masterain
e4d2b3055c Update azure-pipelines.yml for Azure Pipelines
[skip ci]
2023-01-14 17:28:43 -08:00
Masterain
5668931230 Update PublishDistribution.yml
[skip ci]
2023-01-14 17:08:50 -08:00
DismissedLight
5126337138 code style [skip ci] 2023-01-12 19:42:45 +08:00
DismissedLight
4d634d3264 improve concurrent execution 2023-01-12 19:38:06 +08:00
Masterain
15a69fd0de Delete PrereleaseDistribution.yml 2023-01-11 20:42:42 -08:00
Masterain
c232891fe7 Update azure-pipelines.yml for Azure Pipelines 2023-01-11 20:41:03 -08:00
Masterain
c35c2a5700 Update workflows 2023-01-11 19:36:23 -08:00
Masterain
42305094f8 Update network-issue.yml 2023-01-11 16:12:16 -08:00
DismissedLight
9ef48ab05c fix crashes 2023-01-11 16:02:14 +08:00
DismissedLight
eec010870a improve web request experience 2023-01-11 13:11:09 +08:00
DismissedLight
a24fbf535d fix up oversea api style 2023-01-10 15:52:10 +08:00
DismissedLight
f7bd184a3c Merge pull request #352 from solacens/globalization/gotcha-histories
[Globalization] Read gotcha history from input of url or from cache/data_2
2023-01-10 15:22:06 +08:00
DismissedLight
267f285101 fix up oversea launcher support 2023-01-10 15:05:21 +08:00
solacens
2a1e77a9db add intl version gatcha history support 2023-01-10 17:46:29 +11:00
DismissedLight
abdc8e2e9f Merge pull request #334 from solacens/globalization/support-intl-version-game-launching
[Globalization] Support intl version game launching
2023-01-10 14:01:07 +08:00
solacens
64f1af293b merge main 2023-01-10 16:36:40 +11:00
DismissedLight
e0336d6b30 fix content dialog thread issue 2023-01-10 10:21:32 +08:00
DismissedLight
23f3e5df77 ContentDialogFactory 2023-01-09 12:15:11 +08:00
DismissedLight
4a027a8d3f remove filesystem context 2023-01-08 14:54:16 +08:00
DismissedLight
80459708a7 fix panel selector global group name 2023-01-08 12:32:43 +08:00
DismissedLight
650b67bea0 download static resource at startup 2023-01-07 18:27:45 +08:00
Masterain
18b3d23b1c Update azure-pipelines.yml for Azure Pipelines
- optimize CI logic for RPs
2023-01-03 19:17:14 -08:00
solacens
bf08ffa89e Missing changes 2023-01-03 12:20:13 +11:00
DismissedLight
915b1aae32 fix pipeline 2023-01-02 16:50:01 +08:00
DismissedLight
71a1bdc173 add empty page template view 2023-01-02 15:09:53 +08:00
solacens
af4180bdeb Add GenshinImpact.exe to check game running 2023-01-01 23:14:35 +11:00
solacens
a70593c529 Support international version game launching 2023-01-01 22:45:10 +11:00
DismissedLight
810f8704e6 use WinUICommunity.SettingsUI 2023-01-01 14:11:28 +08:00
DismissedLight
0165c03ae6 spiral abyss view 2023-01-01 13:38:27 +08:00
DismissedLight
423188c16a code style 2022-12-29 15:43:17 +08:00
DismissedLight
5fb935635b Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2022-12-29 15:03:33 +08:00
DismissedLight
761049ec17 cultivation improvement 2022-12-29 15:03:21 +08:00
Masterain
3f8c8874f3 Update azure-pipelines.yml for Azure Pipelines 2022-12-28 20:39:16 -08:00
Masterain
b8f354bbc7 Merge pull request #329 from Masterain98/main
devops improvements
2022-12-28 20:35:18 -08:00
Masterain
d45c40d4d7 Update bug-report.yml
add missing dropdown option
2022-12-28 20:19:48 -08:00
Masterain
6b309c4886 Update azure-pipelines.yml
add CI exception
2022-12-28 20:18:29 -08:00
872 changed files with 34262 additions and 13063 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: https://afdian.net/a/DismissedLight

View File

@@ -15,16 +15,19 @@ body:
description: |-
请确保你已完整执行检查清单,否则你的 Issue 可能会被忽略
options:
- label: 我已完整阅读[胡桃工具箱文档](https://hut.ao/FAQ/most-frequent-questions.html),并认为我的问题没有在文档中得到解答
- label: 我已完整阅读[胡桃工具箱文档](https://hut.ao/advanced/FAQ.html),并认为我的问题没有在文档中得到解答
required: true
- 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)
required: true
- label: 我确认没有其他人已经提出了相同或类似的问题
- label: **通过搜索功能**确认没有其他人已经提出了相同或类似的问题
required: true
- label: 会在下方的表单中附上充足的信息以帮助开发人员确定问题
- label: 明白上述的勾选项是**一个有助于快速排查问题的检查清单**,而不是随手确认的选项
required: true
- type: input
@@ -33,7 +36,7 @@ body:
label: Windows 版本
description: |
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
placeholder: 22000.556
placeholder: 22621.1105
validations:
required: true
@@ -42,7 +45,7 @@ body:
attributes:
label: Snap Hutao 版本
description: 在应用标题,应用程序的设置界面中靠下的位置可以找到
placeholder: 1.1.0
placeholder: 1.4.15.0
validations:
required: true
@@ -68,6 +71,7 @@ body:
- 游戏启动器
- 实时便笺
- 养成计算器
- 用户面板
- 文件缓存
- 祈愿记录
- 玩家查询
@@ -95,14 +99,4 @@ body:
description: 详细的描述你期望发生的行为,突出与目前(可能不正确的)行为的不同
validations:
required: false
- type: textarea
id: logs
attributes:
label: 相关的崩溃日志
description: |
在资源管理器中直接输入`%userprofile%/Documents/Hutao`即可进入文件夹
如果应用程序崩溃了,请将`log.db` 文件上传,文件包含了敏感信息,谨慎上传
如果这个表单是关于导入祈愿记录的问题,请包含你导入的`Json`文件
> **务必不要上传`user.db`文件,该文件包含你的帐号敏感信息**

View File

@@ -14,7 +14,7 @@ body:
id: back
attributes:
label: 背景与动机
description: 添加此功能的理由
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
validations:
required: true

View File

@@ -21,8 +21,8 @@ body:
**在填写下面的问题之前请先使用我们的网络诊断工具**
**这个工具将会生成一份报告,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
- 你可以点击下面的链接以下载网络诊断工具:
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-tool.exe)
- [GitHub](https://github.com/DGP-Studio/Snap.Hutao/files/10081999/network-diagnosis-hutao.zip)
- [胡桃资源站](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)
validations:
required: true
@@ -41,7 +41,7 @@ body:
id: user-isp
attributes:
label: 你的运营商
description: 中国用户请精确到省级行政区,海外地区请精确到国家
description: 海外用户请选其它
options:
- 中国电信
- 中国联通
@@ -60,7 +60,9 @@ body:
- 完全无法连接服务器
- 连接速度慢
- 获取到了不正确的页面或数据
- 图片下载错误(429 Error
- 客户端提示 429 Error
- 客户端图片下载错误
- 客户端图片预下载错误
- 其它
validations:
required: true

View File

@@ -2,7 +2,7 @@ name: PublishDistribution
on:
release:
types: [published]
types: [released]
workflow_dispatch:
@@ -16,14 +16,14 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v3
# Download Publish.zip
# Download Assets
- name: Download Release
timeout-minutes: 5
uses: robinraju/release-downloader@v1.5
uses: robinraju/release-downloader@v1.7
with:
repository: "DGP-Studio/Snap.Hutao"
latest: true
fileName: "*.zip"
fileName: "*.msix"
out-file-path: ./release-download
# Upload to Drive
@@ -38,4 +38,14 @@ jobs:
$RCCONF
EOF
rclone copy ./release-download/* dgpODCN:/snaphutao/Releases/
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

3
.gitignore vendored
View File

@@ -4,9 +4,6 @@
.vs/
src/Snap.Hutao/SettingsUI/bin
src/Snap.Hutao/SettingsUI/obj
src/Snap.Hutao/Snap.Hutao/bin/
src/Snap.Hutao/Snap.Hutao/obj/
src/Snap.Hutao/Snap.Hutao/Snap.Hutao_TemporaryKey.pfx

View File

@@ -1,11 +1,14 @@
# [Snap.Hutao](https://hut.ao)
> 唷,找本堂主有何贵干呀?
![](https://repository-images.githubusercontent.com/482734649/5f8cf574-2ef0-43e9-aa8d-6cf094b54dd9)
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
## 下载使用
[<img src="https://get.microsoft.com/images/zh-cn%20light.svg" width="30%" height="30%">](https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52)
# 特别感谢
## 贡献
### 原神组织与个人
* [向我们提交 PR](https://github.com/DGP-Studio/Snap.Hutao/pulls)
* [在 Crowdin 上进行本地化](https://translate.hut.ao/)
## 特别感谢
* [HolographicHat](https://github.com/HolographicHat)
* [UIGF organization](https://uigf.org)
@@ -28,4 +31,14 @@
* [microsoft/vs-threading](https://github.com/microsoft/vs-threading)
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
* [WinUICommunity/SettingsUI](https://github.com/WinUICommunity/SettingsUI)
### 支撑项目
* [Snap.Hutao.Server](https://github.com/DGP-Studio/Snap.Hutao.Server)
* [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)
* [Snap.Data.Mapper](https://github.com/DGP-Studio/Snap.Data.Mapper)
## 近期活跃数据
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)

12
SECURITY.md Normal file
View File

@@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| >=1.4.11 | :white_check_mark: |
| <1.4.11 | :x: |
## Reporting a Vulnerability
Please [open an issue](https://github.com/DGP-Studio/Snap.Hutao/issues/new/choose)

View File

@@ -8,7 +8,28 @@
# 6. Run
trigger:
- main
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
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:
vmImage: 'windows-2022'
@@ -19,7 +40,7 @@ variables:
project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
buildPlatform: 'x64'
buildConfiguration: 'Release'
build_date: $[ format('{0:yyyy}.{0:MM}.{0:dd}', pipeline.startTime) ]
build_date: $[ format('{0:yyyy}.{0:M}.{0:d}', pipeline.startTime) ]
steps:
@@ -72,7 +93,8 @@ steps:
"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/Properties/PublisherDisplayName":"DGP Studio CI",
"Package/Applications/Application/uap:VisualElements/@DisplayName": "胡桃 Alpha"
}
- task: CmdLine@2
@@ -105,18 +127,21 @@ steps:
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
- task: MsixSigning@1
name: signMsix
displayName: Sign MSIX package
inputs:
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).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: PublishPipelineArtifact@1
# displayName: 'Upload Output'
# inputs:
# targetPath: '$(Build.ArtifactStagingDirectory)/'
# artifact: 'Output'
# publishLocation: 'pipeline'
- task: DownloadSecureFile@1
name: cerFile
@@ -135,13 +160,26 @@ steps:
title: '$(build_date).$(rev_number)'
releaseNotesSource: 'inline'
releaseNotesInline: |
## 提示 (Hint)
发布版本由 CI 程序自动打包生成,属于 `Alpha` 测试版,仅用于开发调试和内部测试用途。使用该版本可能存在意料之外的风险,请仅在有明确用途的情况下使用该版本。
This release is a Alpha Testing version generated by CI program automatically in a purpose of debugging and interal testing. Using this release may have unexpected risk, please only use it when you know what you are doing.
## 普通用户请勿下载
该版本由 CI 程序自动打包生成 `Alpha` 测试版本,**仅供开发者测试使用**
普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
assets: |
$(Build.ArtifactStagingDirectory)/*
$(cerFile.secureFilePath)
isPreRelease: true
changeLogCompareToRelease: 'lastFullRelease'
changeLogType: 'commitBased'
changeLogType: 'commitBased'
- task: DownloadSecureFile@1
name: RcloneConfigFile
displayName: Download Rclone Config
inputs:
secureFile: 'rclone.conf'
- task: rclone@1
displayName: Upload CI via Rclone
inputs:
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix downloadDGPCN:/releases/Alpha/'
configPath: '$(RcloneConfigFile.secureFilePath)'

3
crowdin.yml Normal file
View File

@@ -0,0 +1,3 @@
files:
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.%locale%.resx

View File

@@ -10,13 +10,13 @@ csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = false:silent
csharp_style_expression_bodied_indexers = false:silent
csharp_style_expression_bodied_accessors = when_on_single_line:silent
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = when_on_single_line:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = false:warning
csharp_style_var_elsewhere = false:warning
csharp_prefer_simple_using_statement = false:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = file_scoped:silent
@@ -24,11 +24,11 @@ csharp_prefer_static_local_function = false:suggestion
[*.{cs,vb}]
end_of_line = crlf
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
dotnet_code_quality_unused_parameters = non_public:suggestion
dotnet_style_readonly_field = true:suggestion
indent_size = 4
@@ -165,6 +165,7 @@ dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.VSTHRD111.severity = suggestion
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
[*.vb]
#### 命名样式 ####

View File

@@ -1,80 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
namespace SettingsUI.Controls;
public class CheckBoxWithDescriptionControl : CheckBox
{
private readonly CheckBoxWithDescriptionControl _checkBoxSubTextControl;
public CheckBoxWithDescriptionControl()
{
_checkBoxSubTextControl = this;
Loaded += CheckBoxSubTextControl_Loaded;
}
protected override void OnApplyTemplate()
{
Update();
base.OnApplyTemplate();
}
private void Update()
{
if (!string.IsNullOrEmpty(Header))
{
AutomationProperties.SetName(this, Header);
}
}
private void CheckBoxSubTextControl_Loaded(object sender, RoutedEventArgs e)
{
StackPanel panel = new() { Orientation = Orientation.Vertical };
// Add text box only if the description is not empty. Required for additional plugin options.
if (!string.IsNullOrWhiteSpace(Description))
{
panel.Children.Add(new TextBlock() { Margin = new Thickness(0, 10, 0, 0), Text = Header });
//Style = (Style)Application.Current.Resources["SecondaryIsEnabledTextBlockStyle"]
panel.Children.Add(new IsEnabledTextBlock() { Text = Description });
}
else
{
panel.Children.Add(new TextBlock() { Margin = new Thickness(0, 0, 0, 0), Text = Header });
}
_checkBoxSubTextControl.Content = panel;
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(string),
typeof(CheckBoxWithDescriptionControl),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description",
typeof(object),
typeof(CheckBoxWithDescriptionControl),
new PropertyMetadata(default(string)));
[Localizable(true)]
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
[Localizable(true)]
public string Description
{
get => (string)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
}

View File

@@ -1,50 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
namespace SettingsUI.Controls;
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
public class IsEnabledTextBlock : Control
{
public IsEnabledTextBlock()
{
DefaultStyleKey = typeof(IsEnabledTextBlock);
}
protected override void OnApplyTemplate()
{
IsEnabledChanged -= IsEnabledTextBlock_IsEnabledChanged;
SetEnabledState();
IsEnabledChanged += IsEnabledTextBlock_IsEnabledChanged;
base.OnApplyTemplate();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(IsEnabledTextBlock),
null);
[Localizable(true)]
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private void IsEnabledTextBlock_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetEnabledState();
}
private void SetEnabledState()
{
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
}
}

View File

@@ -1,38 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SettingsUI.Controls">
<Style TargetType="local:IsEnabledTextBlock">
<Setter Property="Foreground" Value="{ThemeResource DefaultTextForegroundThemeBrush}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:IsEnabledTextBlock">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Label.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="Label"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
FontFamily="{TemplateBinding FontFamily}"
Foreground="{TemplateBinding Foreground}"
TextWrapping="WrapWholeWords"
Text="{TemplateBinding Text}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:IsEnabledTextBlock" x:Key="SecondaryIsEnabledTextBlockStyle">
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}"/>
</Style>
</ResourceDictionary>

View File

@@ -1,156 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
namespace SettingsUI.Controls;
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplatePart(Name = PartIconPresenter, Type = typeof(ContentPresenter))]
[TemplatePart(Name = PartDescriptionPresenter, Type = typeof(ContentPresenter))]
public class Setting : ContentControl
{
private const string PartIconPresenter = "IconPresenter";
private const string PartDescriptionPresenter = "DescriptionPresenter";
private ContentPresenter? _iconPresenter;
private ContentPresenter? _descriptionPresenter;
private Setting? _setting;
public Setting()
{
DefaultStyleKey = typeof(Setting);
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(string),
typeof(Setting),
new PropertyMetadata(default(string), OnHeaderChanged));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description",
typeof(object),
typeof(Setting),
new PropertyMetadata(null, OnDescriptionChanged));
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
"Icon",
typeof(object),
typeof(Setting),
new PropertyMetadata(default(string), OnIconChanged));
public static readonly DependencyProperty ActionContentProperty = DependencyProperty.Register(
"ActionContent",
typeof(object),
typeof(Setting),
null);
[Localizable(true)]
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
[Localizable(true)]
public object Description
{
get => GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
public object Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public object ActionContent
{
get => GetValue(ActionContentProperty);
set => SetValue(ActionContentProperty, value);
}
protected override void OnApplyTemplate()
{
IsEnabledChanged -= Setting_IsEnabledChanged;
_setting = this;
_iconPresenter = (ContentPresenter)_setting.GetTemplateChild(PartIconPresenter);
_descriptionPresenter = (ContentPresenter)_setting.GetTemplateChild(PartDescriptionPresenter);
Update();
SetEnabledState();
IsEnabledChanged += Setting_IsEnabledChanged;
base.OnApplyTemplate();
}
private static void OnHeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Setting)d).Update();
}
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Setting)d).Update();
}
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Setting)d).Update();
}
private void Setting_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetEnabledState();
}
private void SetEnabledState()
{
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
}
private void Update()
{
if (_setting == null)
{
return;
}
if (_setting.ActionContent != null)
{
if (_setting.ActionContent.GetType() != typeof(Button))
{
// We do not want to override the default AutomationProperties.Name of a button. Its Content property already describes what it does.
if (!string.IsNullOrEmpty(_setting.Header))
{
AutomationProperties.SetName((UIElement)_setting.ActionContent, _setting.Header);
}
}
}
if (_setting._iconPresenter != null)
{
if (_setting.Icon == null)
{
_setting._iconPresenter.Visibility = Visibility.Collapsed;
}
else
{
_setting._iconPresenter.Visibility = Visibility.Visible;
}
}
if (_setting.Description == null)
{
_setting._descriptionPresenter!.Visibility = Visibility.Collapsed;
}
else
{
_setting._descriptionPresenter!.Visibility = Visibility.Visible;
}
}
}

View File

@@ -1,107 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:SettingsUI.Controls">
<Style TargetType="controls:Setting">
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="16" />
<Setter Property="Margin" Value="0,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:Setting">
<Grid x:Name="RootGrid"
CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
MinHeight="48">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="HeaderPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="DescriptionPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="IconPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- Icon -->
<ColumnDefinition Width="*"/>
<!-- Header and subtitle -->
<ColumnDefinition Width="Auto"/>
<!-- Action control -->
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="IconPresenter"
Content="{TemplateBinding Icon}"
HorizontalAlignment="Center"
FontSize="20"
IsTextScaleFactorEnabled="False"
Margin="2,0,18,0"
MaxWidth="20"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
VerticalAlignment="Center"/>
<StackPanel
VerticalAlignment="Center"
Grid.Column="1"
HorizontalAlignment="Stretch"
Margin="0,0,16,0">
<TextBlock
x:Name="HeaderPresenter"
Text="{TemplateBinding Header}"
VerticalAlignment="Center"
Foreground="{ThemeResource CardPrimaryForegroundBrush}" />
<ContentPresenter
x:Name="DescriptionPresenter"
Content="{TemplateBinding Description}"
FontSize="{StaticResource SecondaryTextFontSize}"
TextWrapping="WrapWholeWords"
Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource CaptionTextBlockStyle}">
<Style.Setters>
<Setter Property="TextWrapping" Value="WrapWholeWords"/>
</Style.Setters>
</Style>
<Style TargetType="HyperlinkButton" BasedOn="{StaticResource TextButtonStyle}">
<Style.Setters>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="0,0,0,0"/>
</Style.Setters>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
<ContentPresenter
x:Name="ContentPresenter"
Content="{TemplateBinding ActionContent}"
Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Right" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,37 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
namespace SettingsUI.Controls;
public partial class SettingExpander : Expander
{
public SettingExpander()
{
DefaultStyleKey = typeof(Expander);
Style = (Style)Application.Current.Resources["SettingExpanderStyle"];
RegisterPropertyChangedCallback(Expander.HeaderProperty, OnHeaderChanged);
}
private static void OnHeaderChanged(DependencyObject d, DependencyProperty dp)
{
SettingExpander self = (SettingExpander)d;
if (self.Header != null)
{
if (self.Header.GetType() == typeof(Setting))
{
Setting selfSetting = (Setting)self.Header;
selfSetting.Style = (Style)Application.Current.Resources["ExpanderHeaderSettingStyle"];
if (!string.IsNullOrEmpty(selfSetting.Header))
{
AutomationProperties.SetName(self, selfSetting.Header);
}
}
}
}
}

View File

@@ -1,101 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
namespace SettingsUI.Controls;
/// <summary>
/// Represents a control that can contain multiple settings (or other) controls
/// </summary>
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplatePart(Name = PartDescriptionPresenter, Type = typeof(ContentPresenter))]
public partial class SettingsGroup : ItemsControl
{
private const string PartDescriptionPresenter = "DescriptionPresenter";
private ContentPresenter? _descriptionPresenter;
private SettingsGroup? _settingsGroup;
public SettingsGroup()
{
DefaultStyleKey = typeof(SettingsGroup);
}
[Localizable(true)]
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(string),
typeof(SettingsGroup),
new PropertyMetadata(default(string)));
[Localizable(true)]
public object Description
{
get => GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description",
typeof(object),
typeof(SettingsGroup),
new PropertyMetadata(null, OnDescriptionChanged));
protected override void OnApplyTemplate()
{
IsEnabledChanged -= SettingsGroup_IsEnabledChanged;
_settingsGroup = this;
_descriptionPresenter = (ContentPresenter)_settingsGroup.GetTemplateChild(PartDescriptionPresenter);
SetEnabledState();
IsEnabledChanged += SettingsGroup_IsEnabledChanged;
base.OnApplyTemplate();
}
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((SettingsGroup)d).Update();
}
private void SettingsGroup_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetEnabledState();
}
private void SetEnabledState()
{
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
}
private void Update()
{
if (_settingsGroup == null)
{
return;
}
if (_settingsGroup.Description == null)
{
_settingsGroup._descriptionPresenter!.Visibility = Visibility.Collapsed;
}
else
{
_settingsGroup._descriptionPresenter!.Visibility = Visibility.Visible;
}
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new SettingsGroupAutomationPeer(this);
}
}

View File

@@ -1,71 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:SettingsUI.Controls">
<Style TargetType="controls:SettingsGroup">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="2"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsTabStop" Value="False" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:SettingsGroup">
<Grid HorizontalAlignment="Stretch">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="HeaderPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="DescriptionPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="HeaderPresenter"
Text="{TemplateBinding Header}"
Grid.Row="0"
Style="{ThemeResource BodyStrongTextBlockStyle}"
Margin="1,32,0,0"
AutomationProperties.HeadingLevel="Level2"/>
<ContentPresenter
x:Name="DescriptionPresenter"
Content="{TemplateBinding Description}"
TextWrapping="WrapWholeWords"
Margin="1,4,0,0"
Grid.Row="1"
Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource CaptionTextBlockStyle}">
<Style.Setters>
<Setter Property="TextWrapping" Value="WrapWholeWords"/>
</Style.Setters>
</Style>
<Style TargetType="HyperlinkButton" BasedOn="{StaticResource TextButtonStyle}">
<Style.Setters>
<Setter Property="Padding" Value="0,0,0,0"/>
</Style.Setters>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
<ItemsPresenter Grid.Row="2" Margin="0,8,0,0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,21 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml.Automation.Peers;
namespace SettingsUI.Controls;
public class SettingsGroupAutomationPeer : FrameworkElementAutomationPeer
{
public SettingsGroupAutomationPeer(SettingsGroup owner)
: base(owner)
{
}
protected override string GetNameCore()
{
SettingsGroup? selectedSettingsGroup = (SettingsGroup)Owner;
return selectedSettingsGroup.Header;
}
}

View File

@@ -1,50 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0-windows10.0.18362.0</TargetFrameworks>
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
<RootNamespace>SettingsUI</RootNamespace>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\IsEnabledTextBlock\IsEnabledTextBlock.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\SettingsGroup\SettingsGroup.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\Setting\Setting.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\Button.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\Common.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\TextBlock.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Themes\Colors.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Themes\Generic.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Themes\SettingsExpanderStyles.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Themes\SettingsUI.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>

View File

@@ -1,700 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:contract7NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,7)"
xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)">
<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,5,16,6"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style x:Key="HyperlinkButtonStyle" TargetType="HyperlinkButton">
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
</Style>
<Style x:Key="TextButtonStyle" TargetType="ButtonBase">
<Setter Property="Background" Value="{ThemeResource HyperlinkButtonBackground}"/>
<Setter Property="Foreground" Value="{ThemeResource HyperlinkButtonForeground}"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ButtonBase">
<Grid
Margin="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="4">
<ContentPresenter
x:Name="Text"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
FontWeight="SemiBold"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushDisabled}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- This style overrides the default style so that all ToggleSwitches are right aligned, with the label on the left -->
<Style x:Key="ToggleSwitchSettingStyle" TargetType="ToggleSwitch">
<Setter Property="Foreground" Value="{ThemeResource ToggleSwitchContentForeground}"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="ManipulationMode" Value="System,TranslateX"/>
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
<Setter Property="FocusVisualMargin" Value="-7,-3,-7,-3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleSwitch">
<Grid
contract7Present:CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{ThemeResource ToggleSwitchTopHeaderMargin}"
VerticalAlignment="Top"
x:DeferLoadStrategy="Lazy"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="{ThemeResource ToggleSwitchHeaderForeground}"
IsHitTestVisible="False"
TextWrapping="Wrap"
Visibility="Collapsed"/>
<Grid
Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource ToggleSwitchPreContentMargin}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{ThemeResource ToggleSwitchPostContentMargin}"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12" MaxWidth="12"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid
x:Name="SwitchAreaGrid"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
Margin="0,5"
contract7NotPresent:CornerRadius="{StaticResource ControlCornerRadius}"
contract7Present:CornerRadius="{TemplateBinding CornerRadius}"
Background="{ThemeResource ToggleSwitchContainerBackground}"
Control.IsTemplateFocusTarget="True"/>
<ContentPresenter
x:Name="OffContentPresenter"
Grid.RowSpan="3"
Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding OffContent}"
ContentTemplate="{TemplateBinding OffContentTemplate}"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Opacity="0"/>
<ContentPresenter
x:Name="OnContentPresenter"
Grid.RowSpan="3"
Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding OnContent}"
ContentTemplate="{TemplateBinding OnContentTemplate}"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Opacity="0"/>
<Rectangle
x:Name="OuterBorder"
Grid.Row="1"
Grid.Column="2"
Width="40"
Height="20"
Fill="{ThemeResource ToggleSwitchFillOff}"
RadiusX="10"
RadiusY="10"
Stroke="{ThemeResource ToggleSwitchStrokeOff}"
StrokeThickness="{ThemeResource ToggleSwitchOuterBorderStrokeThickness}"/>
<Rectangle
x:Name="SwitchKnobBounds"
Grid.Row="1"
Grid.Column="2"
Width="40"
Height="20"
Fill="{ThemeResource ToggleSwitchFillOn}"
Opacity="0"
RadiusX="10"
RadiusY="10"
Stroke="{ThemeResource ToggleSwitchStrokeOn}"
StrokeThickness="{ThemeResource ToggleSwitchOnStrokeThickness}"/>
<Grid
x:Name="SwitchKnob"
Grid.Row="1"
Grid.Column="2"
Width="20"
Height="20"
HorizontalAlignment="Left">
<Border
x:Name="SwitchKnobOn"
Grid.Column="2"
Width="12"
Height="12"
Margin="0,0,1,0"
HorizontalAlignment="Center"
contract7Present:BackgroundSizing="OuterBorderEdge"
Background="{ThemeResource ToggleSwitchKnobFillOn}"
BorderBrush="{ThemeResource ToggleSwitchKnobStrokeOn}"
CornerRadius="7"
Opacity="0"
RenderTransformOrigin="0.5, 0.5">
<Border.RenderTransform>
<CompositeTransform/>
</Border.RenderTransform>
</Border>
<Rectangle
x:Name="SwitchKnobOff"
Grid.Column="2"
Width="12"
Height="12"
Margin="-1,0,0,0"
HorizontalAlignment="Center"
Fill="{ThemeResource ToggleSwitchKnobFillOff}"
RadiusX="7"
RadiusY="7"
RenderTransformOrigin="0.5, 0.5">
<Rectangle.RenderTransform>
<CompositeTransform/>
</Rectangle.RenderTransform>
</Rectangle>
<Grid.RenderTransform>
<TranslateTransform x:Name="KnobTranslateTransform"/>
</Grid.RenderTransform>
</Grid>
<Thumb
x:Name="SwitchThumb"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
AutomationProperties.AccessibilityView="Raw">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOff}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOff}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOff}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOn}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOn}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOn}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContainerBackground}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPointerOver}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPointerOver}"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPointerOver}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="SwitchKnobOn.HorizontalAlignment" Value="Right"/>
<Setter Target="SwitchKnobOn.Margin" Value="0,0,3,0"/>
<Setter Target="SwitchKnobOff.HorizontalAlignment" Value="Left"/>
<Setter Target="SwitchKnobOff.Margin" Value="3,0,0,0"/>
</VisualState.Setters>
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPressed}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPressed}"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPressed}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="17"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="17"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchHeaderForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffDisabled}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffDisabled}"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundDisabled}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ToggleStates">
<VisualStateGroup.Transitions>
<VisualTransition
x:Name="DraggingToOnTransition"
GeneratedDuration="0"
From="Dragging"
To="On">
<Storyboard>
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOnOffset}" TargetName="SwitchKnob"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition
x:Name="OnToDraggingTransition"
GeneratedDuration="0"
From="On"
To="Dragging">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition
x:Name="DraggingToOffTransition"
GeneratedDuration="0"
From="Dragging"
To="Off">
<Storyboard>
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOffOffset}" TargetName="SwitchKnob"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition
x:Name="OnToOffTransition"
GeneratedDuration="0"
From="On"
To="Off">
<Storyboard>
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOnToOffOffset}" TargetName="SwitchKnob"/>
</Storyboard>
</VisualTransition>
<VisualTransition
x:Name="OffToOnTransition"
GeneratedDuration="0"
From="Off"
To="On">
<Storyboard>
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOffToOnOffset}" TargetName="SwitchKnob"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Dragging"/>
<VisualState x:Name="Off"/>
<VisualState x:Name="On">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="KnobTranslateTransform"
Storyboard.TargetProperty="X"
To="20"
Duration="0"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ContentStates">
<VisualState x:Name="OffContent">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="OffContentPresenter"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Boolean>True</x:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="OnContent">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="OnContentPresenter"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Boolean>True</x:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,13 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:SettingsUI.Controls">
<Style x:Key="ListViewItemSettingStyle" TargetType="ListViewItem">
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Padding" Value="0,0,0,0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<Style BasedOn="{StaticResource DefaultCheckBoxStyle}" TargetType="controls:CheckBoxWithDescriptionControl"/>
</ResourceDictionary>

View File

@@ -1,20 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="OobeSubtitleStyle" TargetType="TextBlock">
<Setter Property="Margin" Value="0,16,0,0"/>
<Setter Property="Foreground" Value="{ThemeResource DefaultTextForegroundThemeBrush}"/>
<Setter Property="AutomationProperties.HeadingLevel" Value="Level3"/>
<Setter Property="FontFamily" Value="XamlAutoFontFamily"/>
<Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="LineStackingStrategy" Value="MaxHeight"/>
<Setter Property="TextLineBounds" Value="Full"/>
</Style>
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}"/>
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}"/>
</Style>
</ResourceDictionary>

View File

@@ -1,30 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="CardBackgroundFillColorDefaultBrush"/>
<StaticResource x:Key="CardBorderBrush" ResourceKey="CardStrokeColorDefaultBrush"/>
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="TextFillColorPrimaryBrush"/>
<SolidColorBrush x:Key="InfoBarInformationalSeverityBackgroundBrush" Color="#FF34424d"/>
<Color x:Key="InfoBarInformationalSeverityIconBackground">#FF5fb2f2</Color>
<Thickness x:Key="CardBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="CardBackgroundFillColorDefaultBrush"/>
<StaticResource x:Key="CardBorderBrush" ResourceKey="CardStrokeColorDefaultBrush"/>
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="TextFillColorPrimaryBrush"/>
<SolidColorBrush x:Key="InfoBarInformationalSeverityBackgroundBrush" Color="#FFd3e7f7"/>
<Color x:Key="InfoBarInformationalSeverityIconBackground">#FF0063b1</Color>
<Thickness x:Key="CardBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="SystemColorButtonFaceColorBrush"/>
<StaticResource x:Key="CardBorderBrush" ResourceKey="SystemColorButtonTextColorBrush"/>
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="SystemColorButtonTextColorBrush"/>
<SolidColorBrush x:Key="InfoBarInformationalSeverityBackgroundBrush" Color="#FF34424d"/>
<Color x:Key="InfoBarInformationalSeverityIconBackground">#FF5fb2f2</Color>
<Thickness x:Key="CardBorderThickness">2</Thickness>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

View File

@@ -1,9 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///SettingsUI/Controls/Setting/Setting.xaml" />
<ResourceDictionary Source="ms-appx:///SettingsUI/Controls/SettingsGroup/SettingsGroup.xaml" />
<ResourceDictionary Source="ms-appx:///SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -1,48 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:SettingsUI.Controls">
<!-- Thickness -->
<Thickness x:Key="ExpanderContentPadding">0</Thickness>
<Thickness x:Key="ExpanderSettingMargin">56, 8, 40, 8</Thickness>
<SolidColorBrush x:Key="ExpanderChevronPointerOverBackground">Transparent</SolidColorBrush>
<!-- Styles -->
<!-- Setting used in a Expander header -->
<Style x:Key="ExpanderHeaderSettingStyle" TargetType="controls:Setting">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="0,14,0,14"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<Thickness x:Key="ExpanderChevronMargin">0,0,8,0</Thickness>
<!-- Setting used in a Expander header -->
<Style x:Key="ExpanderContentSettingStyle" TargetType="controls:Setting">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0,1,0,0"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="Padding" Value="{StaticResource ExpanderSettingMargin}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<!-- Setting expander style -->
<Style x:Key="SettingExpanderStyle" TargetType="Expander">
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<Style x:Key="ExpanderSeparatorStyle" TargetType="Rectangle">
<Setter Property="Height" Value="1"/>
<Setter Property="Stroke" Value="{ThemeResource CardBorderBrush}"/>
</Style>
</ResourceDictionary>

View File

@@ -1,12 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/Common.xaml"/>
<ResourceDictionary Source="../Styles/TextBlock.xaml"/>
<ResourceDictionary Source="../Styles/Button.xaml"/>
<ResourceDictionary Source="../Themes/Colors.xaml"/>
<ResourceDictionary Source="../Themes/SettingsExpanderStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<x:Double x:Key="SettingActionControlMinWidth">240</x:Double>
</ResourceDictionary>

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Installer;
internal class Program
{
private const string AppxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Appx";
private const string AppxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock";
private const string ValueName = "AllowDevelopmentWithoutDevLicense";
public static async Task Main(string[] args)

View File

@@ -97,13 +97,13 @@ internal static partial class IocHttpClientConfiguration
switch (injectAsName)
{
case DefaultName:
lineBuilder.Append(@"DefaultConfiguration)");
lineBuilder.Append("DefaultConfiguration)");
break;
case XRpcName:
lineBuilder.Append(@"XRpcConfiguration)");
lineBuilder.Append("XRpcConfiguration)");
break;
case XRpc2Name:
lineBuilder.Append(@"XRpc2Configuration)");
lineBuilder.Append("XRpc2Configuration)");
break;
default:
throw new InvalidOperationException($"非法的HttpClientConfigration值: [{injectAsName}]");

View File

@@ -5,4 +5,4 @@
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:启用分析器发布跟踪", Justification = "<挂起>", Scope = "member", Target = "~F:Snap.Hutao.SourceGeneration.TodoAnalyzer.Descriptor")]
[assembly: SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:启用分析器发布跟踪", Justification = "<挂起>", Scope = "member", Target = "~F:Snap.Hutao.SourceGeneration.TypeInternalAnalyzer.typeInternalDescriptor")]

View File

@@ -25,4 +25,4 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,52 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
namespace Snap.Hutao.SourceGeneration;
/// <summary>
/// 高亮TODO
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class TodoAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Descriptor =
new("SH0001", "TODO 项尚未实现", "此 TODO 项需要实现", "Standard", DiagnosticSeverity.Info, true);
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get => ImmutableArray.Create(Descriptor); }
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxTreeAction(HandleSyntaxTree);
}
private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context)
{
SyntaxNode root = context.Tree.GetCompilationUnitRoot(context.CancellationToken);
foreach (SyntaxTrivia node in root.DescendantTrivia(descendIntoTrivia: true))
{
switch (node.Kind())
{
case SyntaxKind.SingleLineCommentTrivia:
case SyntaxKind.MultiLineCommentTrivia:
string text = node.ToString().ToLowerInvariant();
if (text.Contains("todo:"))
{
string hint = node.ToString().Substring(text.IndexOf("todo:") + 6);
DiagnosticDescriptor descriptor = new("SH0001", "TODO 项尚未实现", hint, "Standard", DiagnosticSeverity.Info, true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, node.GetLocation()));
}
break;
}
}
}
}

View File

@@ -0,0 +1,62 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
namespace Snap.Hutao.SourceGeneration;
/// <summary>
/// 类型应为内部分析器
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class TypeInternalAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor typeInternalDescriptor = new("SH001", "Type should be internal", "Type should be internal", "Quality", DiagnosticSeverity.Info, true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(typeInternalDescriptor);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(CompilationStart);
}
private void CompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSyntaxNodeAction(HandleSyntax<ClassDeclarationSyntax>, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeAction(HandleSyntax<InterfaceDeclarationSyntax>, SyntaxKind.InterfaceDeclaration);
context.RegisterSyntaxNodeAction(HandleSyntax<StructDeclarationSyntax>, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeAction(HandleSyntax<EnumDeclarationSyntax>, SyntaxKind.EnumDeclaration);
}
private void HandleSyntax<TSyntax>(SyntaxNodeAnalysisContext classSyntax)
where TSyntax : BaseTypeDeclarationSyntax
{
TSyntax syntax = (TSyntax)classSyntax.Node;
bool privateExists = false;
bool internalExists = false;
foreach(SyntaxToken token in syntax.Modifiers)
{
if (token.IsKind(SyntaxKind.PrivateKeyword))
{
privateExists = true;
}
if (token.IsKind(SyntaxKind.InternalKeyword))
{
internalExists = true;
}
}
if (!privateExists && !internalExists)
{
Location location = syntax.Identifier.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(typeInternalDescriptor, location);
classSyntax.ReportDiagnostic(diagnostic);
}
}
}

View File

@@ -10,12 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\SettingsUI.csproj", "{DCA5678C-896E-49FB-97A7-5A504A5CFF17}"
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.Installer", "Snap.Hutao.Installer\Snap.Hutao.Installer.csproj", "{CEC01691-F65E-4874-9AE2-F571369A7631}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,24 +48,8 @@ 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
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|arm64.ActiveCfg = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|arm64.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.ActiveCfg = Debug|x64
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.Build.0 = Debug|x64
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x86.ActiveCfg = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x86.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.Build.0 = Release|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.ActiveCfg = Release|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.Build.0 = Release|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.ActiveCfg = Release|x64
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.Build.0 = Release|x64
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.ActiveCfg = Release|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.Build.0 = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{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
@@ -84,22 +64,6 @@ Global
{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
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.ActiveCfg = Debug|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.Build.0 = Debug|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.ActiveCfg = Debug|x64
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.Build.0 = Debug|x64
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x86.ActiveCfg = Debug|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x86.Build.0 = Debug|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.Build.0 = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.ActiveCfg = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.Build.0 = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.ActiveCfg = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.Build.0 = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.ActiveCfg = Release|Any CPU
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -9,7 +9,7 @@
},
"pathSegment": {
"add": {
".*": [ ".cs" ]
".*": [ ".cs", ".resx" ]
}
},
"fileSuffixToExtension": {
@@ -19,11 +19,12 @@
},
"fileToFile": {
"add": {
".filenesting.json": [ "App.xaml.cs" ],
"app.manifest": [ "App.xaml.cs" ],
"Package.appxmanifest": [ "App.xaml.cs" ],
"GlobalUsing.cs": [ "Program.cs" ],
".filenesting.json": [ "Program.cs" ],
".editorconfig": [ "Program.cs" ]
"Package.appxmanifest": [ "App.xaml" ],
"Package.StoreAssociation.xml": [ "App.xaml" ],
".editorconfig": [ "Program.cs" ],
"GlobalUsing.cs": [ "Program.cs" ]
}
}
}

View File

@@ -10,25 +10,25 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<muxc:XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///SettingsUI/Themes/SettingsUI.xaml"/>
<ResourceDictionary Source="ms-appx:///SettingsUI/Themes/Generic.xaml"/>
<ResourceDictionary Source="Control/Theme/FontStyle.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"/>
<!-- IconFont -->
<FontFamily x:Key="SymbolThemeFontFamily">ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
<!-- InfoBar Resource -->
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
@@ -42,6 +42,27 @@
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!-- OpenPaneLength -->
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength2">268</x:Double>
<GridLength x:Key="CompatGridLength2">268</GridLength>
<!-- Brushes -->
<SolidColorBrush x:Key="AvatarPropertyAddValueBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
<!-- 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="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
<x:String x:Key="Sponsor_Afadian">https://afdian.net/a/DismissedLight</x:String>
<x:String x:Key="UI_ItemIcon_None">https://static.snapgenshin.com/Bg/UI_ItemIcon_None.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>
<!-- Converters -->
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
@@ -50,41 +71,378 @@
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
<shmmc:ParameterDescriptor 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:PropertyInfoDescriptor x:Key="PropertyDescriptor"/>
<shmmc:MonsterIconConverter x:Key="MonsterIconConverter"/>
<shmmc:PropertyDescriptor 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"/>
<!-- Styles -->
<Style
x:Key="LargeGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
TargetType="GridViewItem">
<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"/>
</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>
<Style x:Key="WebView2ContentDialogStyle" TargetType="ContentDialog">
<Setter Property="Foreground" Value="{ThemeResource ContentDialogForeground}"/>
<Setter Property="Background" Value="{ThemeResource ContentDialogBackground}"/>
<Setter Property="BorderThickness" Value="{ThemeResource ContentDialogBorderWidth}"/>
<Setter Property="BorderBrush" Value="{ThemeResource ContentDialogBorderBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentDialog">
<Border x:Name="Container">
<Grid x:Name="LayoutRoot" Visibility="Collapsed">
<Rectangle x:Name="SmokeLayerBackground" Fill="{ThemeResource ContentDialogSmokeFill}"/>
<Border
x:Name="BackgroundElement"
MinWidth="{ThemeResource ContentDialogMinWidth}"
MinHeight="{ThemeResource ContentDialogMinHeight}"
MaxWidth="{ThemeResource ContentDialogMaxWidth}"
MaxHeight="{ThemeResource ContentDialogMaxHeight}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
BackgroundSizing="InnerBorderEdge"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
FlowDirection="{TemplateBinding FlowDirection}"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<ScaleTransform x:Name="ScaleTransform"/>
</Border.RenderTransform>
<Grid x:Name="DialogSpace" CornerRadius="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer
x:Name="ContentScrollViewer"
HorizontalScrollBarVisibility="Disabled"
IsTabStop="False"
VerticalScrollBarVisibility="Disabled"
ZoomMode="Disabled">
<Grid
Padding="0"
BorderBrush="{ThemeResource ContentDialogSeparatorBorderBrush}"
BorderThickness="{ThemeResource ContentDialogSeparatorThickness}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl
x:Name="Title"
Margin="{ThemeResource ContentDialogTitleMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{TemplateBinding Title}"
ContentTemplate="{TemplateBinding TitleTemplate}"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
MaxLines="2"
TextWrapping="Wrap"/>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<ContentPresenter
x:Name="Content"
Grid.Row="1"
Margin="0,0,0,8"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="{StaticResource ControlContentThemeFontSize}"
Foreground="{TemplateBinding Foreground}"
TextWrapping="Wrap"/>
</Grid>
</ScrollViewer>
<Grid
x:Name="CommandSpace"
Grid.Row="1"
Padding="8,0,8,8"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
XYFocusKeyboardNavigation="Enabled">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="PrimaryColumn" Width="*"/>
<ColumnDefinition x:Name="FirstSpacer" Width="0"/>
<ColumnDefinition x:Name="SecondaryColumn" Width="0"/>
<ColumnDefinition x:Name="SecondSpacer" Width="{ThemeResource ContentDialogButtonSpacing}"/>
<ColumnDefinition x:Name="CloseColumn" Width="*"/>
</Grid.ColumnDefinitions>
<Button
x:Name="PrimaryButton"
HorizontalAlignment="Stretch"
Content="{TemplateBinding PrimaryButtonText}"
ElementSoundMode="FocusOnly"
IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}"
IsTabStop="False"
Style="{TemplateBinding PrimaryButtonStyle}"/>
<Button
x:Name="SecondaryButton"
HorizontalAlignment="Stretch"
Content="{TemplateBinding SecondaryButtonText}"
ElementSoundMode="FocusOnly"
IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}"
IsTabStop="False"
Style="{TemplateBinding SecondaryButtonStyle}"/>
<Button
x:Name="CloseButton"
Grid.Column="4"
HorizontalAlignment="Stretch"
Content="{TemplateBinding CloseButtonText}"
ElementSoundMode="FocusOnly"
IsTabStop="False"
Style="{TemplateBinding CloseButtonStyle}"/>
</Grid>
</Grid>
</Border>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DialogShowingStates">
<VisualStateGroup.Transitions>
<VisualTransition To="DialogHidden">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="False"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFastAnimationDuration}"
Value="1.05"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFastAnimationDuration}"
Value="1.05"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="DialogShowing">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05"/>
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.0"/>
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="DialogHidden"/>
<VisualState x:Name="DialogShowing">
<VisualState.Setters>
<Setter Target="PrimaryButton.IsTabStop" Value="True"/>
<Setter Target="SecondaryButton.IsTabStop" Value="True"/>
<Setter Target="CloseButton.IsTabStop" Value="True"/>
<Setter Target="LayoutRoot.Visibility" Value="Visible"/>
<Setter Target="BackgroundElement.TabFocusNavigation" Value="Cycle"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="DialogShowingWithoutSmokeLayer">
<VisualState.Setters>
<Setter Target="PrimaryButton.IsTabStop" Value="True"/>
<Setter Target="SecondaryButton.IsTabStop" Value="True"/>
<Setter Target="CloseButton.IsTabStop" Value="True"/>
<Setter Target="LayoutRoot.Visibility" Value="Visible"/>
<Setter Target="LayoutRoot.Background" Value="{x:Null}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DialogSizingStates">
<VisualState x:Name="DefaultDialogSizing"/>
<VisualState x:Name="FullDialogSizing">
<VisualState.Setters>
<Setter Target="BackgroundElement.VerticalAlignment" Value="Stretch"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ButtonsVisibilityStates">
<VisualState x:Name="AllVisible">
<VisualState.Setters>
<Setter Target="FirstSpacer.Width" Value="{ThemeResource ContentDialogButtonSpacing}"/>
<Setter Target="SecondaryColumn.Width" Value="*"/>
<Setter Target="SecondaryButton.(Grid.Column)" Value="2"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NoneVisible">
<VisualState.Setters>
<Setter Target="CommandSpace.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PrimaryVisible">
<VisualState.Setters>
<Setter Target="PrimaryButton.(Grid.Column)" Value="4"/>
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SecondaryVisible">
<VisualState.Setters>
<Setter Target="SecondaryButton.(Grid.Column)" Value="4"/>
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="CloseVisible">
<VisualState.Setters>
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PrimaryAndSecondaryVisible">
<VisualState.Setters>
<Setter Target="SecondaryButton.(Grid.Column)" Value="4"/>
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PrimaryAndCloseVisible">
<VisualState.Setters>
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SecondaryAndCloseVisible">
<VisualState.Setters>
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DefaultButtonStates">
<VisualState x:Name="NoDefaultButton"/>
<VisualState x:Name="PrimaryAsDefaultButton">
<VisualState.Setters>
<Setter Target="PrimaryButton.Style" Value="{StaticResource AccentButtonStyle}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SecondaryAsDefaultButton">
<VisualState.Setters>
<Setter Target="SecondaryButton.Style" Value="{StaticResource AccentButtonStyle}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="CloseAsDefaultButton">
<VisualState.Setters>
<Setter Target="CloseButton.Style" Value="{StaticResource AccentButtonStyle}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DialogBorderStates">
<VisualState x:Name="NoBorder"/>
<VisualState x:Name="AccentColorBorder">
<VisualState.Setters>
<Setter Target="BackgroundElement.BorderBrush" Value="{ThemeResource SystemControlForegroundAccentBrush}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ItemsPanelTemplate -->
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
<ItemsStackPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
</Application>

View File

@@ -5,9 +5,8 @@ using CommunityToolkit.WinUI.Notifications;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Exception;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Logging;
using System.Diagnostics;
using Windows.Storage;
@@ -15,9 +14,12 @@ namespace Snap.Hutao;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// This class must be public
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton)]
public partial class App : Application
[SuppressMessage("", "SH001")]
public sealed partial class App : Application
{
private readonly ILogger<App> logger;
@@ -25,7 +27,6 @@ public partial class App : Application
/// Initializes the singleton application object.
/// </summary>
/// <param name="logger">日志器</param>
/// <param name="appCenter">App Center</param>
public App(ILogger<App> logger)
{
// load app resource
@@ -50,8 +51,8 @@ public partial class App : Application
firstInstance.Activated += Activation.Activate;
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;
logger.LogInformation(EventIds.CommonLog, "Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
logger.LogInformation("Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
logger.LogInformation("Cache folder : {folder}", ApplicationData.Current.LocalCacheFolder.Path);
JumpListHelper.ConfigureAsync().SafeForget(logger);
}

View File

@@ -1,39 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Logging;
namespace Snap.Hutao.Context.Database;
/// <summary>
/// 日志数据库上下文
/// 由于写入日志的行为需要锁定数据库上下文
/// 所以将日志单独分离出来进行读写
/// </summary>
public class LogDbContext : DbContext
{
/// <summary>
/// 创建一个新的
/// </summary>
/// <param name="options">选项</param>
private LogDbContext(DbContextOptions<LogDbContext> options)
: base(options)
{
}
/// <summary>
/// 日志记录
/// </summary>
public DbSet<LogEntry> Logs { get; set; } = default!;
/// <summary>
/// 构造一个临时的日志数据库上下文
/// </summary>
/// <param name="sqlConnectionString">连接字符串</param>
/// <returns>日志数据库上下文</returns>
public static LogDbContext Create(string sqlConnectionString)
{
return new(new DbContextOptionsBuilder<LogDbContext>().UseSqlite(sqlConnectionString).Options);
}
}

View File

@@ -1,22 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore.Design;
using Snap.Hutao.Context.FileSystem;
namespace Snap.Hutao.Context.Database;
/// <summary>
/// 此类只用于在生成迁移时提供数据库上下文
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public class LogDbContextDesignTimeFactory : IDesignTimeDbContextFactory<LogDbContext>
{
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public LogDbContext CreateDbContext(string[] args)
{
HutaoContext myDocument = new(new());
return LogDbContext.Create($"Data Source={myDocument.Locate("Log.db")}");
}
}

View File

@@ -1,174 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Context.FileSystem.Location;
using System.IO;
namespace Snap.Hutao.Context.FileSystem;
/// <summary>
/// 文件系统上下文
/// </summary>
/// <typeparam name="TLocation">路径位置类型</typeparam>
internal abstract class FileSystemContext
{
private readonly IFileSystemLocation location;
/// <summary>
/// 初始化文件系统上下文
/// </summary>
/// <param name="location">指定的文件系统位置</param>
public FileSystemContext(IFileSystemLocation location)
{
this.location = location;
EnsureDirectory();
}
/// <summary>
/// 创建文件,若已存在文件,则不会创建
/// </summary>
/// <param name="file">文件</param>
public void CreateFileOrIgnore(string file)
{
file = Locate(file);
if (!File.Exists(file))
{
File.Create(file).Dispose();
}
}
/// <summary>
/// 创建文件夹,若已存在文件,则不会创建
/// </summary>
/// <param name="folder">文件夹</param>
public void CreateFolderOrIgnore(string folder)
{
folder = Locate(folder);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
}
/// <summary>
/// 尝试删除文件夹
/// </summary>
/// <param name="folder">文件夹</param>
public void DeleteFolderOrIgnore(string folder)
{
folder = Locate(folder);
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
}
/// <summary>
/// 检查文件是否存在
/// </summary>
/// <param name="file">文件名称</param>
/// <returns>是否存在</returns>
public bool FileExists(string file)
{
return File.Exists(Locate(file));
}
/// <summary>
/// 检查文件是否存在
/// </summary>
/// <param name="folder">文件夹名称</param>
/// <param name="file">文件名称</param>
/// <returns>是否存在</returns>
public bool FileExists(string folder, string file)
{
return File.Exists(Locate(folder, file));
}
/// <summary>
/// 检查文件是否存在
/// </summary>
/// <param name="folder">文件夹名称</param>
/// <returns>是否存在</returns>
public bool FolderExists(string folder)
{
return Directory.Exists(Locate(folder));
}
/// <summary>
/// 定位根目录中的文件或文件夹
/// </summary>
/// <param name="fileOrFolder">文件或文件夹</param>
/// <returns>绝对路径</returns>
public string Locate(string fileOrFolder)
{
return Path.GetFullPath(fileOrFolder, location.GetPath());
}
/// <summary>
/// 定位根目录下子文件夹中的文件
/// </summary>
/// <param name="folder">文件夹</param>
/// <param name="file">文件</param>
/// <returns>绝对路径</returns>
public string Locate(string folder, string file)
{
return Path.GetFullPath(Path.Combine(folder, file), location.GetPath());
}
/// <summary>
/// 将文件移动到指定的子目录
/// </summary>
/// <param name="file">文件</param>
/// <param name="folder">文件夹</param>
/// <param name="overwrite">是否覆盖</param>
/// <returns>是否成功 当文件不存在时会失败</returns>
public bool MoveToFolderOrIgnore(string file, string folder, bool overwrite = true)
{
string target = Locate(folder, file);
file = Locate(file);
if (File.Exists(file))
{
File.Move(file, target, overwrite);
return true;
}
return false;
}
/// <summary>
/// 等效于 <see cref="File.OpenRead(string)"/> ,但路径经过解析
/// </summary>
/// <param name="file">文件名</param>
/// <returns>文件流</returns>
public FileStream OpenRead(string file)
{
return File.OpenRead(Locate(file));
}
/// <summary>
/// 等效于 <see cref="File.Create(string)"/> ,但路径经过解析
/// </summary>
/// <param name="file">文件名</param>
/// <returns>文件流</returns>
public FileStream Create(string file)
{
return File.Create(Locate(file));
}
/// <summary>
/// 检查根目录
/// </summary>
/// <returns>是否创建了路径</returns>
private bool EnsureDirectory()
{
string folder = location.GetPath();
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
return true;
}
return false;
}
}

View File

@@ -1,17 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Context.FileSystem;
/// <summary>
/// 我的文档上下文
/// </summary>
[Injection(InjectAs.Transient)]
internal class HutaoContext : FileSystemContext
{
/// <inheritdoc cref="FileSystemContext"/>
public HutaoContext(Location.HutaoLocation myDocument)
: base(myDocument)
{
}
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
using Windows.ApplicationModel;
namespace Snap.Hutao.Context.FileSystem.Location;
/// <summary>
/// 我的文档位置
/// </summary>
[Injection(InjectAs.Transient)]
internal class HutaoLocation : IFileSystemLocation
{
private string? path;
/// <inheritdoc/>
public string GetPath()
{
if (string.IsNullOrEmpty(path))
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
path = Path.GetFullPath(Path.Combine(myDocument, folderName));
}
return path;
}
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Context.FileSystem.Location;
/// <summary>
/// 文件系统位置
/// </summary>
public interface IFileSystemLocation
{
/// <summary>
/// 获取路径
/// </summary>
/// <returns>路径</returns>
string GetPath();
}

View File

@@ -1,27 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Context.FileSystem.Location;
/// <summary>
/// 我的文档位置
/// </summary>
[Injection(InjectAs.Transient)]
internal class Metadata : IFileSystemLocation
{
private string? path;
/// <inheritdoc/>
public string GetPath()
{
if (string.IsNullOrEmpty(path))
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
path = Path.GetFullPath(Path.Combine(myDocument, "Hutao", "Metadata"));
}
return path;
}
}

View File

@@ -1,19 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Context.FileSystem.Location;
namespace Snap.Hutao.Context.FileSystem;
/// <summary>
/// 元数据上下文
/// </summary>
[Injection(InjectAs.Transient)]
internal class MetadataContext : FileSystemContext
{
/// <inheritdoc cref="FileSystemContext"/>
public MetadataContext(Metadata metadata)
: base(metadata)
{
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Animation;
/// <summary>
/// 动画时长
/// </summary>
[HighQuality]
internal static class AnimationDurations
{
/// <summary>
/// 图片缩放动画
/// </summary>
public static readonly TimeSpan ImageZoom = TimeSpan.FromSeconds(0.5);
}

View File

@@ -11,17 +11,18 @@ namespace Snap.Hutao.Control.Animation;
/// <summary>
/// 图片放大动画
/// </summary>
internal class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
[HighQuality]
internal sealed class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
{
/// <summary>
/// 构造一个新的图片放大动画
/// </summary>
public ImageZoomInAnimation()
{
Duration = AnimationDurations.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
To = "1.1";
Duration = TimeSpan.FromSeconds(0.5);
To = Core.StringLiterals.OnePointOne;
}
/// <inheritdoc/>
@@ -35,4 +36,4 @@ internal class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
{
return (To?.ToVector3(), From?.ToVector3());
}
}
}

View File

@@ -11,17 +11,18 @@ namespace Snap.Hutao.Control.Animation;
/// <summary>
/// 图片缩小动画
/// </summary>
internal class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
[HighQuality]
internal sealed class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
{
/// <summary>
/// 构造一个新的图片缩小动画
/// </summary>
public ImageZoomOutAnimation()
{
Duration = AnimationDurations.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
To = "1";
Duration = TimeSpan.FromSeconds(0.5);
To = Core.StringLiterals.One;
}
/// <inheritdoc/>

View File

@@ -9,10 +9,11 @@ namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 按给定比例自动调整高度的行为
/// </summary>
internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
[HighQuality]
internal sealed class AutoHeightBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoHeightBehavior>.Depend(nameof(TargetWidth), 1080D);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoHeightBehavior>.Depend(nameof(TargetHeight), 390D);
private static readonly DependencyProperty TargetWidthProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
/// <summary>
/// 目标宽度
@@ -35,7 +36,7 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
UpdateElementHeight();
UpdateElement();
AssociatedObject.SizeChanged += OnSizeChanged;
}
@@ -43,15 +44,16 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
protected override void OnDetaching()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
base.OnDetaching();
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateElementHeight();
UpdateElement();
}
private void UpdateElementHeight()
private void UpdateElement()
{
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
}
}
}

View File

@@ -9,7 +9,8 @@ namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 按给定比例自动调整高度的行为
/// </summary>
internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
[HighQuality]
internal sealed class AutoWidthBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetWidth), 320D);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetHeight), 1024D);
@@ -35,7 +36,7 @@ internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
UpdateElementWidth();
UpdateElement();
AssociatedObject.SizeChanged += OnSizeChanged;
}
@@ -43,14 +44,15 @@ internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
protected override void OnDetaching()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
base.OnDetaching();
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateElementWidth();
UpdateElement();
}
private void UpdateElementWidth()
private void UpdateElement()
{
AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
}

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Control.Behavior;
/// AppTitleBar Workaround
/// https://github.com/microsoft/microsoft-ui-xaml/issues/7756
/// </summary>
internal class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
{
private readonly IMessenger messenger;
@@ -30,6 +30,15 @@ internal class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBa
AssociatedObject.DropDownClosed += OnDropDownClosed;
}
/// <inheritdoc/>
protected override void OnDetaching()
{
AssociatedObject.DropDownOpened -= OnDropDownOpened;
AssociatedObject.DropDownClosed -= OnDropDownClosed;
base.OnDetaching();
}
private void OnDropDownOpened(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(true));

View File

@@ -9,7 +9,8 @@ namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 在元素加载完成后执行命令的行为
/// </summary>
internal class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
[HighQuality]
internal sealed class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
{
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<ICommand>(nameof(Command));
private static readonly DependencyProperty CommandParameterProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<object>(nameof(CommandParameter));
@@ -38,9 +39,7 @@ internal class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
{
if (Command != null && Command.CanExecute(CommandParameter))
{
Command?.Execute(CommandParameter);
Command.Execute(CommandParameter);
}
base.OnAssociatedObjectLoaded();
}
}

View File

@@ -1,36 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 在元素卸载完成后执行命令的行为
/// </summary>
internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
{
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnUnloadedBehavior>.Depend<ICommand>(nameof(Command));
/// <summary>
/// 待执行的命令
/// </summary>
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <inheritdoc/>
protected override void OnDetaching()
{
// 由于卸载顺序问题,必须重写此方法才能正确触发命令
if (Command != null && Command.CanExecute(null))
{
Command.Execute(null);
}
base.OnDetaching();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.Xaml.Interactivity;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 打开附着的浮出控件操作
/// </summary>
[HighQuality]
internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction
{
/// <inheritdoc/>
public object Execute(object sender, object parameter)
{
FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
return null!;
}
}

View File

@@ -8,8 +8,11 @@ namespace Snap.Hutao.Control;
/// <summary>
/// 绑定探针
/// 用于处理特定情况下需要穿透数据上下文的工作
/// DependencyObject will dispose inner ReferenceTracker in any time
/// when object is not used anymore.
/// </summary>
public class BindingProxy : DependencyObject
[HighQuality]
internal sealed class BindingProxy : DependencyObject
{
private static readonly DependencyProperty DataContextProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));

View File

@@ -0,0 +1,31 @@
// 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="double"/> 0
/// </summary>
public static readonly object DoubleZero = 0D;
/// <summary>
/// <see cref="double"/> 0
/// </summary>
public static readonly object DoubleOne = 1D;
/// <summary>
/// <see cref="int"/> 0
/// </summary>
public static readonly object Int32Zero = 0;
/// <summary>
/// <see cref="true"/>
/// </summary>
public static readonly object True = true;
}

View File

@@ -1,7 +1,6 @@
// 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.Extension;
@@ -9,36 +8,25 @@ namespace Snap.Hutao.Control.Extension;
/// <summary>
/// 对话框扩展
/// </summary>
internal static class ContentDialogExtensions
[HighQuality]
internal static class ContentDialogExtension
{
/// <summary>
/// 针对窗口进行初始化
/// </summary>
/// <param name="contentDialog">对话框</param>
/// <param name="window">窗口</param>
/// <returns>初始化完成的对话框</returns>
public static ContentDialog InitializeWithWindow(this ContentDialog contentDialog, Window window)
{
contentDialog.XamlRoot = window.Content.XamlRoot;
return contentDialog;
}
/// <summary>
/// 阻止用户交互
/// </summary>
/// <param name="contentDialog">对话框</param>
/// <returns>用于恢复用户交互</returns>
public static async ValueTask<IAsyncDisposable> BlockAsync(this ContentDialog contentDialog)
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog)
{
await ThreadHelper.SwitchToMainThreadAsync();
contentDialog.ShowAsync().AsTask().SafeForget();
// E_ASYNC_OPERATION_NOT_STARTED 0x80000019
// Only a single ContentDialog can be open at any time.
return new ContentDialogHider(contentDialog);
}
private readonly struct ContentDialogHider : IAsyncDisposable
private class ContentDialogHider : IDisposable
{
private readonly ContentDialog contentDialog;
@@ -47,12 +35,10 @@ internal static class ContentDialogExtensions
this.contentDialog = contentDialog;
}
public async ValueTask DisposeAsync()
public void Dispose()
{
await ThreadHelper.SwitchToMainThreadAsync();
// Hide() must be called on main thread.
contentDialog.Hide();
ThreadHelper.InvokeOnMainThread(contentDialog.Hide);
}
}
}

View File

@@ -1,15 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
/// <summary>
/// 指示此类支持取消任务
/// </summary>
public interface ISupportCancellation
{
/// <summary>
/// 用于通知事件取消的取消令牌
/// </summary>
CancellationToken CancellationToken { get; set; }
}

View File

@@ -5,16 +5,15 @@ using CommunityToolkit.WinUI.UI.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Extension;
using System.Runtime.InteropServices;
using Windows.Storage;
namespace Snap.Hutao.Control.Image;
/// <summary>
/// 缓存图像
/// </summary>
public class CachedImage : ImageEx
[HighQuality]
internal sealed class CachedImage : ImageEx
{
/// <summary>
/// 构造一个新的缓存图像
@@ -28,23 +27,27 @@ public class CachedImage : ImageEx
/// <inheritdoc/>
protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{
// We can only use Ioc to retrive IImageCache,
// no IServiceProvider is available.
IImageCache imageCache = Ioc.Default.GetRequiredService<IImageCache>();
try
{
Verify.Operation(imageUri.Host != string.Empty, "无效的Uri");
StorageFile file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true);
Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri);
// BitmapImage need to be created by main thread.
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true);
// check token state to determine whether the operation should be canceled.
token.ThrowIfCancellationRequested();
// BitmapImage initialize with a uri will increase image quality and loading speed.
return new BitmapImage(new(file.Path));
return new BitmapImage(new(file));
}
catch (COMException)
{
// The image is corrupted, remove it.
await imageCache.RemoveAsync(imageUri.Enumerate()).ConfigureAwait(false);
imageCache.Remove(imageUri.Enumerate());
return null;
}
catch (OperationCanceledException)

View File

@@ -10,8 +10,14 @@ namespace Snap.Hutao.Control.Image;
/// <summary>
/// 合成扩展
/// </summary>
internal static class CompositionExtensions
[HighQuality]
internal static class CompositionExtension
{
private const string Background = nameof(Background);
private const string Foreground = nameof(Foreground);
private const string Source = nameof(Source);
private const string AlphaMask = nameof(AlphaMask);
/// <summary>
/// 创建拼合图视觉对象
/// </summary>
@@ -41,15 +47,15 @@ internal static class CompositionExtensions
{
BlendEffect effect = new()
{
Background = new CompositionEffectSourceParameter("Background"),
Foreground = new CompositionEffectSourceParameter("Foreground"),
Background = new CompositionEffectSourceParameter(Background),
Foreground = new CompositionEffectSourceParameter(Foreground),
Mode = blendEffectMode,
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter("Background", background);
brush.SetSourceParameter("Foreground", foreground);
brush.SetSourceParameter(Background, background);
brush.SetSourceParameter(Foreground, foreground);
return brush;
}
@@ -66,12 +72,12 @@ internal static class CompositionExtensions
{
GrayscaleEffect effect = new()
{
Source = new CompositionEffectSourceParameter("Source"),
Source = new CompositionEffectSourceParameter(Source),
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter("Source", source);
brush.SetSourceParameter(Source, source);
return brush;
}
@@ -88,12 +94,12 @@ internal static class CompositionExtensions
{
LuminanceToAlphaEffect effect = new()
{
Source = new CompositionEffectSourceParameter("Source"),
Source = new CompositionEffectSourceParameter(Source),
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
brush.SetSourceParameter("Source", sourceBrush);
brush.SetSourceParameter(Source, sourceBrush);
return brush;
}
@@ -112,14 +118,14 @@ internal static class CompositionExtensions
{
AlphaMaskEffect maskEffect = new()
{
AlphaMask = new CompositionEffectSourceParameter("AlphaMask"),
Source = new CompositionEffectSourceParameter("Source"),
AlphaMask = new CompositionEffectSourceParameter(AlphaMask),
Source = new CompositionEffectSourceParameter(Source),
};
CompositionEffectBrush brush = compositor.CreateEffectFactory(maskEffect).CreateBrush();
brush.SetSourceParameter("AlphaMask", alphaMask);
brush.SetSourceParameter("Source", sourceBrush);
brush.SetSourceParameter(AlphaMask, alphaMask);
brush.SetSourceParameter(Source, sourceBrush);
return brush;
}
@@ -152,19 +158,17 @@ internal static class CompositionExtensions
/// 创建一个线性渐变画刷
/// </summary>
/// <param name="compositor">合成器</param>
/// <param name="start">起点</param>
/// <param name="end">终点</param>
/// <param name="direction">方向</param>
/// <param name="stops">锚点</param>
/// <returns>线性渐变画刷</returns>
public static CompositionLinearGradientBrush CompositeLinearGradientBrush(
this Compositor compositor,
Vector2 start,
Vector2 end,
GradientDirection direction,
params GradientStop[] stops)
{
CompositionLinearGradientBrush brush = compositor.CreateLinearGradientBrush();
brush.StartPoint = start;
brush.EndPoint = end;
brush.StartPoint = GetStartPointOfDirection(direction);
brush.EndPoint = GetEndPointOfDirection(direction);
foreach (GradientStop stop in stops)
{
@@ -174,24 +178,29 @@ internal static class CompositionExtensions
return brush;
}
/// <summary>
/// 创建一个新的蒙版画刷
/// </summary>
/// <param name="compositor">合成器</param>
/// <param name="source">源</param>
/// <param name="mask">蒙版</param>
/// <returns>蒙版画刷</returns>
public static CompositionMaskBrush CompositeMaskBrush(
this Compositor compositor,
CompositionBrush source,
CompositionBrush mask)
private static Vector2 GetStartPointOfDirection(GradientDirection direction)
{
CompositionMaskBrush brush = compositor.CreateMaskBrush();
brush.Source = source;
brush.Mask = mask;
return brush;
return direction switch
{
GradientDirection.BottomToTop => Vector2.UnitY,
GradientDirection.LeftBottomToRightTop => Vector2.UnitY,
GradientDirection.RightBottomToLeftTop => Vector2.One,
GradientDirection.RightToLeft => Vector2.UnitX,
GradientDirection.RightTopToLeftBottom => Vector2.UnitX,
_ => Vector2.Zero,
};
}
public record struct GradientStop(float Offset, Windows.UI.Color Color);
}
private static Vector2 GetEndPointOfDirection(GradientDirection direction)
{
return direction switch
{
GradientDirection.LeftBottomToRightTop => Vector2.UnitX,
GradientDirection.LeftToRight => Vector2.UnitX,
GradientDirection.LeftTopToRightBottom => Vector2.One,
GradientDirection.RightTopToLeftBottom => Vector2.UnitY,
GradientDirection.TopToBottom => Vector2.UnitY,
_ => Vector2.Zero,
};
}
}

View File

@@ -2,17 +2,16 @@
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Animations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using Windows.Storage;
using Windows.Storage.Streams;
namespace Snap.Hutao.Control.Image;
@@ -20,12 +19,14 @@ namespace Snap.Hutao.Control.Image;
/// 合成图像控件
/// 为其他图像类控件提供基类
/// </summary>
public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
[HighQuality]
internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
private static readonly DependencyProperty EnableLazyLoadingProperty = Property<CompositionImage>.DependBoxed<bool>(nameof(EnableLazyLoading), BoxedValues.True);
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
private readonly IImageCache imageCache;
private readonly IServiceProvider serviceProvider;
private SpriteVisual? spriteVisual;
private bool isShow = true;
@@ -35,8 +36,15 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
/// </summary>
public CompositionImage()
{
imageCache = Ioc.Default.GetRequiredService<IImageCache>();
serviceProvider = Ioc.Default.GetRequiredService<IServiceProvider>();
AllowFocusOnInteraction = false;
IsDoubleTapEnabled = false;
IsHitTestVisible = false;
IsHoldingEnabled = false;
IsRightTapEnabled = false;
IsTabStop = false;
SizeChanged += OnSizeChanged;
}
@@ -49,6 +57,15 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
set => SetValue(SourceProperty, value);
}
/// <summary>
/// 启用延迟加载
/// </summary>
public bool EnableLazyLoading
{
get => (bool)GetValue(EnableLazyLoadingProperty);
set => SetValue(EnableLazyLoadingProperty, value);
}
/// <summary>
/// 合成组合视觉
/// </summary>
@@ -60,15 +77,16 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
/// <summary>
/// 异步加载图像表面
/// </summary>
/// <param name="storageFile">文件</param>
/// <param name="file">文件</param>
/// <param name="token">取消令牌</param>
/// <returns>加载的图像表面</returns>
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
{
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token).ConfigureAwait(true))
{
return LoadedImageSurface.StartLoadFromStream(imageStream);
}
TaskCompletionSource loadCompleteTaskSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
await loadCompleteTaskSource.Task.ConfigureAwait(true);
return surface;
}
/// <summary>
@@ -80,33 +98,22 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
spriteVisual.Size = ActualSize;
}
private static void OnApplyImageFailed(Uri? uri, Exception exception)
{
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
if (exception is HttpRequestException httpRequestException)
{
infoBarService.Error(httpRequestException, $"GET {uri}");
}
else
{
infoBarService.Error(exception, $"应用 {nameof(CompositionImage)} 的源时发生异常");
}
}
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{
CompositionImage image = (CompositionImage)sender;
CancellationToken token = LoadingTokenSource.Register(image);
ILogger<CompositionImage> logger = Ioc.Default.GetRequiredService<ILogger<CompositionImage>>();
IServiceProvider serviceProvider = image.serviceProvider;
ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>();
// source is valid
if (arg.NewValue is Uri inner && !string.IsNullOrEmpty(inner.Host))
if (arg.NewValue is Uri inner && !string.IsNullOrEmpty(inner.OriginalString))
{
// value is different from old one
if (inner != (arg.OldValue as Uri))
{
image.ApplyImageInternalAsync(inner, token).SafeForget(logger, ex => OnApplyImageFailed(inner, ex));
image
.ApplyImageAsync(inner, token)
.SafeForget(logger, ex => OnApplyImageFailed(serviceProvider, inner, ex));
}
}
else
@@ -115,31 +122,47 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
}
}
private async Task ApplyImageInternalAsync(Uri? uri, CancellationToken token)
private static void OnApplyImageFailed(IServiceProvider serviceProvider, Uri? uri, Exception exception)
{
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
if (exception is HttpRequestException httpRequestException)
{
infoBarService.Error(httpRequestException, string.Format(SH.ControlImageCompositionImageHttpRequest, uri));
}
else
{
Exception baseException = exception.GetBaseException();
if (baseException is not COMException)
{
infoBarService.Error(baseException, SH.ControlImageCompositionImageSystemException);
}
}
}
private async Task ApplyImageAsync(Uri? uri, CancellationToken token)
{
await HideAsync(token).ConfigureAwait(true);
LoadedImageSurface? imageSurface = null;
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
if (uri != null)
{
if (uri.Scheme == "ms-appx")
{
imageSurface = LoadedImageSurface.StartLoadFromUri(uri);
}
else
{
StorageFile storageFile = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true);
LoadedImageSurface? imageSurface = null;
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
try
{
imageSurface = await LoadImageSurfaceAsync(storageFile, token).ConfigureAwait(true);
}
catch (COMException)
{
await imageCache.RemoveAsync(uri.Enumerate()).ConfigureAwait(true);
}
IImageCache imageCache = serviceProvider.GetRequiredService<IImageCache>();
string file = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true);
try
{
imageSurface = await LoadImageSurfaceAsync(file, token).ConfigureAwait(true);
}
catch (COMException)
{
imageCache.Remove(uri.Enumerate());
}
catch (IOException)
{
imageCache.Remove(uri.Enumerate());
}
if (imageSurface != null)
@@ -157,8 +180,16 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
if (!isShow)
{
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token).ConfigureAwait(true);
isShow = true;
if (EnableLazyLoading)
{
await AnimationBuilder.Create().Opacity(1d, 0d).StartAsync(this, token).ConfigureAwait(true);
}
else
{
Opacity = 1;
}
}
}
@@ -166,8 +197,16 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
if (isShow)
{
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token).ConfigureAwait(true);
isShow = false;
if (EnableLazyLoading)
{
await AnimationBuilder.Create().Opacity(0d, 1d).StartAsync(this, token).ConfigureAwait(true);
}
else
{
Opacity = 0;
}
}
}

View File

@@ -3,41 +3,60 @@
using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using System.Numerics;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Snap.Hutao.Win32;
namespace Snap.Hutao.Control.Image;
/// <summary>
/// 支持渐变图像
/// 渐变图像
/// </summary>
public class Gradient : CompositionImage
[HighQuality]
internal sealed class Gradient : CompositionImage
{
private static readonly DependencyProperty BackgroundDirectionProperty = Property<Gradient>.Depend(nameof(BackgroundDirection), GradientDirection.TopToBottom);
private static readonly DependencyProperty ForegroundDirectionProperty = Property<Gradient>.Depend(nameof(ForegroundDirection), GradientDirection.TopToBottom);
private double imageAspectRatio;
/// <summary>
/// 背景方向
/// </summary>
public GradientDirection BackgroundDirection
{
get => (GradientDirection)GetValue(BackgroundDirectionProperty);
set => SetValue(BackgroundDirectionProperty, value);
}
/// <summary>
/// 前景方向
/// </summary>
public GradientDirection ForegroundDirection
{
get => (GradientDirection)GetValue(ForegroundDirectionProperty);
set => SetValue(ForegroundDirectionProperty, value);
}
/// <inheritdoc/>
protected override void OnUpdateVisual(SpriteVisual spriteVisual)
{
if (spriteVisual is not null)
{
Height = (double)Math.Clamp(ActualWidth / imageAspectRatio, 0, MaxHeight);
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
spriteVisual.Size = ActualSize;
}
}
/// <inheritdoc/>
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
{
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token).ConfigureAwait(true))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream).AsTask(token).ConfigureAwait(true);
imageAspectRatio = decoder.PixelWidth / (double)decoder.PixelHeight;
return LoadedImageSurface.StartLoadFromStream(imageStream);
}
TaskCompletionSource loadCompleteTaskSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
await loadCompleteTaskSource.Task.ConfigureAwait(true);
imageAspectRatio = surface.NaturalSize.AspectRatio();
return surface;
}
/// <inheritdoc/>
@@ -45,8 +64,8 @@ public class Gradient : CompositionImage
{
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.UniformToFill, vRatio: 0f);
CompositionLinearGradientBrush backgroundBrush = compositor.CompositeLinearGradientBrush(new(1f, 0), Vector2.UnitY, new(0, Colors.White), new(1, Colors.Black));
CompositionLinearGradientBrush foregroundBrush = compositor.CompositeLinearGradientBrush(Vector2.Zero, Vector2.UnitY, new(0, Colors.White), new(0.95f, Colors.Black));
CompositionLinearGradientBrush backgroundBrush = compositor.CompositeLinearGradientBrush(BackgroundDirection, new(0, Colors.White), new(1, Colors.Black));
CompositionLinearGradientBrush foregroundBrush = compositor.CompositeLinearGradientBrush(ForegroundDirection, new(0, Colors.White), new(1, Colors.Black));
CompositionEffectBrush gradientEffectBrush = compositor.CompositeBlendEffectBrush(backgroundBrush, foregroundBrush);
CompositionEffectBrush opacityMaskEffectBrush = compositor.CompositeLuminanceToAlphaEffectBrush(gradientEffectBrush);

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Image;
/// <summary>
/// 渐变方向
/// </summary>
[HighQuality]
internal enum GradientDirection
{
/// <summary>
/// 下到上
/// </summary>
BottomToTop,
/// <summary>
/// 左下到右上
/// </summary>
LeftBottomToRightTop,
/// <summary>
/// 左到右
/// </summary>
LeftToRight,
/// <summary>
/// 左上到右下
/// </summary>
LeftTopToRightBottom,
/// <summary>
/// 右下到左上
/// </summary>
RightBottomToLeftTop,
/// <summary>
/// 右到左
/// </summary>
RightToLeft,
/// <summary>
/// 右上到左下
/// </summary>
RightTopToLeftBottom,
/// <summary>
/// 上到下
/// </summary>
TopToBottom,
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Image;
/// <summary>
/// 渐变锚点
/// </summary>
/// <param name="Offset">便宜</param>
/// <param name="Color">颜色</param>
[HighQuality]
internal struct GradientStop
{
/// <summary>
/// 便宜
/// </summary>
public float Offset;
/// <summary>
/// 颜色
/// </summary>
public Windows.UI.Color Color;
/// <summary>
/// 构造一个新的渐变锚点
/// </summary>
/// <param name="offset">偏移</param>
/// <param name="color">颜色</param>
public GradientStop(float offset, Windows.UI.Color color)
{
Offset = offset;
Color = color;
}
}

View File

@@ -6,13 +6,15 @@ using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Theme;
namespace Snap.Hutao.Control.Image;
/// <summary>
/// 支持单色的图像
/// </summary>
public class MonoChrome : CompositionImage
[HighQuality]
internal sealed class MonoChrome : CompositionImage
{
private CompositionColorBrush? backgroundBrush;
@@ -49,18 +51,13 @@ public class MonoChrome : CompositionImage
private void SetBackgroundColor(CompositionColorBrush backgroundBrush)
{
ApplicationTheme theme = ActualTheme switch
{
ElementTheme.Light => ApplicationTheme.Light,
ElementTheme.Dark => ApplicationTheme.Dark,
_ => Ioc.Default.GetRequiredService<App>().RequestedTheme,
};
ApplicationTheme theme = ThemeHelper.ElementToApplication(ActualTheme);
backgroundBrush.Color = theme switch
{
ApplicationTheme.Light => Colors.Black,
ApplicationTheme.Dark => Colors.White,
_ => throw Must.NeverHappen(),
_ => Colors.Transparent,
};
}
}
}

View File

@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Markup;
/// <summary>
/// Custom <see cref="Markup"/> which can provide <see cref="BitmapIcon"/> values.
/// </summary>
[HighQuality]
[MarkupExtensionReturnType(ReturnType = typeof(BitmapIcon))]
public sealed class BitmapIconExtension : MarkupExtension
internal sealed class BitmapIconExtension : MarkupExtension
{
/// <summary>
/// Gets or sets the <see cref="Uri"/> representing the image to display.

View File

@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Markup;
/// <summary>
/// Custom <see cref="MarkupExtension"/> which can provide <see cref="FontIcon"/> values.
/// </summary>
[HighQuality]
[MarkupExtensionReturnType(ReturnType = typeof(FontIcon))]
internal class FontIconExtension : MarkupExtension
internal sealed class FontIconExtension : MarkupExtension
{
/// <summary>
/// Gets or sets the <see cref="string"/> value representing the icon to display.

View File

@@ -1,28 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;
/// <summary>
/// Custom <see cref="MarkupExtension"/> which can provide <see cref="FontIcon"/> values.
/// </summary>
[MarkupExtensionReturnType(ReturnType = typeof(FontIcon))]
internal class FontIconSourceExtension : MarkupExtension
{
/// <summary>
/// Gets or sets the <see cref="string"/> value representing the icon to display.
/// </summary>
public string Glyph { get; set; } = default!;
/// <inheritdoc/>
protected override object ProvideValue()
{
return new FontIconSource()
{
Glyph = Glyph,
};
}
}

View File

@@ -1,68 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
using Snap.Hutao.Extension;
using Snap.Hutao.Localization;
using System.Globalization;
namespace Snap.Hutao.Control.Markup;
/// <summary>
/// 国际化拓展
/// </summary>
[MarkupExtensionReturnType(ReturnType = typeof(string))]
internal class I18NExtension : MarkupExtension
{
private static readonly ITranslation Translation;
private static readonly Dictionary<string, Type> TranslationMap = new()
{
["zh-CN"] = typeof(LanguagezhCN),
};
static I18NExtension()
{
string currentName = CultureInfo.CurrentUICulture.Name;
Type? languageType = TranslationMap.GetValueOrDefault2(currentName, typeof(LanguagezhCN));
Translation = (ITranslation)Activator.CreateInstance(languageType!)!;
}
/// <summary>
/// 构造默认的国际化拓展
/// </summary>
public I18NExtension()
: base()
{
}
/// <summary>
/// 构造默认的国际化拓展
/// </summary>
/// <param name="key">键</param>
public I18NExtension(string key)
{
Key = key;
}
/// <summary>
/// 键名称
/// </summary>
public string Key { get; set; } = default!;
/// <summary>
/// 获取字符串
/// </summary>
/// <param name="key">键</param>
/// <returns>翻译的字符串</returns>
internal static string Get(string key)
{
return Translation[key];
}
/// <inheritdoc/>
protected override object ProvideValue()
{
return Translation[Key];
}
}

View File

@@ -1,68 +0,0 @@
// 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.Markup;
/// <summary>
/// 国际化帮助类
/// WinUI 3 目前存在部分页面无法使用 MarkupExtension 的问题
/// 使用 此帮助类 绕过限制
/// </summary>
internal class I18NHelper
{
private static readonly DependencyProperty TranslationProperty = Property<I18NHelper>.Attach("Translation", string.Empty, OnKeyChanged);
/// <summary>
/// 获取键
/// </summary>
/// <param name="obj">对象</param>
/// <returns>值</returns>
public static string GetTranslation(DependencyObject obj)
{
return (string)obj.GetValue(TranslationProperty);
}
/// <summary>
/// 设置键
/// </summary>
/// <param name="obj">对象</param>
/// <param name="value">值</param>
public static void SetTranslation(DependencyObject obj, string value)
{
string tarnslation = I18NExtension.Get(value);
obj.SetValue(TranslationProperty, tarnslation);
}
private static void OnKeyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
string translation = I18NExtension.Get(arg.NewValue.ToString() ?? string.Empty);
if (obj is AppBarButton appBarButton)
{
appBarButton.Label = translation;
}
else if (obj is AppBarToggleButton appBarToggleButton)
{
appBarToggleButton.Label = translation;
}
else if (obj is AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.PlaceholderText = translation;
}
else if (obj is ContentControl contentControl)
{
contentControl.Content = translation;
}
else if (obj is MenuFlyoutItem menuFlyoutItem)
{
menuFlyoutItem.Text = translation;
}
else if (obj is TextBlock textBlock)
{
textBlock.Text = translation;
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;
/// <summary>
/// Xaml extension to return a <see cref="string"/> value from resource file associated with a resource key
/// </summary>
[HighQuality]
[MarkupExtensionReturnType(ReturnType = typeof(string))]
internal sealed class ResourceStringExtension : MarkupExtension
{
/// <summary>
/// Gets or sets associated ID from resource strings.
/// </summary>
public string? Name { get; set; }
/// <inheritdoc/>
protected override object ProvideValue()
{
return SH.ResourceManager.GetString(Name ?? string.Empty) ?? Name ?? string.Empty;
}
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;
/// <summary>
/// Uri扩展
/// </summary>
[MarkupExtensionReturnType(ReturnType = typeof(Uri))]
public sealed class UriExtension : MarkupExtension
{
/// <summary>
/// 构造一个新的Uri扩展
/// </summary>
public UriExtension()
{
}
/// <summary>
/// 地址
/// </summary>
public string? Value { get; set; }
/// <inheritdoc/>
protected override object ProvideValue()
{
return new Uri(Value ?? string.Empty);
}
}

View File

@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Media;
/// <summary>
/// BGRA8 结构
/// </summary>
[HighQuality]
[StructLayout(LayoutKind.Explicit)]
public struct Bgra8
internal struct Bgra8
{
/// <summary>
/// B

View File

@@ -12,8 +12,9 @@ namespace Snap.Hutao.Control.Media;
/// <summary>
/// RGBA 颜色
/// </summary>
[HighQuality]
[StructLayout(LayoutKind.Explicit)]
public struct Rgba8
internal struct Rgba8
{
/// <summary>
/// R
@@ -48,7 +49,6 @@ public struct Rgba8
/// <param name="hex">色值字符串</param>
public Rgba8(ReadOnlySpan<char> hex)
{
Must.Argument(hex.Length == 8, "色值长度不为8");
R = 0;
G = 0;
B = 0;

View File

@@ -12,7 +12,8 @@ namespace Snap.Hutao.Control.Media;
/// <summary>
/// 软件位图拓展
/// </summary>
public static class SoftwareBitmapExtension
[HighQuality]
internal static class SoftwareBitmapExtension
{
/// <summary>
/// 混合模式 正常

View File

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

View File

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

View File

@@ -9,9 +9,12 @@ namespace Snap.Hutao.Control.Panel;
/// <summary>
/// 面板选择器
/// </summary>
public sealed partial class PanelSelector : UserControl
[HighQuality]
internal sealed partial class PanelSelector : SplitButton
{
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), "List");
private const string List = nameof(List);
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), List, OnCurrentChanged);
/// <summary>
/// 构造一个新的面板选择器
@@ -30,51 +33,60 @@ public sealed partial class PanelSelector : UserControl
set => SetValue(CurrentProperty, value);
}
private void SplitButtonLoaded(object sender, RoutedEventArgs e)
private static void OnCurrentChanged(PanelSelector sender, string current)
{
MenuFlyout menuFlyout = (MenuFlyout)((SplitButton)sender).Flyout;
((RadioMenuFlyoutItem)menuFlyout.Items[0]).IsChecked = true;
MenuFlyout menuFlyout = (MenuFlyout)sender.RootSplitButton.Flyout;
RadioMenuFlyoutItem targetItem = menuFlyout.Items
.Cast<RadioMenuFlyoutItem>()
.Single(i => (string)i.Tag == current);
targetItem.IsChecked = true;
sender.IconPresenter.Glyph = ((FontIcon)targetItem.Icon).Glyph;
}
private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
OnCurrentChanged((PanelSelector)obj, (string)args.NewValue);
}
private void OnRootControlLoaded(object sender, RoutedEventArgs e)
{
// because the GroupName shares in global
// we have to impl a control scoped GroupName.
PanelSelector selector = (PanelSelector)sender;
MenuFlyout menuFlyout = (MenuFlyout)selector.RootSplitButton.Flyout;
int hash = GetHashCode();
foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast<RadioMenuFlyoutItem>())
{
item.GroupName = $"PanelSelector{hash}Group";
}
OnCurrentChanged(selector, Current);
}
private void SplitButtonClick(SplitButton sender, SplitButtonClickEventArgs args)
{
MenuFlyout menuFlyout = (MenuFlyout)sender.Flyout;
int i = 0;
for (; i < menuFlyout.Items.Count; i++)
{
RadioMenuFlyoutItem current = (RadioMenuFlyoutItem)menuFlyout.Items[i];
if (current.IsChecked)
if ((string)menuFlyout.Items[i].Tag == Current)
{
break;
}
}
i++;
if (i > menuFlyout.Items.Count)
{
i = 1;
}
if (i == menuFlyout.Items.Count)
{
i = 0;
}
++i;
i %= menuFlyout.Items.Count; // move the count index to 0
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)menuFlyout.Items[i];
item.IsChecked = true;
UpdateState(item);
Current = (string)item.Tag;
}
private void RadioMenuFlyoutItemClick(object sender, RoutedEventArgs e)
{
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)sender;
UpdateState(item);
}
private void UpdateState(RadioMenuFlyoutItem item)
{
Current = (string)item.Tag;
IconPresenter.Glyph = ((FontIcon)item.Icon).Glyph;
}
}

View File

@@ -9,6 +9,7 @@ namespace Snap.Hutao.Control;
/// 快速创建 <see cref="TOwner"/> 的 <see cref="DependencyProperty"/>
/// </summary>
/// <typeparam name="TOwner">所有者的类型</typeparam>
[HighQuality]
internal static class Property<TOwner>
{
/// <summary>
@@ -34,6 +35,18 @@ internal static class Property<TOwner>
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
/// 注册依赖属性
/// </summary>
/// <typeparam name="TProperty">属性的类型</typeparam>
/// <param name="name">属性名称</param>
/// <param name="defaultValue">封装的默认值</param>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty DependBoxed<TProperty>(string name, object defaultValue)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
/// 注册依赖属性
/// </summary>

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.ViewModel.Abstraction;
namespace Snap.Hutao.Control;
@@ -12,19 +13,38 @@ namespace Snap.Hutao.Control;
/// 表示支持取消加载的异步页面
/// 在被导航到其他页面前触发取消异步通知
/// </summary>
[HighQuality]
[SuppressMessage("", "CA1001")]
public class ScopedPage : Page
internal class ScopedPage : Page
{
private readonly CancellationTokenSource viewLoadingCancellationTokenSource = new();
private readonly IServiceScope serviceScope;
// Allow GC to Collect the IServiceScope
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(null!);
private readonly CancellationTokenSource viewCancellationTokenSource = new();
private readonly IServiceScope currentScope;
/// <summary>
/// 构造一个新的页面
/// </summary>
public ScopedPage()
{
serviceScope = Ioc.Default.CreateScope();
serviceScope.Track();
Unloaded += OnScopedPageUnloaded;
currentScope = Ioc.Default.CreateScope();
DisposePreviousScope();
// track current
PreviousScopeReference.SetTarget(currentScope);
}
/// <summary>
/// 释放上个范围
/// </summary>
public static void DisposePreviousScope()
{
if (PreviousScopeReference.TryGetTarget(out IServiceScope? scope))
{
scope.Dispose();
}
}
/// <summary>
@@ -33,10 +53,10 @@ public class ScopedPage : Page
/// </summary>
/// <typeparam name="TViewModel">视图模型类型</typeparam>
public void InitializeWith<TViewModel>()
where TViewModel : class, ISupportCancellation
where TViewModel : class, IViewModel
{
ISupportCancellation viewModel = serviceScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewLoadingCancellationTokenSource.Token;
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
@@ -58,23 +78,36 @@ public class ScopedPage : Page
/// <inheritdoc/>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
viewLoadingCancellationTokenSource.Cancel();
using (viewCancellationTokenSource)
{
// Cancel all tasks executed by the view model
viewCancellationTokenSource.Cancel();
IViewModel viewModel = (IViewModel)DataContext;
// Try dispose scope when page is not presented
serviceScope.Dispose();
viewLoadingCancellationTokenSource.Dispose();
using (SemaphoreSlim locker = viewModel.DisposeLock)
{
// Wait to ensure viewmodel operation is completed
locker.Wait();
viewModel.IsViewDisposed = true;
// Dispose the scope
currentScope.Dispose();
}
}
}
/// <inheritdoc/>
[SuppressMessage("", "VSTHRD100")]
protected override async void OnNavigatedTo(NavigationEventArgs e)
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is INavigationData extra)
{
await NotifyRecipentAsync(extra).ConfigureAwait(false);
NotifyRecipentAsync(extra).SafeForget();
}
}
private void OnScopedPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
DataContext = null;
Unloaded -= OnScopedPageUnloaded;
}
}

View File

@@ -1,7 +1,5 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
// Some part of this file came from:
// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
@@ -9,15 +7,18 @@ using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using Snap.Hutao.Control.Theme;
using Windows.UI;
namespace Snap.Hutao.Control.Text;
/// <summary>
/// 专用于呈现描述文本的文本块
/// Some part of this file came from:
/// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
/// </summary>
public class DescriptionTextBlock : ContentControl
[HighQuality]
internal sealed class DescriptionTextBlock : ContentControl
{
private static readonly DependencyProperty DescriptionProperty = Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
@@ -33,6 +34,7 @@ public class DescriptionTextBlock : ContentControl
public DescriptionTextBlock()
{
IsTabStop = false;
Content = new TextBlock()
{
TextWrapping = TextWrapping.Wrap,

View File

@@ -0,0 +1,210 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wsc="using:WinUICommunity.SettingsUI.Controls">
<FontFamily x:Key="MiSans">ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
<FontFamily x:Key="CascadiaMonoAndMiSans">ms-appx:///Resource/Font/CascadiaMono.ttf#Cascadia Mono, ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
<StaticResource x:Key="PivotHeaderItemFontFamily" ResourceKey="MiSans"/>
<StaticResource x:Key="ContentControlThemeFontFamily" ResourceKey="MiSans"/>
<Style BasedOn="{StaticResource BodyTextBlockStyle}" TargetType="TextBlock"/>
<Style x:Key="BaseTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
<Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="LineStackingStrategy" Value="MaxHeight"/>
<Setter Property="TextLineBounds" Value="Full"/>
</Style>
<Style
x:Key="HeaderTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="46"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="SubheaderTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="34"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="TitleTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource TitleTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="SubtitleTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource SubtitleTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="BodyTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style
x:Key="CaptionTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource CaptionTextBlockFontSize}"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style
x:Key="BodyStrongTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource BodyStrongTextBlockFontSize}"/>
</Style>
<Style
x:Key="TitleLargeTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource TitleLargeTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="DisplayTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource DisplayTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}" TargetType="MenuFlyoutItem">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultMenuFlyoutSubItemStyle}" TargetType="MenuFlyoutSubItem">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style TargetType="InfoBar">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultSettingStyle}" TargetType="wsc:Setting"/>
<Style x:Key="DefaultSettingStyle" TargetType="wsc:Setting">
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Margin" Value="0,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="wsc:Setting">
<Grid
x:Name="RootGrid"
MinHeight="48"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- Icon -->
<ColumnDefinition Width="*"/>
<!-- Header and subtitle -->
<ColumnDefinition Width="Auto"/>
<!-- Action control -->
</Grid.ColumnDefinitions>
<ContentPresenter
x:Name="IconPresenter"
MaxWidth="20"
Margin="2,0,18,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Icon}"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
IsTextScaleFactorEnabled="False"/>
<StackPanel
Grid.Column="1"
Margin="0,0,16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<TextBlock
x:Name="HeaderPresenter"
VerticalAlignment="Center"
FontFamily="{StaticResource MiSans}"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
Text="{TemplateBinding Header}"/>
<ContentPresenter
x:Name="DescriptionPresenter"
Content="{TemplateBinding Description}"
FontFamily="{StaticResource MiSans}"
FontSize="{StaticResource SecondaryTextFontSize}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="WrapWholeWords">
<ContentPresenter.Resources>
<Style BasedOn="{StaticResource CaptionTextBlockStyle}" TargetType="TextBlock">
<Style.Setters>
<Setter Property="TextWrapping" Value="WrapWholeWords"/>
</Style.Setters>
</Style>
<Style BasedOn="{StaticResource TextButtonStyle}" TargetType="HyperlinkButton">
<Style.Setters>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="0,0,0,0"/>
</Style.Setters>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
<ContentPresenter
x:Name="ContentPresenter"
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding ActionContent}"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="HeaderPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="DescriptionPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="IconPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -4,12 +4,13 @@
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Core;
namespace Snap.Hutao.Control.Theme;
/// <summary>
/// 主题帮助工具类
/// </summary>
public static class ThemeHelper
[HighQuality]
internal static class ThemeHelper
{
/// <summary>
/// 判断主题是否相等
@@ -42,6 +43,21 @@ public static class ThemeHelper
};
}
/// <summary>
/// 从 <see cref="ElementTheme"/> 转换到 <see cref="ApplicationTheme"/>
/// </summary>
/// <param name="applicationTheme">元素主题</param>
/// <returns>应用主题</returns>
public static ApplicationTheme ElementToApplication(ElementTheme applicationTheme)
{
return applicationTheme switch
{
ElementTheme.Light => ApplicationTheme.Light,
ElementTheme.Dark => ApplicationTheme.Dark,
_ => Ioc.Default.GetRequiredService<App>().RequestedTheme,
};
}
/// <summary>
/// 从 <see cref="ElementTheme"/> 转换到 <see cref="SystemBackdropTheme"/>
/// </summary>

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Control;
/// </summary>
/// <typeparam name="TFrom">源类型</typeparam>
/// <typeparam name="TTo">目标类型</typeparam>
public abstract class ValueConverterBase<TFrom, TTo> : IValueConverter
internal abstract class ValueConverter<TFrom, TTo> : IValueConverter
{
/// <inheritdoc/>
public object? Convert(object value, Type targetType, object parameter, string language)
@@ -23,7 +23,7 @@ public abstract class ValueConverterBase<TFrom, TTo> : IValueConverter
catch (Exception ex)
{
Ioc.Default
.GetRequiredService<ILogger<ValueConverterBase<TFrom, TTo>>>()
.GetRequiredService<ILogger<ValueConverter<TFrom, TTo>>>()
.LogError(ex, "值转换器异常");
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Abstraction;
[HighQuality]
[SuppressMessage("", "SA1600")]
internal abstract class DisposableObject : IDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose()
{
if (!IsDisposed)
{
GC.SuppressFinalize(this);
Dispose(isDisposing: true);
}
}
protected virtual void Dispose(bool isDisposing)
{
IsDisposed = true;
}
protected void VerifyNotDisposed()
{
if (IsDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}

View File

@@ -4,13 +4,15 @@
namespace Snap.Hutao.Core.Abstraction;
/// <summary>
/// 表示支持验证
/// 可克隆
/// </summary>
internal interface ISupportValidation
/// <typeparam name="TSelf">自身类型</typeparam>
[HighQuality]
internal interface ICloneable<TSelf>
{
/// <summary>
/// 验证
/// 克隆
/// </summary>
/// <returns>当前数据是否有效</returns>
public bool Validate();
/// <returns>新的克隆</returns>
TSelf Clone();
}

View File

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

View File

@@ -7,7 +7,8 @@ namespace Snap.Hutao.Core.Abstraction;
/// 有名称的对象
/// 指示该对象可通过名称区分
/// </summary>
internal interface INamed
[HighQuality]
internal interface INamedService
{
/// <summary>
/// 名称

View File

@@ -1,22 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Abstraction;
/// <summary>
/// 可异步初始化
/// </summary>
internal interface ISupportAsyncInitialization
{
/// <summary>
/// 是否已经初始化完成
/// </summary>
public bool IsInitialized { get; }
/// <summary>
/// 异步初始化
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>初始化任务</returns>
ValueTask<bool> InitializeAsync();
}

View File

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

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Annotation;
/// <summary>
/// 本地化键
/// </summary>
[HighQuality]
[AttributeUsage(AttributeTargets.Field)]
internal class LocalizationKeyAttribute : Attribute
{
/// <summary>
/// 指定本地化键
/// </summary>
/// <param name="key">键</param>
public LocalizationKeyAttribute(string key)
{
Key = key;
}
/// <summary>
/// 键
/// </summary>
public string Key { get; }
}

View File

@@ -1,34 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.Storage;
namespace Snap.Hutao.Core.Caching;
/// <summary>
/// 为图像缓存提供抽象
/// </summary>
/// <typeparam name="T">缓存类型</typeparam>
internal interface IImageCache
[HighQuality]
internal interface IImageCache : ICastableService
{
/// <summary>
/// Gets the StorageFile containing cached item for given Uri
/// Gets the file path containing cached item for given Uri
/// </summary>
/// <param name="uri">Uri of the item.</param>
/// <returns>a StorageFile</returns>
Task<StorageFile> GetFileFromCacheAsync(Uri uri);
/// <returns>a string path</returns>
Task<string> GetFileFromCacheAsync(Uri uri);
/// <summary>
/// Removed items based on uri list passed
/// </summary>
/// <param name="uriForCachedItems">Enumerable uri list</param>
/// <returns>awaitable Task</returns>
Task RemoveAsync(IEnumerable<Uri> uriForCachedItems);
void Remove(IEnumerable<Uri> uriForCachedItems);
/// <summary>
/// Removes cached files that have expired
/// Removes invalid cached files
/// </summary>
/// <param name="duration">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref="CacheDuration"/> is used.</param>
/// <returns>awaitable task</returns>
Task RemoveExpiredAsync(TimeSpan? duration = null);
void RemoveInvalid();
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Caching;
/// <summary>
/// 图像缓存 文件路径操作
/// </summary>
[HighQuality]
internal interface IImageCacheFilePathOperation
{
/// <summary>
/// 从分类与文件名获取文件路径
/// </summary>
/// <param name="category">分类</param>
/// <param name="fileName">文件名</param>
/// <returns>文件路径</returns>
string GetFilePathFromCategoryAndFileName(string category, string fileName);
}

View File

@@ -2,15 +2,13 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.Logging;
using System.Collections.Immutable;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Windows.Storage;
using Windows.Storage.FileProperties;
namespace Snap.Hutao.Core.Caching;
@@ -18,15 +16,15 @@ namespace Snap.Hutao.Core.Caching;
/// Provides methods and tools to cache files in a folder
/// The class's name will become the cache folder's name
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton, typeof(IImageCache))]
[HttpClient(HttpClientConfigration.Default)]
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 16)]
[SuppressMessage("", "CA1001")]
public class ImageCache : IImageCache
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 8)]
internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
{
private const string DateAccessedProperty = "System.DateAccessed";
private const string CacheFolderName = nameof(ImageCache);
private static readonly ImmutableDictionary<int, TimeSpan> RetryCountToDelay = new Dictionary<int, TimeSpan>()
private static readonly Dictionary<int, TimeSpan> RetryCountToDelay = new()
{
[0] = TimeSpan.FromSeconds(4),
[1] = TimeSpan.FromSeconds(16),
@@ -34,19 +32,15 @@ public class ImageCache : IImageCache
[3] = TimeSpan.FromSeconds(4),
[4] = TimeSpan.FromSeconds(16),
[5] = TimeSpan.FromSeconds(64),
}.ToImmutableDictionary();
};
private readonly List<string> extendedPropertyNames = new() { DateAccessedProperty };
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
private readonly ILogger logger;
// violate di rule
private readonly HttpClient httpClient;
private StorageFolder? baseFolder;
private string? cacheFolderName;
private StorageFolder? cacheFolder;
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
private string? baseFolder;
private string? cacheFolder;
/// <summary>
/// Initializes a new instance of the <see cref="ImageCache"/> class.
@@ -57,115 +51,102 @@ public class ImageCache : IImageCache
{
this.logger = logger;
httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
CacheDuration = TimeSpan.FromDays(30);
RetryCount = 3;
}
/// <summary>
/// Gets or sets the life duration of every cache entry.
/// </summary>
public TimeSpan CacheDuration { get; }
/// <summary>
/// Gets or sets the number of retries trying to ensure the file is cached.
/// </summary>
public uint RetryCount { get; }
/// <summary>
/// Clears all files in the cache
/// </summary>
/// <returns>awaitable task</returns>
public async Task ClearAsync()
/// <inheritdoc/>
public void RemoveInvalid()
{
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
string folder = GetCacheFolder();
string[] files = Directory.GetFiles(folder);
await RemoveAsync(files).ConfigureAwait(false);
}
List<string> filesToDelete = new();
/// <summary>
/// Removes cached files that have expired
/// </summary>
/// <param name="duration">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref="CacheDuration"/> is used.</param>
/// <returns>awaitable task</returns>
public async Task RemoveExpiredAsync(TimeSpan? duration = null)
{
TimeSpan expiryDuration = duration ?? CacheDuration;
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
List<StorageFile> filesToDelete = new();
foreach (StorageFile file in files)
foreach (string file in files)
{
if (file == null)
{
continue;
}
if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))
if (IsFileInvalid(file, false))
{
filesToDelete.Add(file);
}
}
await RemoveAsync(filesToDelete).ConfigureAwait(false);
RemoveInternal(filesToDelete);
}
/// <summary>
/// Removed items based on uri list passed
/// </summary>
/// <param name="uriForCachedItems">Enumerable uri list</param>
/// <returns>awaitable Task</returns>
public async Task RemoveAsync(IEnumerable<Uri> uriForCachedItems)
/// <inheritdoc/>
public void Remove(IEnumerable<Uri> uriForCachedItems)
{
if (uriForCachedItems == null || !uriForCachedItems.Any())
{
return;
}
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
string folder = GetCacheFolder();
string[] files = Directory.GetFiles(folder);
List<StorageFile> filesToDelete = new();
Dictionary<string, StorageFile> cachedFiles = files.ToDictionary(file => file.Name);
List<string> filesToDelete = new();
foreach (Uri uri in uriForCachedItems)
{
string fileName = GetCacheFileName(uri);
if (cachedFiles.TryGetValue(fileName, out StorageFile? file))
string filePath = Path.Combine(folder, GetCacheFileName(uri));
if (Array.IndexOf(files, filePath) >= 0)
{
filesToDelete.Add(file);
filesToDelete.Add(filePath);
}
}
await RemoveAsync(filesToDelete).ConfigureAwait(false);
RemoveInternal(filesToDelete);
}
/// <summary>
/// Gets the StorageFile containing cached item for given Uri
/// </summary>
/// <param name="uri">Uri of the item.</param>
/// <returns>a StorageFile</returns>
public async Task<StorageFile> GetFileFromCacheAsync(Uri uri)
/// <inheritdoc/>
public async Task<string> GetFileFromCacheAsync(Uri uri)
{
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
string fileName = GetCacheFileName(uri);
string filePath = Path.Combine(GetCacheFolder(), fileName);
IStorageItem? item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
if (item == null || (await item.GetBasicPropertiesAsync()).Size == 0)
if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0)
{
StorageFile baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false);
await DownloadFileAsync(uri, baseFile).ConfigureAwait(false);
item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
TaskCompletionSource taskCompletionSource = new();
try
{
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
{
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
}
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
{
await task.ConfigureAwait(false);
}
concurrentTasks.TryRemove(fileName, out _);
}
finally
{
taskCompletionSource.TrySetResult();
}
}
return Must.NotNull((item as StorageFile)!);
return filePath;
}
/// <inheritdoc/>
public string GetFilePathFromCategoryAndFileName(string category, string fileName)
{
Uri dummyUri = new(Web.HutaoEndpoints.StaticFile(category, fileName));
return Path.Combine(GetCacheFolder(), GetCacheFileName(dummyUri));
}
private static void RemoveInternal(IEnumerable<string> filePaths)
{
foreach (string filePath in filePaths)
{
try
{
File.Delete(filePath);
}
catch
{
}
}
}
private static string GetCacheFileName(Uri uri)
@@ -176,50 +157,21 @@ public class ImageCache : IImageCache
return System.Convert.ToHexString(hash);
}
/// <summary>
/// Override-able method that checks whether file is valid or not.
/// </summary>
/// <param name="file">storage file</param>
/// <param name="duration">cache duration</param>
/// <param name="treatNullFileAsOutOfDate">option to mark uninitialized file as expired</param>
/// <returns>bool indicate whether file has expired or not</returns>
private async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
private static bool IsFileInvalid(string file, bool treatNullFileAsInvalid = true)
{
if (file == null)
if (!File.Exists(file))
{
return treatNullFileAsOutOfDate;
return treatNullFileAsInvalid;
}
// Get extended properties.
IDictionary<string, object> extraProperties = await file.Properties
.RetrievePropertiesAsync(extendedPropertyNames)
.AsTask()
.ConfigureAwait(false);
// Get date-accessed property.
object? propValue = extraProperties[DateAccessedProperty];
if (propValue != null)
{
DateTimeOffset? lastAccess = propValue as DateTimeOffset?;
if (lastAccess.HasValue)
{
return DateTime.Now.Subtract(lastAccess.Value.DateTime) > duration;
}
}
BasicProperties properties = await file
.GetBasicPropertiesAsync()
.AsTask()
.ConfigureAwait(false);
return properties.Size == 0 || DateTime.Now.Subtract(properties.DateModified.DateTime) > duration;
FileInfo fileInfo = new(file);
return fileInfo.Length == 0;
}
private async Task DownloadFileAsync(Uri uri, StorageFile baseFile)
private async Task DownloadFileAsync(Uri uri, string baseFile)
{
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
logger.LogInformation("Begin downloading for {uri}", uri);
int retryCount = 0;
while (retryCount < 6)
@@ -230,18 +182,23 @@ public class ImageCache : IImageCache
{
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
using (FileStream fileStream = File.Create(baseFile.Path))
using (FileStream fileStream = File.Create(baseFile))
{
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
return;
}
}
}
else if (message.StatusCode == HttpStatusCode.NotFound)
{
// directly goto https://static.hut.ao
retryCount += 3;
}
else if (message.StatusCode == HttpStatusCode.TooManyRequests)
{
retryCount++;
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
logger.LogInformation("Retry after {delay}.", delay);
logger.LogInformation("Retry {uri} after {delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false);
}
else
@@ -252,61 +209,20 @@ public class ImageCache : IImageCache
if (retryCount == 3)
{
uri = new UriBuilder(uri) { Host = "static.hut.ao", }.Uri;
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao }.Uri;
}
}
}
/// <summary>
/// Initializes with default values if user has not initialized explicitly
/// </summary>
/// <returns>awaitable task</returns>
private async Task InitializeInternalAsync()
{
if (cacheFolder != null)
{
return;
}
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
{
baseFolder ??= ApplicationData.Current.TemporaryFolder;
if (string.IsNullOrWhiteSpace(cacheFolderName))
{
cacheFolderName = GetType().Name;
}
cacheFolder = await baseFolder
.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
.AsTask()
.ConfigureAwait(false);
}
}
private async Task<StorageFolder> GetCacheFolderAsync()
private string GetCacheFolder()
{
if (cacheFolder == null)
{
await InitializeInternalAsync().ConfigureAwait(false);
baseFolder ??= ApplicationData.Current.LocalCacheFolder.Path;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
cacheFolder = info.FullName;
}
return Must.NotNull(cacheFolder!);
}
private async Task RemoveAsync(IEnumerable<StorageFile> files)
{
foreach (StorageFile file in files)
{
try
{
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file.Path);
await file.DeleteAsync().AsTask().ConfigureAwait(false);
}
catch
{
logger.LogError(EventIds.CacheException, "Failed to delete file: {file}", file.Path);
}
}
return cacheFolder!;
}
}

View File

@@ -8,7 +8,8 @@ namespace Snap.Hutao.Core;
/// <summary>
/// 命令行建造器
/// </summary>
public class CommandLineBuilder
[HighQuality]
internal sealed class CommandLineBuilder
{
private const char WhiteSpace = ' ';
private readonly Dictionary<string, string?> options = new();
@@ -56,6 +57,7 @@ public class CommandLineBuilder
{
s.Append(WhiteSpace);
s.Append(key);
if (!string.IsNullOrEmpty(value))
{
s.Append(WhiteSpace);

View File

@@ -4,19 +4,20 @@
using System.Security.Cryptography;
using System.Text;
namespace Snap.Hutao.Core.Convert;
namespace Snap.Hutao.Core;
/// <summary>
/// 支持Md5转换
/// </summary>
internal abstract class Md5Convert
[HighQuality]
internal static class Convert
{
/// <summary>
/// 获取字符串的MD5计算结果
/// </summary>
/// <param name="source">源字符串</param>
/// <returns>计算的结果</returns>
public static string ToHexString(string source)
public static string ToMd5HexString(string source)
{
byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(source));
return System.Convert.ToHexString(hash);

View File

@@ -2,19 +2,21 @@
// Licensed under the MIT license.
using Microsoft.Win32;
using Snap.Hutao.Core.Convert;
using Snap.Hutao.Core.Json;
using Snap.Hutao.Extension;
using System.Runtime.InteropServices;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Collections.Immutable;
using System.IO;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization.Metadata;
using Windows.ApplicationModel;
using Windows.Win32.Foundation;
namespace Snap.Hutao.Core;
/// <summary>
/// 核心环境参数
/// </summary>
[HighQuality]
internal static class CoreEnvironment
{
/// <summary>
@@ -25,12 +27,48 @@ internal static class CoreEnvironment
/// <summary>
/// 米游社移动端请求UA
/// </summary>
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 miHoYoBBS/{HoyolabXrpcVersion}";
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{HoyolabXrpcVersion}";
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string HoyolabXrpcVersion = "2.42.1";
public const string HoyolabXrpcVersion = "2.44.1";
/// <summary>
/// 盐
/// </summary>
// https://github.com/UIGF-org/Hoyolab.Salt
public static readonly ImmutableDictionary<SaltType, string> DynamicSecretSalts = new Dictionary<SaltType, string>()
{
[SaltType.K2] = "dZAwGk4e9aC0MXXItkwnHamjA1x30IYw",
[SaltType.LK2] = "IEIZiKYaput2OCKQprNuGsog1NZc1FkS",
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
}.ToImmutableDictionary();
/// <summary>
/// 默认的Json序列化选项
/// </summary>
public static readonly JsonSerializerOptions JsonOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers =
{
JsonTypeInfoResolvers.ResolveEnumType,
},
},
};
/// <summary>
/// 当前版本
/// </summary>
public static readonly Version Version;
/// <summary>
/// 标准UA
@@ -38,9 +76,14 @@ internal static class CoreEnvironment
public static readonly string CommonUA;
/// <summary>
/// 当前版本
/// 数据文件夹
/// </summary>
public static readonly Version Version;
public static readonly string DataFolder;
/// <summary>
/// 包家族名称
/// </summary>
public static readonly string FamilyName;
/// <summary>
/// 米游社设备Id
@@ -53,35 +96,19 @@ internal static class CoreEnvironment
public static readonly string HutaoDeviceId;
/// <summary>
/// 包家族名称
/// 安装位置
/// </summary>
public static readonly string FamilyName;
/// <summary>
/// 默认的Json序列化选项
/// </summary>
public static readonly JsonSerializerOptions JsonOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = new JsonTextEncoder(),
PropertyNameCaseInsensitive = true,
WriteIndented = true,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers =
{
JsonTypeInfoResolvers.ResolveEnumType,
},
},
};
public static readonly string InstalledLocation;
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
private const string MachineGuidValue = "MachineGuid";
static CoreEnvironment()
{
DataFolder = GetDatafolderPath();
Version = Package.Current.Id.Version.ToVersion();
FamilyName = Package.Current.Id.FamilyName;
InstalledLocation = Package.Current.InstalledLocation.Path;
CommonUA = $"Snap Hutao/{Version}";
// simply assign a random guid
@@ -93,6 +120,30 @@ internal static class CoreEnvironment
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
return Md5Convert.ToHexString($"{userName}{machineGuid}");
return Convert.ToMd5HexString($"{userName}{machineGuid}");
}
private static string GetDatafolderPath()
{
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
if (string.IsNullOrEmpty(preferredPath))
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#if RELEASE
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
#else
// 使得迁移能正常生成
string folderName = "Hutao";
#endif
string path = Path.GetFullPath(Path.Combine(myDocument, folderName));
Directory.CreateDirectory(path);
return path;
}
else
{
return preferredPath;
}
}
}

View File

@@ -12,7 +12,8 @@ namespace Snap.Hutao.Core.Database;
/// </summary>
/// <typeparam name="TEntity">实体的类型</typeparam>
/// <typeparam name="TMessage">消息的类型</typeparam>
internal class DbCurrent<TEntity, TMessage>
[HighQuality]
internal sealed class DbCurrent<TEntity, TMessage>
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TEntity>, new()
{

View File

@@ -3,13 +3,15 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Database;
/// <summary>
/// 数据库集合上下文
/// 数据库集合扩展
/// </summary>
public static class DbSetExtension
[HighQuality]
internal static class DbSetExtension
{
/// <summary>
/// 获取对应的数据库上下文
@@ -17,6 +19,7 @@ public static class DbSetExtension
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <returns>对应的数据库上下文</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
where TEntity : class
{
@@ -134,4 +137,4 @@ public static class DbSetExtension
dbSet.Update(entity);
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
}
}
}

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