mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e50c1b9184 | ||
|
|
54535cd822 | ||
|
|
ece2737633 | ||
|
|
4b012424b9 | ||
|
|
6d66af6c84 | ||
|
|
027874d4cf | ||
|
|
3001936ab3 | ||
|
|
400e097fa7 | ||
|
|
ffce055d75 | ||
|
|
a820c41ad7 | ||
|
|
485010c895 | ||
|
|
5feddf566e | ||
|
|
cdfe306b16 | ||
|
|
189c61ddea | ||
|
|
0ac7d6e94d | ||
|
|
042e3b5747 | ||
|
|
0372f1a8e3 | ||
|
|
08a630fd43 | ||
|
|
3781cad896 | ||
|
|
78bc2f09d5 | ||
|
|
8c52921b3e | ||
|
|
605aecb216 | ||
|
|
e3e124d52f | ||
|
|
0866e1947b | ||
|
|
dbcb7dd879 | ||
|
|
44687dd87b | ||
|
|
1a209f6c8d | ||
|
|
8633b78725 | ||
|
|
2e20701c6c | ||
|
|
9c4d4cda1e | ||
|
|
a32481980b | ||
|
|
b5577e76a5 | ||
|
|
6c2ff9b3c9 | ||
|
|
818365b816 | ||
|
|
d7dd8c6f0d | ||
|
|
faad104e0e | ||
|
|
2f6ee75f80 | ||
|
|
34f319bdac | ||
|
|
f242808768 | ||
|
|
98f18f91d8 | ||
|
|
0fd1f6959a | ||
|
|
ba46ed64db | ||
|
|
0fb8312605 | ||
|
|
b722554950 | ||
|
|
165c33ef2c | ||
|
|
54bb3d634b | ||
|
|
629975480a | ||
|
|
5a36448c23 | ||
|
|
80a6aaab46 | ||
|
|
6c83cd3da5 | ||
|
|
e60a04a2bc | ||
|
|
aec483510f | ||
|
|
c245fe654e | ||
|
|
898d95bb1d | ||
|
|
1df22e5b75 | ||
|
|
332e09fef0 | ||
|
|
2a77daf2ca | ||
|
|
8a47ea8727 | ||
|
|
b3937ac810 | ||
|
|
ed5c52dc63 | ||
|
|
461d139602 | ||
|
|
164ec2af33 | ||
|
|
e30523c621 | ||
|
|
11d0405102 | ||
|
|
a1c0b4f830 | ||
|
|
e476ed5960 | ||
|
|
ffc999360d | ||
|
|
84058011c7 | ||
|
|
c18e0c40c5 | ||
|
|
ad78515094 | ||
|
|
38367a090d | ||
|
|
ce30f609fb | ||
|
|
f4b9cc7c48 | ||
|
|
7c2212f44c | ||
|
|
95eddef457 | ||
|
|
02447bc966 | ||
|
|
fb88e33d16 | ||
|
|
5fa36416ef | ||
|
|
7076caaa5d | ||
|
|
b7b1155cfc | ||
|
|
6351f2b460 | ||
|
|
35ac2f33ba | ||
|
|
f8ff1988bb | ||
|
|
907d70ba71 | ||
|
|
a5bdc17712 | ||
|
|
f078d92f33 | ||
|
|
f2d4f0f1d3 | ||
|
|
fcde9b21ae | ||
|
|
24f09861fd | ||
|
|
47708adc83 | ||
|
|
79a254235a | ||
|
|
d9bcb3b16b | ||
|
|
cf7dd548a2 | ||
|
|
04deeb7086 | ||
|
|
9fb2da41cd | ||
|
|
bb01f3a3cb | ||
|
|
f7f2d9c867 | ||
|
|
01b7e58b3e | ||
|
|
2518ae0b90 | ||
|
|
7d4a8cdcd9 | ||
|
|
623893e00e | ||
|
|
0d34c81bcf | ||
|
|
5f3d0126b3 | ||
|
|
5d1fe3f38a | ||
|
|
c810ffa625 | ||
|
|
ee70205245 | ||
|
|
06c8b347d3 | ||
|
|
5c6ab1dee9 | ||
|
|
ad440e0561 | ||
|
|
ca56d8c636 | ||
|
|
da0ee0cca6 | ||
|
|
5d00d9cc0d | ||
|
|
e8b27e6655 | ||
|
|
0ac79012d1 | ||
|
|
bb2665b75e | ||
|
|
d22ac39c1d | ||
|
|
a312603d61 | ||
|
|
0732ea0e06 | ||
|
|
e4d2b3055c | ||
|
|
5668931230 | ||
|
|
5126337138 | ||
|
|
4d634d3264 | ||
|
|
15a69fd0de | ||
|
|
c232891fe7 | ||
|
|
c35c2a5700 | ||
|
|
42305094f8 | ||
|
|
9ef48ab05c | ||
|
|
eec010870a | ||
|
|
a24fbf535d | ||
|
|
f7bd184a3c | ||
|
|
267f285101 | ||
|
|
2a1e77a9db | ||
|
|
abdc8e2e9f | ||
|
|
64f1af293b | ||
|
|
e0336d6b30 | ||
|
|
23f3e5df77 | ||
|
|
4a027a8d3f | ||
|
|
80459708a7 | ||
|
|
bf08ffa89e | ||
|
|
af4180bdeb | ||
|
|
a70593c529 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal 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
|
||||
27
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
27
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -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`文件,该文件包含你的帐号敏感信息**
|
||||
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
id: back
|
||||
attributes:
|
||||
label: 背景与动机
|
||||
description: 添加此功能的理由
|
||||
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/network-issue.yml
vendored
10
.github/ISSUE_TEMPLATE/network-issue.yml
vendored
@@ -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
|
||||
|
||||
20
.github/workflows/PublishDistribution.yml
vendored
20
.github/workflows/PublishDistribution.yml
vendored
@@ -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
|
||||
|
||||
24
README.md
24
README.md
@@ -1,11 +1,14 @@
|
||||
# [Snap.Hutao](https://hut.ao)
|
||||
> 唷,找本堂主有何贵干呀?
|
||||

|
||||
|
||||

|
||||
## 下载使用
|
||||
[<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)
|
||||
@@ -29,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)
|
||||
|
||||
## 近期活跃数据
|
||||

|
||||
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal 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)
|
||||
@@ -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:
|
||||
@@ -91,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
|
||||
@@ -124,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
|
||||
@@ -144,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'
|
||||
@@ -155,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'
|
||||
|
||||
- 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
3
crowdin.yml
Normal 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
|
||||
@@ -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]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}]");
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,24 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<muxc:XamlControlsResources/>
|
||||
<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>
|
||||
@@ -44,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>
|
||||
@@ -67,7 +71,7 @@
|
||||
<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"/>
|
||||
@@ -75,14 +79,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"/>
|
||||
@@ -90,6 +91,7 @@
|
||||
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
|
||||
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
|
||||
<!-- Styles -->
|
||||
|
||||
<Style
|
||||
x:Key="LargeGridViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||
@@ -112,6 +114,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 +445,4 @@
|
||||
</ItemsPanelTemplate>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
</Application>
|
||||
@@ -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.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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,35 +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);
|
||||
#if RELEASE
|
||||
// 将测试版与正式版的文件目录分离
|
||||
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
|
||||
#else
|
||||
// 使得迁移能正常生成
|
||||
string folderName = "Hutao";
|
||||
#endif
|
||||
path = Path.GetFullPath(Path.Combine(myDocument, folderName));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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>
|
||||
/// 构造一个新的缓存图像
|
||||
@@ -23,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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +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;
|
||||
|
||||
@@ -21,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;
|
||||
@@ -36,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;
|
||||
}
|
||||
|
||||
@@ -50,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>
|
||||
@@ -82,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
|
||||
@@ -117,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)
|
||||
@@ -163,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,15 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System.IO;
|
||||
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);
|
||||
@@ -28,8 +25,8 @@ public class Gradient : CompositionImage
|
||||
/// </summary>
|
||||
public GradientDirection BackgroundDirection
|
||||
{
|
||||
get { return (GradientDirection)GetValue(BackgroundDirectionProperty); }
|
||||
set { SetValue(BackgroundDirectionProperty, value); }
|
||||
get => (GradientDirection)GetValue(BackgroundDirectionProperty);
|
||||
set => SetValue(BackgroundDirectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,8 +34,8 @@ public class Gradient : CompositionImage
|
||||
/// </summary>
|
||||
public GradientDirection ForegroundDirection
|
||||
{
|
||||
get { return (GradientDirection)GetValue(ForegroundDirectionProperty); }
|
||||
set { SetValue(ForegroundDirectionProperty, value); }
|
||||
get => (GradientDirection)GetValue(ForegroundDirectionProperty);
|
||||
set => SetValue(ForegroundDirectionProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -46,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;
|
||||
}
|
||||
}
|
||||
@@ -54,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <summary>
|
||||
/// 渐变方向
|
||||
/// </summary>
|
||||
public enum GradientDirection
|
||||
[HighQuality]
|
||||
internal enum GradientDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// 下到上
|
||||
|
||||
34
src/Snap.Hutao/Snap.Hutao/Control/Image/GradientStop.cs
Normal file
34
src/Snap.Hutao/Snap.Hutao/Control/Image/GradientStop.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// BGRA8 结构
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Bgra8
|
||||
internal struct Bgra8
|
||||
{
|
||||
/// <summary>
|
||||
/// B
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// 软件位图拓展
|
||||
/// </summary>
|
||||
public static class SoftwareBitmapExtension
|
||||
[HighQuality]
|
||||
internal static class SoftwareBitmapExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 混合模式 正常
|
||||
|
||||
65
src/Snap.Hutao/Snap.Hutao/Control/Panel/AspectRatio.cs
Normal file
65
src/Snap.Hutao/Snap.Hutao/Control/Panel/AspectRatio.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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=""/>
|
||||
</SplitButton.Content>
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="List"
|
||||
Text="列表"/>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="Grid"
|
||||
Text="网格"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
</UserControl>
|
||||
<SplitButton.Content>
|
||||
<FontIcon Name="IconPresenter" Glyph=""/>
|
||||
</SplitButton.Content>
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="List"
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="Grid"
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
210
src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
Normal file
210
src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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, "值转换器异常");
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -7,7 +7,8 @@ namespace Snap.Hutao.Core.Abstraction;
|
||||
/// 有名称的对象
|
||||
/// 指示该对象可通过名称区分
|
||||
/// </summary>
|
||||
internal interface INamed
|
||||
[HighQuality]
|
||||
internal interface INamedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
// 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 file path containing cached item for given Uri
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.Caching;
|
||||
/// <summary>
|
||||
/// 图像缓存 文件路径操作
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IImageCacheFilePathOperation
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// 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;
|
||||
@@ -17,14 +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 = 8)]
|
||||
public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
{
|
||||
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),
|
||||
@@ -32,13 +32,13 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
[3] = TimeSpan.FromSeconds(4),
|
||||
[4] = TimeSpan.FromSeconds(16),
|
||||
[5] = TimeSpan.FromSeconds(64),
|
||||
}.ToImmutableDictionary();
|
||||
};
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
private string? baseFolder;
|
||||
private string? cacheFolder;
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
foreach (Uri uri in uriForCachedItems)
|
||||
{
|
||||
string filePath = Path.Combine(folder, GetCacheFileName(uri));
|
||||
if (files.Contains(filePath))
|
||||
if (Array.IndexOf(files, filePath) >= 0)
|
||||
{
|
||||
filesToDelete.Add(filePath);
|
||||
}
|
||||
@@ -100,11 +100,29 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
/// <inheritdoc/>
|
||||
public async Task<string> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
string filePath = Path.Combine(GetCacheFolder(), GetCacheFileName(uri));
|
||||
string fileName = GetCacheFileName(uri);
|
||||
string filePath = Path.Combine(GetCacheFolder(), fileName);
|
||||
|
||||
if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0)
|
||||
{
|
||||
await DownloadFileAsync(uri, filePath).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 filePath;
|
||||
@@ -153,7 +171,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
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)
|
||||
@@ -174,7 +192,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
else if (message.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
// directly goto https://static.hut.ao
|
||||
retryCount = 3;
|
||||
retryCount += 3;
|
||||
}
|
||||
else if (message.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
@@ -191,7 +209,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
if (retryCount == 3)
|
||||
{
|
||||
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao, }.Uri;
|
||||
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao }.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -2,11 +2,12 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Convert;
|
||||
using Snap.Hutao.Core.Json;
|
||||
using Snap.Hutao.Extension;
|
||||
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;
|
||||
|
||||
@@ -15,6 +16,7 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 核心环境参数
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class CoreEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
@@ -25,35 +27,63 @@ 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.43.1";
|
||||
public const string HoyolabXrpcVersion = "2.44.1";
|
||||
|
||||
/// <summary>
|
||||
/// 盐
|
||||
/// </summary>
|
||||
// https://github.com/UIGF-org/Hoyolab.Salt
|
||||
public static readonly ImmutableDictionary<string, string> DynamicSecrets = new Dictionary<string, string>()
|
||||
public static readonly ImmutableDictionary<SaltType, string> DynamicSecretSalts = new Dictionary<SaltType, string>()
|
||||
{
|
||||
[nameof(SaltType.K2)] = "ODzG1Jrn6zebX19VRmaJwjFI2CDvBUGq",
|
||||
[nameof(SaltType.LK2)] = "V1PYbXKQY7ysdx3MNCcNbsE1LtY2QZpW",
|
||||
[nameof(SaltType.X4)] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
|
||||
[nameof(SaltType.X6)] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
|
||||
[nameof(SaltType.PROD)] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
|
||||
[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
|
||||
/// </summary>
|
||||
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
|
||||
@@ -66,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
|
||||
@@ -106,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可枚举扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取选中的值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>选中的值或默认值</returns>
|
||||
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
|
||||
where TSource : ISelectable
|
||||
{
|
||||
return source.SingleOrDefault(i => i.IsSelected);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可选择的项
|
||||
/// 若要使用 <see cref="DbCurrent{TEntity, TMessage}"/> 必须实现该接口
|
||||
/// 若要使用 <see cref="DbCurrent{TEntity, TMessage}"/>
|
||||
/// 或 <see cref="ScopedDbCurrent{TEntity, TMessage}"/>
|
||||
/// 必须实现该接口
|
||||
/// </summary>
|
||||
public interface ISelectable
|
||||
[HighQuality]
|
||||
internal interface ISelectable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置当前项的选中状态
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可查询扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class QueryableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// source.Where(predicate).ExecuteDelete()
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <param name="predicate">条件</param>
|
||||
/// <returns>SQL返回个数</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ExecuteDeleteWhere<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
|
||||
{
|
||||
return source.Where(predicate).ExecuteDelete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// source.Where(predicate).ExecuteDeleteAsync(token)
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <param name="predicate">条件</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>SQL返回个数</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
|
||||
{
|
||||
return source.Where(predicate).ExecuteDeleteAsync(token);
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal class ScopedDbCurrent<TEntity, TMessage>
|
||||
internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
{
|
||||
private readonly IServiceScopeFactory scopeFactory;
|
||||
private readonly Func<IServiceProvider, DbSet<TEntity>> dbSetFunc;
|
||||
private readonly Func<IServiceProvider, DbSet<TEntity>> dbSetSelector;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private TEntity? current;
|
||||
@@ -27,12 +27,12 @@ internal class ScopedDbCurrent<TEntity, TMessage>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="scopeFactory">范围工厂</param>
|
||||
/// <param name="dbSetFunc">数据集</param>
|
||||
/// <param name="dbSetSelector">数据集选择器</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public ScopedDbCurrent(IServiceScopeFactory scopeFactory, Func<IServiceProvider, DbSet<TEntity>> dbSetFunc, IMessenger messenger)
|
||||
public ScopedDbCurrent(IServiceScopeFactory scopeFactory, Func<IServiceProvider, DbSet<TEntity>> dbSetSelector, IMessenger messenger)
|
||||
{
|
||||
this.scopeFactory = scopeFactory;
|
||||
this.dbSetFunc = dbSetFunc;
|
||||
this.dbSetSelector = dbSetSelector;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ internal class ScopedDbCurrent<TEntity, TMessage>
|
||||
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
DbSet<TEntity> dbSet = dbSetFunc(scope.ServiceProvider);
|
||||
DbSet<TEntity> dbSet = dbSetSelector(scope.ServiceProvider);
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
|
||||
@@ -9,18 +9,9 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// <summary>
|
||||
/// 设置帮助类
|
||||
/// </summary>
|
||||
public static class SettingEntryHelper
|
||||
[HighQuality]
|
||||
internal static class SettingEntryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// "True"
|
||||
/// </summary>
|
||||
public static readonly string TrueString = true.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// "False"
|
||||
/// </summary>
|
||||
public static readonly string FalseString = false.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或添加一个对应的设置
|
||||
/// </summary>
|
||||
@@ -35,8 +26,7 @@ public static class SettingEntryHelper
|
||||
if (entry == null)
|
||||
{
|
||||
entry = new(key, value);
|
||||
dbSet.Add(entry);
|
||||
dbSet.Context().SaveChanges();
|
||||
dbSet.AddAndSave(entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
@@ -7,8 +7,9 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
/// 指示被标注的类型可注入 HttpClient
|
||||
/// 由源生成器生成注入代码
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class HttpClientAttribute : Attribute
|
||||
internal sealed class HttpClientAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的特性
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
/// <summary>
|
||||
/// Http客户端配置
|
||||
/// </summary>
|
||||
public enum HttpClientConfigration
|
||||
[HighQuality]
|
||||
internal enum HttpClientConfigration
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认配置
|
||||
|
||||
@@ -6,9 +6,15 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
/// <summary>
|
||||
/// 配置首选Http消息处理器特性
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class PrimaryHttpMessageHandlerAttribute : Attribute
|
||||
internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
|
||||
{
|
||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.MaxConnectionsPerServer"/>
|
||||
public int MaxConnectionsPerServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.UseCookies"/>
|
||||
/// </summary>
|
||||
public bool UseCookies { get; set; }
|
||||
}
|
||||
@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
/// <summary>
|
||||
/// 注入方法
|
||||
/// </summary>
|
||||
public enum InjectAs
|
||||
[HighQuality]
|
||||
internal enum InjectAs
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示应注册为单例对象
|
||||
|
||||
@@ -7,8 +7,9 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
/// 指示被标注的类型可注入
|
||||
/// 由源生成器生成注入代码
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class InjectionAttribute : Attribute
|
||||
internal sealed class InjectionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示该类将注入为不带有接口实现的类
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 对象扩展
|
||||
/// </summary>
|
||||
public static class ObjectExtension
|
||||
[HighQuality]
|
||||
internal static class CastableServiceExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// <see langword="as"/> 的链式调用扩展
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标转换类型</typeparam>
|
||||
/// <param name="obj">对象</param>
|
||||
/// <param name="service">对象</param>
|
||||
/// <returns>转换类型后的对象</returns>
|
||||
public static T? ImplictAs<T>(this object? obj)
|
||||
public static T? ImplictAs<T>(this ICastableService service)
|
||||
where T : class
|
||||
{
|
||||
return obj as T;
|
||||
return service as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 可转换类型服务
|
||||
/// </summary>
|
||||
internal interface ICastableService
|
||||
{
|
||||
}
|
||||
@@ -3,15 +3,19 @@
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Context.FileSystem;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Ioc"/> 配置
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class IocConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
@@ -19,7 +23,7 @@ internal static class IocConfiguration
|
||||
/// </summary>
|
||||
/// <param name="services">集合</param>
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
public static IServiceCollection AddJsonSerializerOptions(this IServiceCollection services)
|
||||
public static IServiceCollection AddJsonOptions(this IServiceCollection services)
|
||||
{
|
||||
return services.AddSingleton(CoreEnvironment.JsonOptions);
|
||||
}
|
||||
@@ -31,9 +35,7 @@ internal static class IocConfiguration
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
public static IServiceCollection AddDatebase(this IServiceCollection services)
|
||||
{
|
||||
HutaoContext myDocument = new(new());
|
||||
|
||||
string dbFile = myDocument.Locate("Userdata.db");
|
||||
string dbFile = System.IO.Path.Combine(CoreEnvironment.DataFolder, "Userdata.db");
|
||||
string sqlConnectionString = $"Data Source={dbFile}";
|
||||
|
||||
// temporarily create a context
|
||||
@@ -41,11 +43,23 @@ internal static class IocConfiguration
|
||||
{
|
||||
if (context.Database.GetPendingMigrations().Any())
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.WriteLine("[Debug] Performing AppDbContext Migrations");
|
||||
#endif
|
||||
context.Database.Migrate();
|
||||
}
|
||||
|
||||
SettingEntry entry = context.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name);
|
||||
Localization.Initialize(entry.Value!);
|
||||
}
|
||||
|
||||
return services.AddDbContext<AppDbContext>(builder => builder.UseSqlite(sqlConnectionString));
|
||||
return services.AddDbContext<AppDbContext>(builder =>
|
||||
{
|
||||
builder
|
||||
#if DEBUG
|
||||
.EnableSensitiveDataLogging()
|
||||
#endif
|
||||
.UseSqlite(sqlConnectionString);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// <summary>
|
||||
/// <see cref="Ioc"/> 与 <see cref="HttpClient"/> 配置
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static partial class IocHttpClientConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// 服务管理器
|
||||
/// 依赖注入的核心管理类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static partial class ServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 服务范围扩展
|
||||
/// </summary>
|
||||
public static class ServiceScopeExtension
|
||||
{
|
||||
private static IServiceScope? scopeReference;
|
||||
|
||||
/// <summary>
|
||||
/// 追踪服务范围
|
||||
/// </summary>
|
||||
/// <param name="scope">范围</param>
|
||||
public static void Track(this IServiceScope scope)
|
||||
{
|
||||
DisposeLast();
|
||||
scopeReference = scope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放上个范围
|
||||
/// </summary>
|
||||
public static void DisposeLast()
|
||||
{
|
||||
scopeReference?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,16 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.Exception;
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 异常记录器
|
||||
/// </summary>
|
||||
internal class ExceptionRecorder
|
||||
[HighQuality]
|
||||
internal sealed class ExceptionRecorder
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
@@ -27,20 +28,27 @@ internal class ExceptionRecorder
|
||||
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
|
||||
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())
|
||||
#if RELEASE
|
||||
#pragma warning disable VSTHRD002
|
||||
Ioc.Default.GetRequiredService<Web.Hutao.HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
#pragma warning restore VSTHRD002
|
||||
#endif
|
||||
StringBuilder dataDetailBuilder = new();
|
||||
foreach (DictionaryEntry entry in e.Exception.Data)
|
||||
{
|
||||
provider.Dispose();
|
||||
string key = $"{entry.Key}";
|
||||
string value = $"{entry.Value}";
|
||||
|
||||
dataDetailBuilder.Append(key).Append(':').Append(value).Append("\r\n");
|
||||
}
|
||||
|
||||
logger.LogError(e.Exception, "未经处理的异常\r\n{detail}", dataDetailBuilder.ToString());
|
||||
}
|
||||
|
||||
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||
{
|
||||
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
|
||||
logger.LogCritical("XAML绑定失败: {message}", e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 运行环境异常
|
||||
/// 用户的计算机中的某些设置不符合要求
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class RuntimeEnvironmentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的运行环境异常
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public RuntimeEnvironmentException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 帮助更好的抛出异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[System.Diagnostics.StackTraceHidden]
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作取消
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="OperationCanceledException">操作取消异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner)
|
||||
{
|
||||
throw new OperationCanceledException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏文件操作失败
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="GameFileOperationException">文件操作失败</exception>
|
||||
public static GameFileOperationException GameFileOperation(string message, Exception inner)
|
||||
{
|
||||
throw new GameFileOperationException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 包转换错误
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="PackageConvertException">包转换错误异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static PackageConvertException PackageConvert(string message, Exception inner)
|
||||
{
|
||||
throw new PackageConvertException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据损坏
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="UserdataCorruptedException">数据损坏</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner)
|
||||
{
|
||||
throw new UserdataCorruptedException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行环境异常
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="RuntimeEnvironmentException">环境异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception inner)
|
||||
{
|
||||
throw new RuntimeEnvironmentException(message, inner);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据损坏异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class UserdataCorruptedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户数据损坏异常
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public UserdataCorruptedException(string message, Exception innerException)
|
||||
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, message), innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Snap.Hutao.Core.ExpressionService;
|
||||
/// Class to cast to type <see cref="TTo"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TTo">Target type</typeparam>
|
||||
public static class CastTo<TTo>
|
||||
[HighQuality]
|
||||
internal static class CastTo<TTo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Casts <see cref="s"/> to <see cref="TTo"/>.
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Snap.Hutao.Core.ExpressionService;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举帮助类
|
||||
/// </summary>
|
||||
public static class EnumExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断枚举是否有对应的Flag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">枚举类型</typeparam>
|
||||
/// <param name="enum">待检查的枚举</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>是否有对应的Flag</returns>
|
||||
public static bool HasOption<T>(this T @enum, T value)
|
||||
where T : struct, Enum
|
||||
{
|
||||
return ExpressionCache<T>.Entry(@enum, value);
|
||||
}
|
||||
|
||||
private static class ExpressionCache<T>
|
||||
{
|
||||
public static readonly Func<T, T, bool> Entry = Get();
|
||||
|
||||
private static Func<T, T, bool> Get()
|
||||
{
|
||||
ParameterExpression paramSource = Expression.Parameter(typeof(T));
|
||||
ParameterExpression paramValue = Expression.Parameter(typeof(T));
|
||||
|
||||
BinaryExpression logicalAnd = Expression.AndAssign(paramSource, paramValue);
|
||||
BinaryExpression equal = Expression.Equal(logicalAnd, paramValue);
|
||||
|
||||
// 生成一个源类型入,目标类型出的 lamdba
|
||||
return Expression.Lambda<Func<T, T, bool>>(equal, paramSource, paramValue).Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,19 +14,25 @@ namespace Snap.Hutao.Core.IO.Bits;
|
||||
/// <summary>
|
||||
/// BITS Job
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
internal sealed class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
{
|
||||
private const uint BitsEngineNoProgressTimeout = 120;
|
||||
private const int MaxResumeAttempts = 10;
|
||||
/// <summary>
|
||||
/// 任务名称前缀
|
||||
/// </summary>
|
||||
public const string JobNamePrefix = "SnapHutaoBitsJob";
|
||||
|
||||
private const uint Timeout = 29;
|
||||
private const int MaxResumeAttempts = 3;
|
||||
|
||||
private readonly string displayName;
|
||||
private readonly ILogger<BitsJob> log;
|
||||
private readonly object lockObj = new();
|
||||
private readonly ILogger<BitsJob> logger;
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
private IBackgroundCopyJob? nativeJob;
|
||||
private System.Exception? jobException;
|
||||
private BG_JOB_PROGRESS progress;
|
||||
private Exception? jobException;
|
||||
private BG_JOB_PROGRESS jobProgress;
|
||||
private BG_JOB_STATE state;
|
||||
private bool isJobComplete;
|
||||
private int resumeAttempts;
|
||||
@@ -35,31 +41,33 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
{
|
||||
this.displayName = displayName;
|
||||
nativeJob = job;
|
||||
log = serviceProvider.GetRequiredService<ILogger<BitsJob>>();
|
||||
logger = serviceProvider.GetRequiredService<ILogger<BitsJob>>();
|
||||
}
|
||||
|
||||
public HRESULT ErrorCode { get; private set; }
|
||||
|
||||
public static BitsJob CreateJob(IServiceProvider serviceProvider, IBackgroundCopyManager backgroundCopyManager, Uri uri, string filePath)
|
||||
{
|
||||
ILogger<BitsJob> service = serviceProvider.GetRequiredService<ILogger<BitsJob>>();
|
||||
string text = $"BitsDownloadJob - {uri}";
|
||||
IBackgroundCopyJob ppJob;
|
||||
ILogger<BitsJob> logger = serviceProvider.GetRequiredService<ILogger<BitsJob>>();
|
||||
string jobName = $"{JobNamePrefix} - {uri}";
|
||||
IBackgroundCopyJob job;
|
||||
try
|
||||
{
|
||||
backgroundCopyManager.CreateJob(text, BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out Guid _, out ppJob);
|
||||
ppJob.SetNotifyFlags(11u);
|
||||
ppJob.SetNoProgressTimeout(BitsEngineNoProgressTimeout);
|
||||
ppJob.SetPriority(BG_JOB_PRIORITY.BG_JOB_PRIORITY_FOREGROUND);
|
||||
ppJob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_AUTODETECT, null, null);
|
||||
backgroundCopyManager.CreateJob(jobName, BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out Guid _, out job);
|
||||
|
||||
// BG_NOTIFY_JOB_TRANSFERRED & BG_NOTIFY_JOB_ERROR & BG_NOTIFY_JOB_MODIFICATION
|
||||
job.SetNotifyFlags(0B1011);
|
||||
job.SetNoProgressTimeout(Timeout);
|
||||
job.SetPriority(BG_JOB_PRIORITY.BG_JOB_PRIORITY_FOREGROUND);
|
||||
job.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_AUTODETECT, default, default);
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
service.LogInformation("Failed to create job. {message}", ex.Message);
|
||||
logger.LogInformation("Failed to create job. {message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
BitsJob bitsJob = new(serviceProvider, text, ppJob);
|
||||
BitsJob bitsJob = new(serviceProvider, jobName, job);
|
||||
bitsJob.InitJob(uri.AbsoluteUri, filePath);
|
||||
return bitsJob;
|
||||
}
|
||||
@@ -72,9 +80,9 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
UpdateJobState();
|
||||
CompleteOrCancel();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogInformation("Failed to job transfer: {message}", ex.Message);
|
||||
logger.LogInformation("Failed to job transfer: {message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +91,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
IBackgroundCopyError error2 = error;
|
||||
try
|
||||
{
|
||||
log.LogInformation("Failed job: {message}", displayName);
|
||||
logger.LogInformation("Failed job: {message}", displayName);
|
||||
UpdateJobState();
|
||||
BG_ERROR_CONTEXT errorContext = BG_ERROR_CONTEXT.BG_ERROR_CONTEXT_NONE;
|
||||
HRESULT returnCode = new(0);
|
||||
@@ -92,11 +100,11 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
ErrorCode = returnCode;
|
||||
jobException = new IOException(string.Format("Error context: {0}, Error code: {1}", errorContext, returnCode));
|
||||
CompleteOrCancel();
|
||||
log.LogInformation(jobException, "Job Exception:");
|
||||
logger.LogInformation(jobException, "Job Exception:");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.LogInformation("Failed to handle job error: {message}", ex.Message);
|
||||
logger?.LogInformation("Failed to handle job error: {message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +116,9 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
if (state == BG_JOB_STATE.BG_JOB_STATE_TRANSIENT_ERROR)
|
||||
{
|
||||
HRESULT errorCode = GetErrorCode(job);
|
||||
if (errorCode == -2145844944)
|
||||
|
||||
// BG_E_HTTP_ERROR_304
|
||||
if (errorCode == 0x80190130)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
CompleteOrCancel();
|
||||
@@ -122,7 +132,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
return;
|
||||
}
|
||||
|
||||
log.LogInformation("Max resume attempts for job '{name}' exceeded. Canceling.", displayName);
|
||||
logger.LogInformation("Max resume attempts for job '{name}' exceeded. Canceling.", displayName);
|
||||
CompleteOrCancel();
|
||||
}
|
||||
else if (IsProgressingState(state))
|
||||
@@ -134,16 +144,16 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
CompleteOrCancel();
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogInformation(ex, "message");
|
||||
logger.LogInformation(ex, "message");
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
log.LogInformation("Canceling job {name}", displayName);
|
||||
lock (lockObj)
|
||||
logger.LogInformation("Canceling job {name}", displayName);
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (!isJobComplete)
|
||||
{
|
||||
@@ -154,16 +164,18 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
}
|
||||
}
|
||||
|
||||
public void WaitForCompletion(Action<ProgressUpdateStatus> callback, CancellationToken cancellationToken)
|
||||
public void WaitForCompletion(IProgress<ProgressUpdateStatus> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.Register(Cancel);
|
||||
int noProgressSeconds = 0;
|
||||
int noTransferSeconds = 0;
|
||||
ulong previousTransferred = 0;
|
||||
try
|
||||
{
|
||||
UpdateJobState();
|
||||
while (IsProgressingState(state) || state == BG_JOB_STATE.BG_JOB_STATE_QUEUED)
|
||||
{
|
||||
if (noProgressSeconds > BitsEngineNoProgressTimeout)
|
||||
if (noProgressSeconds > Timeout || noTransferSeconds > Timeout)
|
||||
{
|
||||
jobException = new TimeoutException($"Timeout reached for job {displayName} whilst in state {state}");
|
||||
break;
|
||||
@@ -176,7 +188,18 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
if (state is BG_JOB_STATE.BG_JOB_STATE_TRANSFERRING or BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED or BG_JOB_STATE.BG_JOB_STATE_ACKNOWLEDGED)
|
||||
{
|
||||
noProgressSeconds = 0;
|
||||
callback(new ProgressUpdateStatus((long)progress.BytesTransferred, (long)progress.BytesTotal));
|
||||
if (jobProgress.BytesTransferred > previousTransferred)
|
||||
{
|
||||
previousTransferred = jobProgress.BytesTransferred;
|
||||
noTransferSeconds = 0;
|
||||
logger.LogInformation("{job}: {read} / {total}", displayName, jobProgress.BytesTransferred, jobProgress.BytesTotal);
|
||||
progress.Report(new ProgressUpdateStatus((long)jobProgress.BytesTransferred, (long)jobProgress.BytesTotal));
|
||||
}
|
||||
else
|
||||
{
|
||||
++noTransferSeconds;
|
||||
logger.LogInformation("{job} no transfer for {x} seconds", displayName, noTransferSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh every seconds.
|
||||
@@ -222,7 +245,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
return;
|
||||
}
|
||||
|
||||
lock (lockObj)
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (isJobComplete)
|
||||
{
|
||||
@@ -231,7 +254,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
|
||||
if (state == BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED)
|
||||
{
|
||||
log.LogInformation("Completing job '{name}'.", displayName);
|
||||
logger.LogInformation("Completing job '{name}'.", displayName);
|
||||
Invoke(() => nativeJob?.Complete(), "Bits Complete");
|
||||
while (state == BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED)
|
||||
{
|
||||
@@ -241,7 +264,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogInformation("Canceling job '{name}'.", displayName);
|
||||
logger.LogInformation("Canceling job '{name}'.", displayName);
|
||||
Invoke(() => nativeJob?.Cancel(), "Bits Cancel");
|
||||
}
|
||||
|
||||
@@ -261,7 +284,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
{
|
||||
if (!isJobComplete)
|
||||
{
|
||||
Invoke(() => nativeJob?.GetProgress(out progress), "GetProgress");
|
||||
Invoke(() => nativeJob?.GetProgress(out jobProgress), "GetProgress");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +299,9 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogInformation("{name} failed. {exception}", displayName, ex);
|
||||
logger.LogInformation("{name} failed. {exception}", displayName, ex);
|
||||
if (throwOnFailure)
|
||||
{
|
||||
throw;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user