Compare commits

..

137 Commits

Author SHA1 Message Date
DismissedLight
85b9fc1a08 fix setting restart 2023-03-13 21:01:55 +08:00
DismissedLight
5aba2eab97 support multi-clienting 2023-03-13 19:13:35 +08:00
DismissedLight
dc8d7ac913 Merge pull request #598 from GashByte/main
Add 'Mult-Start' & fix a start game bug
2023-03-12 22:32:23 +08:00
x3zF Love U
a832ea96ea repair 'LaunchMultipleInstancesGameAsync' 2023-03-12 22:24:29 +08:00
x3zF Love U
29582efaee reduce the Task waiting time of mult-start 2023-03-12 18:15:40 +08:00
x3zF Love U
6caeb1d238 Add 'Mult-Start' & fix a start game bug 2023-03-12 16:32:59 +08:00
DismissedLight
439a8dd475 Merge pull request #594 from GashByte/main
add 'Copy' Button to 'SettingPage - Device Id'
2023-03-11 16:04:56 +08:00
x3zF Love U
60c7e65abb add 'Copy' Button to 'SettingPage - Device Id' 2023-03-11 15:42:21 +08:00
DismissedLight
5c0984b064 rescale assets [skip ci] 2023-03-11 13:19:45 +08:00
DismissedLight
9b8cce30a7 fix package logo 2023-03-11 13:02:26 +08:00
DismissedLight
463a842cb1 update assets 2023-03-10 21:48:14 +08:00
DismissedLight
8ee2908633 launch screenshot folder 2023-03-10 20:32:49 +08:00
DismissedLight
c71ecd89e3 support game resource download switch 2023-03-07 16:28:00 +08:00
DismissedLight
128b985609 fix game resource convert 2023-03-06 11:29:53 +08:00
DismissedLight
b1a03662d9 launch game impl switch monitor 2023-03-05 18:47:36 +08:00
DismissedLight
706fb3404b refactor localization 2023-03-03 16:49:27 +08:00
DismissedLight
63e1273d6a Merge pull request #582 from DGP-Studio/l10n_main
New Crowdin updates
2023-03-03 15:33:45 +08:00
Masterain
400a3b99ae New translations SH.resx (English) 2023-03-02 23:31:00 -08:00
Masterain
c0c1774db8 New translations SH.resx (Chinese Traditional) 2023-03-02 23:30:59 -08:00
Masterain
099fbf4052 New translations SH.resx (Korean) 2023-03-02 23:30:58 -08:00
Masterain
5c4405e545 Update Crowdin configuration file 2023-03-02 23:30:30 -08:00
DismissedLight
ac68579d6a fix #567 2023-02-28 12:35:49 +08:00
DismissedLight
f3387bb8c8 fix #566 2023-02-28 11:52:43 +08:00
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
855 changed files with 25815 additions and 9242 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
@@ -96,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,7 +21,7 @@ body:
**在填写下面的问题之前请先使用我们的网络诊断工具**
**这个工具将会生成一份报告,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
- 你可以点击下面的链接以下载网络诊断工具:
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-tool.exe)
- [胡桃资源站](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

View File

@@ -2,7 +2,7 @@ name: PublishDistribution
on:
release:
types: [published]
types: [released]
workflow_dispatch:
@@ -23,7 +23,7 @@ jobs:
with:
repository: "DGP-Studio/Snap.Hutao"
latest: true
fileName: "*.zip"
fileName: "*.msix"
out-file-path: ./release-download
# Upload to Drive

View File

@@ -1,12 +1,14 @@
# [Snap.Hutao](https://hut.ao)
![](https://repository-images.githubusercontent.com/482734649/5f8cf574-2ef0-43e9-aa8d-6cf094b54dd9)
> 唷,找本堂主有何贵干呀?
## 下载使用
[<img src="https://get.microsoft.com/images/zh-cn%20light.svg" width="30%" height="30%">](https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52)
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
## 贡献
# 特别感谢
* [向我们提交 PR](https://github.com/DGP-Studio/Snap.Hutao/pulls)
* [在 Crowdin 上进行本地化](https://translate.hut.ao/)
## 特别感谢
* [HolographicHat](https://github.com/HolographicHat)
* [UIGF organization](https://uigf.org)
@@ -30,4 +32,13 @@
* [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)
* [WinUICommunity/SettingsUI](https://github.com/WinUICommunity/SettingsUI)
* [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

@@ -17,6 +17,7 @@ trigger:
- azure-pipelines.yml
- .github/ISSUE_TEMPLATE/*.yml
- .github/workflows/*.yml
- src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
pr:
branches:
include:
@@ -27,6 +28,7 @@ pr:
- azure-pipelines.yml
- .github/ISSUE_TEMPLATE/*.yml
- .github/workflows/*.yml
- src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
pool:
@@ -125,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
@@ -145,7 +150,6 @@ steps:
secureFile: 'Snap.Hutao.CI.cer'
- task: GitHubRelease@1
condition: or(eq(variables['Build.Reason'], 'Manual'), eq(variables['Build.Reason'], 'IndividualCI'), eq(variables['Build.Reason'], 'BatchedCI'))
inputs:
gitHubConnection: 'github.com_Masterain'
repositoryName: 'DGP-Studio/Snap.Hutao'
@@ -156,10 +160,11 @@ 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)
@@ -176,5 +181,5 @@ steps:
- task: rclone@1
displayName: Upload CI via Rclone
inputs:
arguments: 'copy $(Build.ArtifactStagingDirectory)/* downloadDGPCN:/releases/Alpha/'
configPath: '$(RcloneConfigFile.secureFilePath)/rclone.conf'
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.%osx_locale%.resx

Binary file not shown.

BIN
res/HutaoIcon2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

BIN
res/HutaoIcon2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
res/HutaoIconSource.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

3
res/LEGAL NOTICE.md Normal file
View File

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

View File

@@ -10,7 +10,7 @@ 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

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

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

@@ -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

@@ -12,8 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
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
@@ -50,8 +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
{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
@@ -66,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

@@ -16,13 +16,14 @@
<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"/>
@@ -43,12 +44,16 @@
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!-- OpenPaneLength -->
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength2">252</x:Double>
<GridLength x:Key="CompatGridLength2">252</GridLength>
<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#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-cookie</x:String>
<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>
@@ -61,12 +66,13 @@
<!-- Converters -->
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<cwuc:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
<shmmc:ParameterDescriptor x:Key="DescParamDescriptor"/>
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
<shmmc:EmotionIconConverter x:Key="EmotionIconConverter"/>
<shmmc:EquipIconConverter x:Key="EquipIconConverter"/>
@@ -74,14 +80,11 @@
<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:EmptyCollectionToBoolConverter x:Key="EmptyCollectionToBoolConverter"/>
<shvc:EmptyCollectionToBoolRevertConverter x:Key="EmptyCollectionToBoolRevertConverter"/>
<shvc:EmptyCollectionToVisibilityConverter x:Key="EmptyCollectionToVisibilityConverter"/>
<shvc:EmptyCollectionToVisibilityRevertConverter x:Key="EmptyCollectionToVisibilityRevertConverter"/>
<shvc:EmptyObjectToBoolConverter x:Key="EmptyObjectToBoolConverter"/>
<shvc:EmptyObjectToBoolRevertConverter x:Key="EmptyObjectToBoolRevertConverter"/>
<shvc:EmptyObjectToVisibilityConverter x:Key="EmptyObjectToVisibilityConverter"/>
@@ -112,6 +115,328 @@
<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/>
@@ -121,4 +446,4 @@
</ItemsPanelTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
</Application>

View File

@@ -3,22 +3,24 @@
using CommunityToolkit.WinUI.Notifications;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Logging;
using System.Diagnostics;
using Windows.ApplicationModel.Background;
using Windows.Storage;
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;
@@ -26,7 +28,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
@@ -51,8 +52,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.LocalCacheFolder.Path);
logger.LogInformation("Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
logger.LogInformation("Cache folder : {folder}", ApplicationData.Current.LocalCacheFolder.Path);
JumpListHelper.ConfigureAsync().SafeForget(logger);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

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

@@ -8,23 +8,25 @@ namespace Snap.Hutao.Control.Extension;
/// <summary>
/// 对话框扩展
/// </summary>
internal static class ContentDialogExtensions
[HighQuality]
internal static class ContentDialogExtension
{
/// <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;
@@ -33,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

@@ -5,7 +5,6 @@ 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;
namespace Snap.Hutao.Control.Image;
@@ -13,7 +12,8 @@ namespace Snap.Hutao.Control.Image;
/// <summary>
/// 缓存图像
/// </summary>
public class CachedImage : ImageEx
[HighQuality]
internal sealed class CachedImage : ImageEx
{
/// <summary>
/// 构造一个新的缓存图像
@@ -22,17 +22,20 @@ public class CachedImage : ImageEx
{
IsCacheEnabled = true;
EnableLazyLoading = true;
LazyLoadingThreshold = 500;
}
/// <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");
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.

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;
}
@@ -172,25 +178,6 @@ 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)
{
CompositionMaskBrush brush = compositor.CreateMaskBrush();
brush.Source = source;
brush.Mask = mask;
return brush;
}
private static Vector2 GetStartPointOfDirection(GradientDirection direction)
{
return direction switch
@@ -216,6 +203,4 @@ internal static class CompositionExtensions
_ => Vector2.Zero,
};
}
public record struct GradientStop(float Offset, Windows.UI.Color Color);
}
}

View File

@@ -2,12 +2,12 @@
// 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;
@@ -19,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;
@@ -34,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;
}
@@ -48,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>
@@ -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,35 +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
{
string 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)
{
imageCache.Remove(uri.Enumerate());
}
catch (IOException)
{
imageCache.Remove(uri.Enumerate());
}
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)
@@ -161,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;
}
}
}
@@ -170,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

@@ -5,16 +5,15 @@ using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using System.IO;
using Windows.Graphics.Imaging;
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);
@@ -44,7 +43,7 @@ public class Gradient : CompositionImage
{
if (spriteVisual is not null)
{
Height = (double)Math.Clamp(ActualWidth / imageAspectRatio, 0, MaxHeight);
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
spriteVisual.Size = ActualSize;
}
}
@@ -52,19 +51,11 @@ public class Gradient : CompositionImage
/// <inheritdoc/>
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
{
using (FileStream fileStream = new(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (IRandomAccessStream imageStream = fileStream.AsRandomAccessStream())
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
imageAspectRatio = decoder.PixelWidth / (double)decoder.PixelHeight;
}
}
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;
}

View File

@@ -6,7 +6,8 @@ namespace Snap.Hutao.Control.Image;
/// <summary>
/// 渐变方向
/// </summary>
public enum GradientDirection
[HighQuality]
internal enum GradientDirection
{
/// <summary>
/// 下到上

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

@@ -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

@@ -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,33 +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
Name="RootSplitButton"
Padding="0,6"
Click="SplitButtonClick">
<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", OnCurrentChanged);
private const string List = nameof(List);
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), List, OnCurrentChanged);
/// <summary>
/// 构造一个新的面板选择器

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

@@ -13,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>
@@ -36,8 +55,8 @@ public class ScopedPage : Page
public void InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
IViewModel viewModel = serviceScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewLoadingCancellationTokenSource.Token;
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
@@ -59,11 +78,10 @@ public class ScopedPage : Page
/// <inheritdoc/>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
using (viewLoadingCancellationTokenSource)
using (viewCancellationTokenSource)
{
// Cancel tasks executed by the view model
viewLoadingCancellationTokenSource.Cancel();
// Cancel all tasks executed by the view model
viewCancellationTokenSource.Cancel();
IViewModel viewModel = (IViewModel)DataContext;
using (SemaphoreSlim locker = viewModel.DisposeLock)
@@ -73,20 +91,23 @@ public class ScopedPage : Page
viewModel.IsViewDisposed = true;
// Dispose the scope
serviceScope.Dispose();
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

@@ -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 ValueConverter<TFrom, TTo> : IValueConverter
internal abstract class ValueConverter<TFrom, TTo> : IValueConverter
{
/// <inheritdoc/>
public object? Convert(object value, Type targetType, object parameter, string language)

View File

@@ -3,8 +3,9 @@
namespace Snap.Hutao.Core.Abstraction;
[HighQuality]
[SuppressMessage("", "SA1600")]
public abstract class DisposableObject : IDisposable
internal abstract class DisposableObject : IDisposable
{
public bool IsDisposed { get; private set; }

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; }
}

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