mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
139 Commits
feat/notif
...
fix/layout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aa9557526 | ||
|
|
55706e68f0 | ||
|
|
63c4bb6a23 | ||
|
|
baa4b88622 | ||
|
|
de2ce0db63 | ||
|
|
6cbf8ca918 | ||
|
|
df125904a6 | ||
|
|
1bad9d0928 | ||
|
|
a762166db4 | ||
|
|
f432e5ba42 | ||
|
|
46dd4db25c | ||
|
|
2e685182e6 | ||
|
|
4f1bbd2e89 | ||
|
|
0f5b6dda16 | ||
|
|
4c38bb528f | ||
|
|
44e7f7482c | ||
|
|
8a2fa3c701 | ||
|
|
6e10819609 | ||
|
|
c1b838bd97 | ||
|
|
268c4c6c71 | ||
|
|
59dabaa5be | ||
|
|
dcac7ac792 | ||
|
|
2ad6dad282 | ||
|
|
4185336556 | ||
|
|
15a627e50a | ||
|
|
2ea53fd39d | ||
|
|
6de3cba550 | ||
|
|
03c1bacfe9 | ||
|
|
d157476a9a | ||
|
|
e35c03ac90 | ||
|
|
9619835cc2 | ||
|
|
6936a30a74 | ||
|
|
506d198167 | ||
|
|
9c2131cb9d | ||
|
|
896e3d49f2 | ||
|
|
7da24790c4 | ||
|
|
ebf163f200 | ||
|
|
26043a6a73 | ||
|
|
ad67001556 | ||
|
|
428cd35a7f | ||
|
|
1cf3450264 | ||
|
|
1849ac16da | ||
|
|
4e9d87af50 | ||
|
|
eb125e547f | ||
|
|
c17798a8c9 | ||
|
|
fa9b39d134 | ||
|
|
8dc85c7a25 | ||
|
|
5845c718e1 | ||
|
|
0c093851ed | ||
|
|
04c187aa16 | ||
|
|
25cf9f5356 | ||
|
|
c30c8b114a | ||
|
|
41f5a04e5a | ||
|
|
4f26d6efc2 | ||
|
|
0d6b5e595e | ||
|
|
e30bebd1a3 | ||
|
|
ffb34c0e93 | ||
|
|
35b35ab649 | ||
|
|
f067350cd8 | ||
|
|
14648dafe8 | ||
|
|
7a7d81cfee | ||
|
|
30c055e7ba | ||
|
|
6a922d9db6 | ||
|
|
02a6e64a8c | ||
|
|
51c4e66472 | ||
|
|
48875195bf | ||
|
|
a179e0e838 | ||
|
|
00c3e94e97 | ||
|
|
eec7224c07 | ||
|
|
85bcf37b1b | ||
|
|
465c7035c0 | ||
|
|
11620816ec | ||
|
|
c5b75d6f82 | ||
|
|
6241bbc997 | ||
|
|
afbd8ec6ad | ||
|
|
288cc841ac | ||
|
|
a9295c0a37 | ||
|
|
29cd690032 | ||
|
|
78d8539ae2 | ||
|
|
7dec87586b | ||
|
|
d169f355f3 | ||
|
|
e1784e2078 | ||
|
|
32b1b698df | ||
|
|
d6c7df1593 | ||
|
|
904fdf7fc9 | ||
|
|
b3e4ebb5d3 | ||
|
|
09b9af4575 | ||
|
|
8930548f44 | ||
|
|
34dbcc6f5d | ||
|
|
75c25cec53 | ||
|
|
22251ca937 | ||
|
|
57c9531db8 | ||
|
|
fd73743159 | ||
|
|
6594d9032d | ||
|
|
ae1b452697 | ||
|
|
bc3df782e4 | ||
|
|
3ef117e683 | ||
|
|
8b1613d46f | ||
|
|
c2708aaedd | ||
|
|
db3de01eec | ||
|
|
635b52980b | ||
|
|
4988c1bd47 | ||
|
|
fd499e372c | ||
|
|
a6c376cd44 | ||
|
|
425b3789bf | ||
|
|
a6986bc201 | ||
|
|
301a14611a | ||
|
|
59db7d968a | ||
|
|
45f4b46e9e | ||
|
|
82b5d9b12a | ||
|
|
6fb5cfe25d | ||
|
|
1df78345c5 | ||
|
|
fd817af9c3 | ||
|
|
3f6efe2e24 | ||
|
|
ef6d88ba2e | ||
|
|
26748e5885 | ||
|
|
5f180846f6 | ||
|
|
57ea6bb34e | ||
|
|
95024e4107 | ||
|
|
678ec191a6 | ||
|
|
40b26d9d3c | ||
|
|
e5677e7de4 | ||
|
|
9ae8b182e7 | ||
|
|
02fe498cce | ||
|
|
f924f78ee2 | ||
|
|
977e749939 | ||
|
|
f8ad3b98fc | ||
|
|
ee39f7fec5 | ||
|
|
c8c1d21c9b | ||
|
|
72395fdb89 | ||
|
|
31670953b0 | ||
|
|
ae2415dbca | ||
|
|
5b109013a0 | ||
|
|
279e107919 | ||
|
|
94a5e71130 | ||
|
|
07cdfcea28 | ||
|
|
a93eb505d6 | ||
|
|
aa46b6531b | ||
|
|
b36399f572 |
17
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
17
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
@@ -18,7 +18,7 @@ body:
|
||||
options:
|
||||
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
|
||||
required: true
|
||||
|
||||
|
||||
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
||||
required: true
|
||||
|
||||
@@ -29,17 +29,17 @@ body:
|
||||
id: winver
|
||||
attributes:
|
||||
label: Windows 版本
|
||||
description: |
|
||||
description: |
|
||||
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
|
||||
placeholder: 例:22000.556
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: input
|
||||
id: shver
|
||||
attributes:
|
||||
label: Snap Hutao 版本
|
||||
description: 在应用标题,应用程序的设置界面中靠下的位置可以找到
|
||||
description: 在应用标题,应用程序的反馈中心界面中可以找到
|
||||
placeholder: 例:1.4.15.0
|
||||
validations:
|
||||
required: true
|
||||
@@ -48,10 +48,10 @@ body:
|
||||
id: deviceid
|
||||
attributes:
|
||||
label: 设备 ID
|
||||
description: |
|
||||
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
|
||||
description: |
|
||||
在胡桃工具箱的反馈中心界面,你可以找到并复制你的设备 ID
|
||||
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
|
||||
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
|
||||
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe),它将显示你的设备 ID
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -79,7 +79,7 @@ body:
|
||||
- 公告
|
||||
- 其它
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
@@ -107,4 +107,3 @@ body:
|
||||
options:
|
||||
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
|
||||
required: true
|
||||
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
17
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
@@ -18,7 +18,7 @@ body:
|
||||
options:
|
||||
- label: I have read [FAQ page](https://hut.ao/advanced/FAQ.html) and [Exception page](https://hut.ao/advanced/exceptions.html) in Snap Hutao document, and my issue is not answered
|
||||
required: true
|
||||
|
||||
|
||||
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
|
||||
required: true
|
||||
|
||||
@@ -29,12 +29,12 @@ body:
|
||||
id: winver
|
||||
attributes:
|
||||
label: Windows Version
|
||||
description: |
|
||||
description: |
|
||||
Use `Win+R` and input `winver`, Windows build version is usually at the second line
|
||||
placeholder: e.g. 22000.556
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: input
|
||||
id: shver
|
||||
attributes:
|
||||
@@ -48,10 +48,10 @@ body:
|
||||
id: deviceid
|
||||
attributes:
|
||||
label: Device ID
|
||||
description: |
|
||||
In Snap Hutao's settings page, you can find and copy your device ID
|
||||
description: |
|
||||
In Snap Hutao's Feedback Center, you can find and copy your device ID
|
||||
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
|
||||
If your program cannot startup, please download and run [Diagnosis Tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
|
||||
If your program cannot startup, please download and run [Diagnostic Tooling](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe), it will shows your device ID.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -74,12 +74,12 @@ body:
|
||||
- User Interface
|
||||
- Snap Hutao Cloud
|
||||
- Snap Hutao Account
|
||||
- Checkin
|
||||
- Checkin
|
||||
- Wiki
|
||||
- Announcement
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
@@ -107,4 +107,3 @@ body:
|
||||
options:
|
||||
- label: I believe the description above is detail enough to allow developers to reproduce the issue
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -13,4 +13,8 @@ updates:
|
||||
groups:
|
||||
packages:
|
||||
patterns:
|
||||
- "*"
|
||||
- "*"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/workflows" # GitHub Workflows
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
23
.github/workflows/alpha.yml
vendored
23
.github/workflows/alpha.yml
vendored
@@ -13,16 +13,28 @@ on:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
paths-ignore:
|
||||
- '.gitattributes'
|
||||
- '.github/**'
|
||||
- '.gitignore'
|
||||
- '.gitmodules'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**.yml'
|
||||
- '**.resx'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4.0.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0
|
||||
|
||||
@@ -34,20 +46,21 @@ jobs:
|
||||
VERSION_API_TOKEN: ${{ secrets.VERSION_API_TOKEN }}
|
||||
|
||||
- name: Sign Msix
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
shell: pwsh
|
||||
run: |
|
||||
[System.Convert]::FromBase64String("${{ secrets.CERTIFICATE }}") | Set-Content -AsByteStream temp.pfx
|
||||
signtool.exe sign /debug /v /a /fd SHA256 /f temp.pfx /p ${{ secrets.PW }} ${{ github.workspace }}\src\output\Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
|
||||
|
||||
- name: Upload signed msix
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v3
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}
|
||||
path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
|
||||
|
||||
- name: Add summary
|
||||
if: success()
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$summary = "
|
||||
|
||||
16
.github/workflows/close_stale.yml
vendored
Normal file
16
.github/workflows/close_stale.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
any-of-labels: 'needs-more-info,需要更多信息'
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 3 days.'
|
||||
days-before-stale: 7
|
||||
days-before-close: 3
|
||||
close-issue-reason: not_planned
|
||||
20
.github/workflows/issue_similarity.yml
vendored
Normal file
20
.github/workflows/issue_similarity.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Issues Similarity Analysis
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
similarity-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: analysis
|
||||
uses: actions-cool/issues-similarity-analysis@v1
|
||||
with:
|
||||
filter-threshold: 0.5
|
||||
comment-title: '### Probable Similar Topics'
|
||||
title-excludes: '[Publish]:,[Bug]:,[Feat]:,[Network]:,[ENG]'
|
||||
comment-body: '${index}. ${similarity} #${number}'
|
||||
show-footer: false
|
||||
show-mentioned: true
|
||||
since-days: 365
|
||||
26
.github/workflows/lock_closed_issues.yml
vendored
Normal file
26
.github/workflows/lock_closed_issues.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: '30'
|
||||
issue-comment: 'This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related topic.'
|
||||
issue-lock-reason: 'resolved'
|
||||
process-only: 'issues'
|
||||
log-output: false
|
||||
@@ -74,3 +74,4 @@ Refresh:
|
||||
script:
|
||||
- apt-get install -y curl
|
||||
- curl -X PATCH "$PURGE_URL"
|
||||
- curl -X POST -o /dev/null "$UPLOAD_OSS_URL"
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# CI process script for Snap.Hutao
|
||||
# Usage:
|
||||
# 1. Append the script in Pipelines
|
||||
# 2. Upload the pfx and cer certificates to Pipelines Library secrets
|
||||
# 3. Permit the pfx usage
|
||||
# 4. Add a `pw` variable in the script variables, which is pfx password
|
||||
# 5. Connect the GitHub in project settings
|
||||
# 6. Run
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
# trigger:
|
||||
# branches:
|
||||
# include:
|
||||
# - main
|
||||
# - develop
|
||||
# paths:
|
||||
# exclude:
|
||||
# - README.md
|
||||
# - azure-pipelines.yml
|
||||
# - .github/ISSUE_TEMPLATE/*.yml
|
||||
# - .github/workflows/*.yml
|
||||
# - src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
|
||||
# pr:
|
||||
# branches:
|
||||
# include:
|
||||
# - main
|
||||
# paths:
|
||||
# exclude:
|
||||
# - README.md
|
||||
# - azure-pipelines.yml
|
||||
# - .github/ISSUE_TEMPLATE/*.yml
|
||||
# - .github/workflows/*.yml
|
||||
# - src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
|
||||
|
||||
|
||||
pool:
|
||||
name: Default
|
||||
demands: agent.name -equals Hutao-Server
|
||||
|
||||
variables:
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
solution: '$(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao.sln'
|
||||
project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
|
||||
buildPlatform: 'x64'
|
||||
buildConfiguration: 'Release'
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: Install dotNet
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '8.x'
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: dotnet cake
|
||||
inputs:
|
||||
script: dotnet tool restore && dotnet cake
|
||||
|
||||
- task: MsixSigning@1
|
||||
name: signMsix
|
||||
displayName: Sign MSIX package
|
||||
inputs:
|
||||
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(version).msix'
|
||||
certificate: 'DGP_Studio_CI.pfx'
|
||||
passwordVariable: 'pw'
|
||||
condition: succeeded()
|
||||
|
||||
- task: DownloadSecureFile@1
|
||||
name: cerFile
|
||||
displayName: Download Root CA
|
||||
inputs:
|
||||
secureFile: 'Snap.Hutao.CI.cer'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
artifact: 'Snap.Hutao.Alpha-$(version).msix'
|
||||
publishLocation: 'pipeline'
|
||||
|
||||
#- task: GitHubRelease@1
|
||||
# inputs:
|
||||
# gitHubConnection: 'github.com_Masterain'
|
||||
# repositoryName: 'DGP-Automation/Hutao-Auto-Release'
|
||||
# action: 'create'
|
||||
# target: '$(Build.SourceVersion)'
|
||||
# tagSource: 'userSpecifiedTag'
|
||||
# tag: '$(version)'
|
||||
# title: '$(version)'
|
||||
# releaseNotesSource: 'inline'
|
||||
# releaseNotesInline: |
|
||||
# ## 普通用户请勿下载
|
||||
# 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
|
||||
#
|
||||
# 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
|
||||
#
|
||||
# assets: |
|
||||
# $(Build.ArtifactStagingDirectory)/*
|
||||
# $(cerFile.secureFilePath)
|
||||
# isPreRelease: true
|
||||
# changeLogCompareToRelease: 'lastFullRelease'
|
||||
# changeLogType: 'commitBased'
|
||||
|
||||
|
||||
- task: rclone@1
|
||||
displayName: Upload CI via Rclone
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
||||
inputs:
|
||||
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(version).msix downloadDGPCN:/releases/Alpha/'
|
||||
configPath: 'C:\agent\_work\_tasks\rclone.conf'
|
||||
|
||||
- task: rclone@1
|
||||
displayName: Upload PR CI via Rclone
|
||||
condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(version).msix downloadDGPCN:/releases/PR/'
|
||||
configPath: 'C:\agent\_work\_tasks\rclone.conf'
|
||||
62
build.cake
62
build.cake
@@ -1,5 +1,5 @@
|
||||
#tool "nuget:?package=nuget.commandline&version=6.5.0"
|
||||
#addin nuget:?package=Cake.Http&version=3.0.2
|
||||
#tool "nuget:?package=nuget.commandline&version=6.9.1"
|
||||
#addin nuget:?package=Cake.Http&version=4.0.0
|
||||
|
||||
var target = Argument("target", "Build");
|
||||
var configuration = Argument("configuration", "Release");
|
||||
@@ -28,43 +28,33 @@ string manifest
|
||||
get => System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao", "Package.appxmanifest");
|
||||
}
|
||||
|
||||
if (AzurePipelines.IsRunningOnAzurePipelines)
|
||||
{
|
||||
repoDir = AzurePipelines.Environment.Build.SourcesDirectory.FullPath;
|
||||
outputPath = AzurePipelines.Environment.Build.ArtifactStagingDirectory.FullPath;
|
||||
|
||||
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
|
||||
version = HttpGet(
|
||||
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
|
||||
new HttpSettings
|
||||
{
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
{ "Authorization", versionAuth }
|
||||
}
|
||||
}
|
||||
);
|
||||
Information($"Version: {version}");
|
||||
|
||||
AzurePipelines.Commands.SetVariable("version", version);
|
||||
}
|
||||
else if (GitHubActions.IsRunningOnGitHubActions)
|
||||
if (GitHubActions.IsRunningOnGitHubActions)
|
||||
{
|
||||
repoDir = GitHubActions.Environment.Workflow.Workspace.FullPath;
|
||||
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
|
||||
|
||||
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
|
||||
version = HttpGet(
|
||||
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
|
||||
new HttpSettings
|
||||
{
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
if (GitHubActions.Environment.PullRequest.IsPullRequest)
|
||||
{
|
||||
version = System.DateTime.Now.ToString("yyyy.M.d.0");
|
||||
|
||||
Information("Is Pull Request. Skip version.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
|
||||
version = HttpGet(
|
||||
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
|
||||
new HttpSettings
|
||||
{
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
{ "Authorization", versionAuth }
|
||||
}
|
||||
}
|
||||
);
|
||||
Information($"Version: {version}");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Information($"Version: {version}");
|
||||
}
|
||||
|
||||
GitHubActions.Commands.SetOutputParameter("version", version);
|
||||
}
|
||||
@@ -106,7 +96,7 @@ Task("Generate AppxManifest")
|
||||
|
||||
var content = System.IO.File.ReadAllText(manifest);
|
||||
|
||||
if (AzurePipelines.IsRunningOnAzurePipelines || GitHubActions.IsRunningOnGitHubActions)
|
||||
if (GitHubActions.IsRunningOnGitHubActions)
|
||||
{
|
||||
Information("Using CI configuraion");
|
||||
content = content
|
||||
@@ -175,7 +165,7 @@ Task("Build MSIX")
|
||||
.Does(() =>
|
||||
{
|
||||
var arguments = "arguments";
|
||||
if (AzurePipelines.IsRunningOnAzurePipelines || GitHubActions.IsRunningOnGitHubActions)
|
||||
if (GitHubActions.IsRunningOnGitHubActions)
|
||||
{
|
||||
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao.Alpha-{version}.msix");
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources/>
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.TokenizingTextBox/TokenizingTextBox.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
|
||||
@@ -22,7 +23,9 @@
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SegmentedOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Thickness.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
|
||||
|
||||
@@ -23,10 +23,12 @@ internal static class ControlAnimationConstants
|
||||
/// <summary>
|
||||
/// 图像淡入
|
||||
/// </summary>
|
||||
public static readonly TimeSpan ImageFadeIn = TimeSpan.FromSeconds(0.3);
|
||||
public static readonly TimeSpan ImageScaleFadeIn = TimeSpan.FromSeconds(0.3);
|
||||
|
||||
/// <summary>
|
||||
/// 图像淡出
|
||||
/// </summary>
|
||||
public static readonly TimeSpan ImageFadeOut = TimeSpan.FromSeconds(0.2);
|
||||
public static readonly TimeSpan ImageScaleFadeOut = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
public static readonly TimeSpan ImageOpacityFadeInOut = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.AutoSuggestBox;
|
||||
|
||||
[DependencyProperty("FilterCommand", typeof(ICommand))]
|
||||
[DependencyProperty("FilterCommandParameter", typeof(object))]
|
||||
[DependencyProperty("AvailableTokens", typeof(IReadOnlyDictionary<string, SearchToken>))]
|
||||
internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
|
||||
{
|
||||
public AutoSuggestTokenBox()
|
||||
{
|
||||
DefaultStyleKey = typeof(TokenizingTextBox);
|
||||
TextChanged += OnFilterSuggestionRequested;
|
||||
QuerySubmitted += OnQuerySubmitted;
|
||||
TokenItemAdding += OnTokenItemAdding;
|
||||
TokenItemAdded += OnTokenItemModified;
|
||||
TokenItemRemoved += OnTokenItemModified;
|
||||
}
|
||||
|
||||
private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// TODO: CornerRadius
|
||||
// Popup? popup = this.FindDescendant("SuggestionsPopup") as Popup;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnQuerySubmitted(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if (args.ChosenSuggestion is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter);
|
||||
}
|
||||
|
||||
private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(args.TokenText))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
args.Item = AvailableTokens.GetValueOrDefault(args.TokenText) ?? new SearchToken(SearchTokenKind.None, args.TokenText);
|
||||
}
|
||||
|
||||
private void OnTokenItemModified(TokenizingTextBox sender, object args)
|
||||
{
|
||||
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.AutoSuggestBox;
|
||||
|
||||
internal sealed class SearchToken
|
||||
{
|
||||
public SearchToken(SearchTokenKind kind, string value, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null)
|
||||
{
|
||||
Value = value;
|
||||
Kind = kind;
|
||||
IconUri = iconUri;
|
||||
SideIconUri = sideIconUri;
|
||||
Quality = quality;
|
||||
}
|
||||
|
||||
public SearchTokenKind Kind { get; }
|
||||
|
||||
public string Value { get; set; } = default!;
|
||||
|
||||
public Uri? IconUri { get; }
|
||||
|
||||
public Uri? SideIconUri { get; }
|
||||
|
||||
public Color? Quality { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.AutoSuggestBox;
|
||||
|
||||
internal enum SearchTokenKind
|
||||
{
|
||||
None,
|
||||
AssociationType,
|
||||
Avatar,
|
||||
BodyType,
|
||||
ElementName,
|
||||
FightProperty,
|
||||
ItemQuality,
|
||||
Weapon,
|
||||
WeaponType,
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
@@ -45,10 +46,6 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
|
||||
return;
|
||||
}
|
||||
|
||||
if (Command is not null && Command.CanExecute(CommandParameter))
|
||||
{
|
||||
Command.Execute(CommandParameter);
|
||||
executed = true;
|
||||
}
|
||||
executed = Command.TryExecute(CommandParameter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
[DependencyProperty("Period", typeof(TimeSpan))]
|
||||
[DependencyProperty("Command", typeof(ICommand))]
|
||||
[DependencyProperty("CommandParameter", typeof(object))]
|
||||
internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavior : BehaviorBase<FrameworkElement>, IDisposable
|
||||
{
|
||||
private TaskCompletionSource acutalThemeChangedTaskCompletionSource = new();
|
||||
private CancellationTokenSource periodicTimerCancellationTokenSource = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
periodicTimerCancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
protected override bool Initialize()
|
||||
{
|
||||
AssociatedObject.ActualThemeChanged += OnActualThemeChanged;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
RunCoreAsync().SafeForget();
|
||||
}
|
||||
|
||||
protected override bool Uninitialize()
|
||||
{
|
||||
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
{
|
||||
acutalThemeChangedTaskCompletionSource.TrySetResult();
|
||||
periodicTimerCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
private void TryExecuteCommand()
|
||||
{
|
||||
if (AssociatedObject is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Command.TryExecute(CommandParameter);
|
||||
}
|
||||
|
||||
private async ValueTask RunCoreAsync()
|
||||
{
|
||||
using (PeriodicTimer timer = new(Period))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (!IsAttached)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ITaskContext taskContext = Ioc.Default.GetRequiredService<ITaskContext>();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
TryExecuteCommand();
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
try
|
||||
{
|
||||
Task nextTickTask = timer.WaitForNextTickAsync(periodicTimerCancellationTokenSource.Token).AsTask();
|
||||
await Task.WhenAny(nextTickTask, acutalThemeChangedTaskCompletionSource.Task).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
|
||||
acutalThemeChangedTaskCompletionSource = new();
|
||||
periodicTimerCancellationTokenSource = new();
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
internal sealed class ColorSegmentCollection : List<IColorSegment>
|
||||
{
|
||||
}
|
||||
@@ -9,5 +9,5 @@ internal interface IColorSegment
|
||||
{
|
||||
Color Color { get; }
|
||||
|
||||
double Value { get; }
|
||||
double Value { get; set; }
|
||||
}
|
||||
@@ -9,36 +9,48 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
[DependencyProperty("Source", typeof(List<IColorSegment>), default!, nameof(OnSourceChanged))]
|
||||
[DependencyProperty("Source", typeof(ColorSegmentCollection), default!, nameof(OnSourceChanged))]
|
||||
internal sealed partial class SegmentedBar : ContentControl
|
||||
{
|
||||
private readonly LinearGradientBrush brush = new() { StartPoint = new(0, 0), EndPoint = new(1, 0), };
|
||||
|
||||
public SegmentedBar()
|
||||
{
|
||||
HorizontalContentAlignment = HorizontalAlignment.Stretch;
|
||||
VerticalContentAlignment = VerticalAlignment.Stretch;
|
||||
|
||||
Content = new Rectangle()
|
||||
{
|
||||
Fill = brush,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
};
|
||||
}
|
||||
|
||||
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
SegmentedBar segmentedBar = (SegmentedBar)obj;
|
||||
UpdateLinearGradientBrush((SegmentedBar)obj);
|
||||
}
|
||||
|
||||
private static void UpdateLinearGradientBrush(SegmentedBar segmentedBar)
|
||||
{
|
||||
GradientStopCollection collection = segmentedBar.brush.GradientStops;
|
||||
collection.Clear();
|
||||
|
||||
if (args.NewValue as List<IColorSegment> is [_, ..] list)
|
||||
ColorSegmentCollection segmentCollection = segmentedBar.Source;
|
||||
|
||||
double total = segmentCollection.Sum(seg => seg.Value);
|
||||
if (total is 0D)
|
||||
{
|
||||
double total = list.Sum(seg => seg.Value);
|
||||
double offset = 0;
|
||||
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(list))
|
||||
{
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
offset += segment.Value / total;
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
double offset = 0;
|
||||
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(segmentCollection))
|
||||
{
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
offset += segment.Value / total;
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using NotifyCollectionChangedAction = System.Collections.Specialized.NotifyCollectionChangedAction;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<object>
|
||||
where T : class
|
||||
{
|
||||
private readonly List<T> view;
|
||||
private readonly ObservableCollection<SortDescription> sortDescriptions;
|
||||
private readonly Dictionary<string, PropertyInfo?> sortProperties;
|
||||
private readonly bool liveShapingEnabled;
|
||||
private readonly HashSet<string?> observedFilterProperties = [];
|
||||
|
||||
private IList<T> source;
|
||||
private Predicate<T>? filter;
|
||||
private int deferCounter;
|
||||
private WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>? sourceWeakEventListener;
|
||||
|
||||
public AdvancedCollectionView()
|
||||
: this(new List<T>(0))
|
||||
{
|
||||
}
|
||||
|
||||
public AdvancedCollectionView(IList<T> source, bool isLiveShaping = false)
|
||||
{
|
||||
liveShapingEnabled = isLiveShaping;
|
||||
view = [];
|
||||
sortDescriptions = [];
|
||||
sortDescriptions.CollectionChanged += SortDescriptionsCollectionChanged;
|
||||
sortProperties = [];
|
||||
Source = source;
|
||||
}
|
||||
|
||||
public event EventHandler<object>? CurrentChanged;
|
||||
|
||||
public event CurrentChangingEventHandler? CurrentChanging;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public event VectorChangedEventHandler<object>? VectorChanged;
|
||||
|
||||
public IList<T> Source
|
||||
{
|
||||
get => source;
|
||||
|
||||
[MemberNotNull(nameof(source))]
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(source, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is not null)
|
||||
{
|
||||
DetachPropertyChangedHandler(source);
|
||||
}
|
||||
|
||||
source = value;
|
||||
AttachPropertyChangedHandler(source);
|
||||
|
||||
sourceWeakEventListener?.Detach();
|
||||
|
||||
if (source is INotifyCollectionChanged sourceNotifyCollectionChanged)
|
||||
{
|
||||
sourceWeakEventListener = new WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>(this)
|
||||
{
|
||||
// Call the actual collection changed event
|
||||
OnEventAction = (source, changed, arg3) => SourceNotifyCollectionChangedCollectionChanged(source, arg3),
|
||||
|
||||
// The source doesn't exist anymore
|
||||
OnDetachAction = (listener) =>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sourceWeakEventListener);
|
||||
sourceNotifyCollectionChanged.CollectionChanged -= sourceWeakEventListener.OnEvent;
|
||||
},
|
||||
};
|
||||
sourceNotifyCollectionChanged.CollectionChanged += sourceWeakEventListener.OnEvent;
|
||||
}
|
||||
|
||||
HandleSourceChanged();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => view.Count;
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => source is null || source.IsReadOnly;
|
||||
}
|
||||
|
||||
public IObservableVector<object> CollectionGroups
|
||||
{
|
||||
get => default!;
|
||||
}
|
||||
|
||||
public T? CurrentItem
|
||||
{
|
||||
get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : default;
|
||||
set => MoveCurrentTo(value);
|
||||
}
|
||||
|
||||
public int CurrentPosition { get; private set; }
|
||||
|
||||
public bool HasMoreItems
|
||||
{
|
||||
get => source is ISupportIncrementalLoading { HasMoreItems: true };
|
||||
}
|
||||
|
||||
public bool IsCurrentAfterLast
|
||||
{
|
||||
get => CurrentPosition >= view.Count;
|
||||
}
|
||||
|
||||
public bool IsCurrentBeforeFirst
|
||||
{
|
||||
get => CurrentPosition < 0;
|
||||
}
|
||||
|
||||
public bool CanFilter
|
||||
{
|
||||
get => true;
|
||||
}
|
||||
|
||||
public Predicate<T>? Filter
|
||||
{
|
||||
get => filter;
|
||||
set
|
||||
{
|
||||
if (filter == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
filter = value;
|
||||
HandleFilterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanSort
|
||||
{
|
||||
get => true;
|
||||
}
|
||||
|
||||
public IList<SortDescription> SortDescriptions
|
||||
{
|
||||
get => sortDescriptions;
|
||||
}
|
||||
|
||||
public IEnumerable<T> SourceCollection
|
||||
{
|
||||
get => source;
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> View
|
||||
{
|
||||
get => view;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => view[index];
|
||||
set => view[index] = value;
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
|
||||
public void RefreshFilter()
|
||||
{
|
||||
HandleFilterChanged();
|
||||
}
|
||||
|
||||
public void RefreshSorting()
|
||||
{
|
||||
HandleSortChanged();
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return view.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return view.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
source.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return view.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
view.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
source.Remove(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
public int IndexOf(T? item)
|
||||
{
|
||||
return view.IndexOf(item!);
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
source.Insert(index, item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Remove(view[index]);
|
||||
}
|
||||
|
||||
public bool MoveCurrentTo(T? item)
|
||||
{
|
||||
return (item is not null && item.Equals(CurrentItem)) || MoveCurrentToIndex(IndexOf(item));
|
||||
}
|
||||
|
||||
public bool MoveCurrentToPosition(int index)
|
||||
{
|
||||
return MoveCurrentToIndex(index);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToFirst()
|
||||
{
|
||||
return MoveCurrentToIndex(0);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToLast()
|
||||
{
|
||||
return MoveCurrentToIndex(view.Count - 1);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToNext()
|
||||
{
|
||||
return MoveCurrentToIndex(CurrentPosition + 1);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToPrevious()
|
||||
{
|
||||
return MoveCurrentToIndex(CurrentPosition - 1);
|
||||
}
|
||||
|
||||
public IAsyncOperation<LoadMoreItemsResult>? LoadMoreItemsAsync(uint count)
|
||||
{
|
||||
return (source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count);
|
||||
}
|
||||
|
||||
public void ObserveFilterProperty(string propertyName)
|
||||
{
|
||||
observedFilterProperties.Add(propertyName);
|
||||
}
|
||||
|
||||
public void ClearObservedFilterProperties()
|
||||
{
|
||||
observedFilterProperties.Clear();
|
||||
}
|
||||
|
||||
public IDisposable DeferRefresh()
|
||||
{
|
||||
return new NotificationDeferrer(this);
|
||||
}
|
||||
|
||||
int IComparer<object>.Compare(object? x, object? y)
|
||||
{
|
||||
if (sortProperties.Count <= 0)
|
||||
{
|
||||
Type listType = source.GetType();
|
||||
Type? type;
|
||||
|
||||
if (listType.IsGenericType)
|
||||
{
|
||||
type = listType.GetGenericArguments()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
type = x?.GetType();
|
||||
}
|
||||
|
||||
foreach (SortDescription sd in sortDescriptions)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sd.PropertyName))
|
||||
{
|
||||
sortProperties[sd.PropertyName] = type?.GetProperty(sd.PropertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SortDescription sd in sortDescriptions)
|
||||
{
|
||||
object? cx, cy;
|
||||
|
||||
if (string.IsNullOrEmpty(sd.PropertyName))
|
||||
{
|
||||
cx = x;
|
||||
cy = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyInfo? pi = sortProperties[sd.PropertyName];
|
||||
|
||||
cx = pi?.GetValue(x);
|
||||
cy = pi?.GetValue(y);
|
||||
}
|
||||
|
||||
int cmp = sd.Comparer.Compare(cx, cy);
|
||||
|
||||
if (cmp is not 0)
|
||||
{
|
||||
return sd.Direction is SortDirection.Ascending ? +cmp : -cmp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal void OnPropertyChanged([CallerMemberName] string propertyName = default!)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private void ItemOnPropertyChanged(object? item, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (!liveShapingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
T typedItem = (T)item;
|
||||
|
||||
bool? filterResult = filter?.Invoke(typedItem);
|
||||
|
||||
if (filterResult.HasValue && observedFilterProperties.Contains(e.PropertyName))
|
||||
{
|
||||
int viewIndex = view.IndexOf(typedItem);
|
||||
if (viewIndex != -1 && !filterResult.Value)
|
||||
{
|
||||
RemoveFromView(viewIndex, typedItem);
|
||||
}
|
||||
else if (viewIndex == -1 && filterResult.Value)
|
||||
{
|
||||
int index = source.IndexOf(typedItem);
|
||||
HandleItemAdded(index, typedItem);
|
||||
}
|
||||
}
|
||||
|
||||
if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName))
|
||||
{
|
||||
int oldIndex = view.IndexOf(typedItem);
|
||||
|
||||
// Check if item is in view:
|
||||
if (oldIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
view.RemoveAt(oldIndex);
|
||||
int targetIndex = view.BinarySearch(typedItem, this);
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex = ~targetIndex;
|
||||
}
|
||||
|
||||
// Only trigger expensive UI updates if the index really changed:
|
||||
if (targetIndex != oldIndex)
|
||||
{
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, typedItem));
|
||||
|
||||
view.Insert(targetIndex, typedItem);
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, typedItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Insert(targetIndex, typedItem);
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(e.PropertyName))
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachPropertyChangedHandler(IEnumerable items)
|
||||
{
|
||||
if (!liveShapingEnabled || items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object item in items)
|
||||
{
|
||||
if (item is INotifyPropertyChanged notifyPropertyChanged)
|
||||
{
|
||||
notifyPropertyChanged.PropertyChanged += ItemOnPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DetachPropertyChangedHandler(IEnumerable items)
|
||||
{
|
||||
if (!liveShapingEnabled || items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object item in items)
|
||||
{
|
||||
if (item is INotifyPropertyChanged notifyPropertyChanged)
|
||||
{
|
||||
notifyPropertyChanged.PropertyChanged -= ItemOnPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSortChanged()
|
||||
{
|
||||
sortProperties.Clear();
|
||||
view.Sort(this);
|
||||
sortProperties.Clear();
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
|
||||
}
|
||||
|
||||
private void HandleFilterChanged()
|
||||
{
|
||||
if (filter is not null)
|
||||
{
|
||||
for (int index = 0; index < view.Count; index++)
|
||||
{
|
||||
T item = view[index];
|
||||
if (filter(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveFromView(index, item);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<T> viewHash = new(view);
|
||||
int viewIndex = 0;
|
||||
for (int index = 0; index < source.Count; index++)
|
||||
{
|
||||
T item = source[index];
|
||||
if (viewHash.Contains(item))
|
||||
{
|
||||
viewIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HandleItemAdded(index, item, viewIndex))
|
||||
{
|
||||
viewIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSourceChanged()
|
||||
{
|
||||
sortProperties.Clear();
|
||||
T? currentItem = CurrentItem;
|
||||
view.Clear();
|
||||
foreach (T item in Source)
|
||||
{
|
||||
if (filter is not null && !filter(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sortDescriptions.Count > 0)
|
||||
{
|
||||
int targetIndex = view.BinarySearch(item, this);
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex = ~targetIndex;
|
||||
}
|
||||
|
||||
view.Insert(targetIndex, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
sortProperties.Clear();
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
|
||||
MoveCurrentTo(currentItem);
|
||||
}
|
||||
|
||||
private void SourceNotifyCollectionChangedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
ArgumentNullException.ThrowIfNull(e.NewItems);
|
||||
AttachPropertyChangedHandler(e.NewItems);
|
||||
if (deferCounter <= 0)
|
||||
{
|
||||
if (e.NewItems?.Count == 1)
|
||||
{
|
||||
object? newItem = e.NewItems[0];
|
||||
ArgumentNullException.ThrowIfNull(newItem);
|
||||
HandleItemAdded(e.NewStartingIndex, (T)newItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
ArgumentNullException.ThrowIfNull(e.OldItems);
|
||||
DetachPropertyChangedHandler(e.OldItems);
|
||||
if (deferCounter <= 0)
|
||||
{
|
||||
if (e.OldItems?.Count == 1)
|
||||
{
|
||||
object? oldItem = e.OldItems[0];
|
||||
ArgumentNullException.ThrowIfNull(oldItem);
|
||||
HandleItemRemoved(e.OldStartingIndex, (T)oldItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (deferCounter <= 0)
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleItemAdded(int newStartingIndex, T newItem, int? viewIndex = null)
|
||||
{
|
||||
if (filter is not null && !filter(newItem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int newViewIndex = view.Count;
|
||||
|
||||
if (sortDescriptions.Count > 0)
|
||||
{
|
||||
sortProperties.Clear();
|
||||
newViewIndex = view.BinarySearch(newItem, this);
|
||||
if (newViewIndex < 0)
|
||||
{
|
||||
newViewIndex = ~newViewIndex;
|
||||
}
|
||||
}
|
||||
else if (filter is not null)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
HandleSourceChanged();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newStartingIndex == 0 || view.Count == 0)
|
||||
{
|
||||
newViewIndex = 0;
|
||||
}
|
||||
else if (newStartingIndex == source.Count - 1)
|
||||
{
|
||||
newViewIndex = view.Count;
|
||||
}
|
||||
else if (viewIndex.HasValue)
|
||||
{
|
||||
newViewIndex = viewIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0, j = 0; i < source.Count; i++)
|
||||
{
|
||||
if (i == newStartingIndex)
|
||||
{
|
||||
newViewIndex = j;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Equals(view[j], source[i]))
|
||||
{
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.Insert(newViewIndex, newItem);
|
||||
if (newViewIndex <= CurrentPosition)
|
||||
{
|
||||
CurrentPosition++;
|
||||
}
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, newViewIndex, newItem));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleItemRemoved(int oldStartingIndex, T oldItem)
|
||||
{
|
||||
if (filter is not null && !filter(oldItem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldStartingIndex < 0 || oldStartingIndex >= view.Count || !Equals(view[oldStartingIndex], oldItem))
|
||||
{
|
||||
oldStartingIndex = view.IndexOf(oldItem);
|
||||
}
|
||||
|
||||
if (oldStartingIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveFromView(oldStartingIndex, oldItem);
|
||||
}
|
||||
|
||||
private void RemoveFromView(int itemIndex, T item)
|
||||
{
|
||||
view.RemoveAt(itemIndex);
|
||||
if (itemIndex <= CurrentPosition)
|
||||
{
|
||||
CurrentPosition--;
|
||||
}
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, itemIndex, item));
|
||||
}
|
||||
|
||||
private void SortDescriptionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleSortChanged();
|
||||
}
|
||||
|
||||
private bool MoveCurrentToIndex(int i)
|
||||
{
|
||||
if (i < -1 || i >= view.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == CurrentPosition)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentChangingEventArgs e = new();
|
||||
OnCurrentChanging(e);
|
||||
if (e.Cancel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentPosition = i;
|
||||
OnCurrentChanged(default!);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnCurrentChanging(CurrentChangingEventArgs e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentChanging?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnCurrentChanged(object e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentChanged?.Invoke(this, e);
|
||||
OnPropertyChanged(nameof(CurrentItem));
|
||||
}
|
||||
|
||||
private void OnVectorChanged(IVectorChangedEventArgs e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VectorChanged?.Invoke(this, e);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
}
|
||||
|
||||
internal sealed class NotificationDeferrer : IDisposable
|
||||
{
|
||||
private readonly AdvancedCollectionView<T> advancedCollectionView;
|
||||
private readonly T? currentItem;
|
||||
|
||||
public NotificationDeferrer(AdvancedCollectionView<T> acvs)
|
||||
{
|
||||
advancedCollectionView = acvs;
|
||||
currentItem = advancedCollectionView.CurrentItem;
|
||||
advancedCollectionView.deferCounter++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
advancedCollectionView.MoveCurrentTo(currentItem);
|
||||
advancedCollectionView.deferCounter--;
|
||||
advancedCollectionView.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
internal interface IAdvancedCollectionView<T> : ICollectionView, IEnumerable
|
||||
where T : class
|
||||
{
|
||||
bool CanFilter { get; }
|
||||
|
||||
bool CanSort { get; }
|
||||
|
||||
object? ICollectionView.CurrentItem
|
||||
{
|
||||
get => CurrentItem;
|
||||
}
|
||||
|
||||
new T? CurrentItem { get; }
|
||||
|
||||
Predicate<T>? Filter { get; set; }
|
||||
|
||||
IList<SortDescription> SortDescriptions { get; }
|
||||
|
||||
IEnumerable<T> SourceCollection { get; }
|
||||
|
||||
object IList<object>.this[int index]
|
||||
{
|
||||
get => this[index];
|
||||
set => this[index] = (T)value;
|
||||
}
|
||||
|
||||
new T this[int index] { get; set; }
|
||||
|
||||
void ICollection<object>.Add(object item)
|
||||
{
|
||||
Add((T)item);
|
||||
}
|
||||
|
||||
void Add(T item);
|
||||
|
||||
void ClearObservedFilterProperties();
|
||||
|
||||
bool ICollection<object>.Contains(object item)
|
||||
{
|
||||
return Contains((T)item);
|
||||
}
|
||||
|
||||
bool Contains(T item);
|
||||
|
||||
void ICollection<object>.CopyTo(object[] array, int arrayIndex)
|
||||
{
|
||||
CopyTo((T[])array, arrayIndex);
|
||||
}
|
||||
|
||||
void CopyTo(T[] array, int arrayIndex);
|
||||
|
||||
IDisposable DeferRefresh();
|
||||
|
||||
IEnumerator<object> IEnumerable<object>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
new IEnumerator<T> GetEnumerator();
|
||||
|
||||
int IList<object>.IndexOf(object item)
|
||||
{
|
||||
return IndexOf((T)item);
|
||||
}
|
||||
|
||||
int IndexOf(T item);
|
||||
|
||||
void IList<object>.Insert(int index, object item)
|
||||
{
|
||||
Insert(index, (T)item);
|
||||
}
|
||||
|
||||
void Insert(int index, T item);
|
||||
|
||||
bool ICollectionView.MoveCurrentTo(object item)
|
||||
{
|
||||
return MoveCurrentTo((T)item);
|
||||
}
|
||||
|
||||
bool MoveCurrentTo(T item);
|
||||
|
||||
void ObserveFilterProperty(string propertyName);
|
||||
|
||||
void Refresh();
|
||||
|
||||
void RefreshFilter();
|
||||
|
||||
void RefreshSorting();
|
||||
|
||||
bool ICollection<object>.Remove(object item)
|
||||
{
|
||||
return Remove((T)item);
|
||||
}
|
||||
|
||||
bool Remove(T item);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
internal sealed class VectorChangedEventArgs : IVectorChangedEventArgs
|
||||
{
|
||||
public VectorChangedEventArgs(CollectionChange cc, int index = -1, object item = null!)
|
||||
{
|
||||
CollectionChange = cc;
|
||||
Index = (uint)index;
|
||||
}
|
||||
|
||||
public CollectionChange CollectionChange { get; }
|
||||
|
||||
public uint Index { get; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Extension;
|
||||
|
||||
internal static class CommandExtension
|
||||
{
|
||||
public static bool TryExecute(this ICommand? command, object? parameter = null)
|
||||
{
|
||||
if (command is not null && command.CanExecute(parameter))
|
||||
{
|
||||
command.Execute(parameter);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
24
src/Snap.Hutao/Snap.Hutao/Control/Helper/UIElementHelper.cs
Normal file
24
src/Snap.Hutao/Snap.Hutao/Control/Helper/UIElementHelper.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Control.Helper;
|
||||
|
||||
[SuppressMessage("", "SH001")]
|
||||
[DependencyProperty("VisibilityObject", typeof(object), null, nameof(OnVisibilityObjectChanged), IsAttached = true, AttachedType = typeof(UIElement))]
|
||||
[DependencyProperty("OpacityObject", typeof(object), null, nameof(OnOpacityObjectChanged), IsAttached = true, AttachedType = typeof(UIElement))]
|
||||
public sealed partial class UIElementHelper
|
||||
{
|
||||
private static void OnVisibilityObjectChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UIElement element = (UIElement)dp;
|
||||
element.Visibility = e.NewValue is null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
private static void OnOpacityObjectChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UIElement element = (UIElement)dp;
|
||||
element.Opacity = e.NewValue is null ? 0D : 1D;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
internal interface IXamlElementAccessor;
|
||||
@@ -192,7 +192,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await AnimationBuilder
|
||||
.Create()
|
||||
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
|
||||
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageScaleFadeIn)
|
||||
.StartAsync(this, token)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
@@ -213,7 +213,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await AnimationBuilder
|
||||
.Create()
|
||||
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
|
||||
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageScaleFadeOut)
|
||||
.StartAsync(this, token)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@ internal struct Bgra32
|
||||
/// </summary>
|
||||
public byte A;
|
||||
|
||||
public Bgra32(byte b, byte g, byte r, byte a)
|
||||
{
|
||||
B = b;
|
||||
G = g;
|
||||
R = r;
|
||||
A = a;
|
||||
}
|
||||
|
||||
public readonly double Luminance { get => ((0.299 * R) + (0.587 * G) + (0.114 * B)) / 255; }
|
||||
|
||||
/// <summary>
|
||||
/// 从 Color 转换
|
||||
/// </summary>
|
||||
@@ -44,4 +54,11 @@ internal struct Bgra32
|
||||
*(uint*)&bgra8 = BinaryPrimitives.ReverseEndianness(*(uint*)&color);
|
||||
return bgra8;
|
||||
}
|
||||
|
||||
public static unsafe implicit operator Color(Bgra32 bgra8)
|
||||
{
|
||||
Unsafe.SkipInit(out Color color);
|
||||
*(uint*)&color = BinaryPrimitives.ReverseEndianness(*(uint*)&bgra8);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
|
||||
/// </summary>
|
||||
internal struct Hsl32
|
||||
internal struct Hsla32
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hue in 0..360 range.
|
||||
@@ -46,14 +46,14 @@ internal struct Rgba32
|
||||
/// <summary>
|
||||
/// 使用 RGBA 代码初始化新的结构
|
||||
/// </summary>
|
||||
/// <param name="code">RGBA 代码</param>
|
||||
public unsafe Rgba32(uint code)
|
||||
/// <param name="xrgbaCode">RGBA 代码</param>
|
||||
public unsafe Rgba32(uint xrgbaCode)
|
||||
{
|
||||
// uint layout: 0xRRGGBBAA -> AABBGGRR
|
||||
// uint layout: 0xRRGGBBAA is AABBGGRR
|
||||
// AABBGGRR -> RRGGBBAA
|
||||
fixed (Rgba32* pSelf = &this)
|
||||
{
|
||||
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(code);
|
||||
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(xrgbaCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +67,14 @@ internal struct Rgba32
|
||||
|
||||
public static unsafe implicit operator Color(Rgba32 hexColor)
|
||||
{
|
||||
// AABBGGRR -> BBGGRRAA
|
||||
// AABBGGRR -> 000000AA
|
||||
uint a = (*(uint*)&hexColor) >> 24;
|
||||
// Goal : Rgba32:RRGGBBAA(0xAABBGGRR) -> Color: AARRGGBB(0xBBGGRRAA)
|
||||
// Step1: Rgba32:RRGGBBAA(0xAABBGGRR) -> UInt32:AA000000(0x000000AA)
|
||||
uint a = ((*(uint*)&hexColor) >> 24) & 0x000000FF;
|
||||
|
||||
// AABBGGRR -> BBGGRR00
|
||||
uint rgb = (*(uint*)&hexColor) << 8;
|
||||
// Step2: Rgba32:RRGGBBAA(0xAABBGGRR) -> UInt32:00RRGGBB(0xRRGGBB00)
|
||||
uint rgb = ((*(uint*)&hexColor) << 8) & 0xFFFFFF00;
|
||||
|
||||
// BBGGRR00 + 000000AA
|
||||
// Step2: UInt32:00RRGGBB(0xRRGGBB00) + UInt32:AA000000(0x000000AA) -> UInt32:AARRGGBB(0xRRGGBBAA)
|
||||
uint rgba = rgb + a;
|
||||
|
||||
return *(Color*)&rgba;
|
||||
@@ -85,7 +85,7 @@ internal struct Rgba32
|
||||
/// </summary>
|
||||
/// <param name="hsl">HSL 颜色</param>
|
||||
/// <returns>RGBA8颜色</returns>
|
||||
public static Rgba32 FromHsl(Hsl32 hsl)
|
||||
public static Rgba32 FromHsl(Hsla32 hsl)
|
||||
{
|
||||
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
|
||||
double h1 = hsl.H / 60;
|
||||
@@ -142,7 +142,7 @@ internal struct Rgba32
|
||||
/// 转换到 HSL 颜色
|
||||
/// </summary>
|
||||
/// <returns>HSL 颜色</returns>
|
||||
public readonly Hsl32 ToHsl()
|
||||
public readonly Hsla32 ToHsl()
|
||||
{
|
||||
const double toDouble = 1.0 / 255;
|
||||
double r = toDouble * R;
|
||||
@@ -175,7 +175,7 @@ internal struct Rgba32
|
||||
double lightness = 0.5 * (max + min);
|
||||
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
|
||||
|
||||
Hsl32 ret;
|
||||
Hsla32 ret;
|
||||
ret.H = 60 * h1;
|
||||
ret.S = saturation;
|
||||
ret.L = lightness;
|
||||
|
||||
@@ -25,8 +25,7 @@ internal static class SoftwareBitmapExtension
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out byte* data, out uint length);
|
||||
Span<Bgra32> bytes = new(data, unchecked((int)length / sizeof(Bgra32)));
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
foreach (ref Bgra32 pixel in bytes)
|
||||
{
|
||||
byte baseAlpha = pixel.A;
|
||||
@@ -39,4 +38,43 @@ internal static class SoftwareBitmapExtension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe double Luminance(this SoftwareBitmap softwareBitmap)
|
||||
{
|
||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
double sum = 0;
|
||||
foreach (ref readonly Bgra32 pixel in bytes)
|
||||
{
|
||||
sum += pixel.Luminance;
|
||||
}
|
||||
|
||||
return sum / bytes.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap)
|
||||
{
|
||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
double b = 0, g = 0, r = 0, a = 0;
|
||||
foreach (ref readonly Bgra32 pixel in bytes)
|
||||
{
|
||||
b += pixel.B;
|
||||
g += pixel.G;
|
||||
r += pixel.R;
|
||||
a += pixel.A;
|
||||
}
|
||||
|
||||
return new((byte)(b / bytes.Length), (byte)(g / bytes.Length), (byte)(r / bytes.Length), (byte)(a / bytes.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/Snap.Hutao/Snap.Hutao/Control/Panel/EqualPanel.cs
Normal file
89
src/Snap.Hutao/Snap.Hutao/Control/Panel/EqualPanel.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Data;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
[DependencyProperty("Spacing", typeof(double), default(double), nameof(OnSpacingChanged))]
|
||||
internal partial class EqualPanel : Microsoft.UI.Xaml.Controls.Panel
|
||||
{
|
||||
private double maxItemWidth;
|
||||
private double maxItemHeight;
|
||||
private int visibleItemsCount;
|
||||
|
||||
public EqualPanel()
|
||||
{
|
||||
RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnHorizontalAlignmentChanged);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
maxItemWidth = 0;
|
||||
maxItemHeight = 0;
|
||||
|
||||
List<UIElement> elements = [.. Children.Where(element => element.Visibility == Visibility.Visible)];
|
||||
visibleItemsCount = elements.Count;
|
||||
|
||||
foreach (ref readonly UIElement child in CollectionsMarshal.AsSpan(elements))
|
||||
{
|
||||
child.Measure(availableSize);
|
||||
maxItemWidth = Math.Max(maxItemWidth, child.DesiredSize.Width);
|
||||
maxItemHeight = Math.Max(maxItemHeight, child.DesiredSize.Height);
|
||||
}
|
||||
|
||||
if (visibleItemsCount > 0)
|
||||
{
|
||||
// Return equal widths based on the widest item
|
||||
// In very specific edge cases the AvailableWidth might be infinite resulting in a crash.
|
||||
if (HorizontalAlignment is not HorizontalAlignment.Stretch || double.IsInfinity(availableSize.Width))
|
||||
{
|
||||
return new Size((maxItemWidth * visibleItemsCount) + (Spacing * (visibleItemsCount - 1)), maxItemHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Equal columns based on the available width, adjust for spacing
|
||||
double totalWidth = availableSize.Width - (Spacing * (visibleItemsCount - 1));
|
||||
maxItemWidth = totalWidth / visibleItemsCount;
|
||||
return new Size(availableSize.Width, maxItemHeight);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Size(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
double x = 0;
|
||||
|
||||
// Check if there's more (little) width available - if so, set max item width to the maximum possible as we have an almost perfect height.
|
||||
if (finalSize.Width > (visibleItemsCount * maxItemWidth) + (Spacing * (visibleItemsCount - 1)))
|
||||
{
|
||||
maxItemWidth = (finalSize.Width - (Spacing * (visibleItemsCount - 1))) / visibleItemsCount;
|
||||
}
|
||||
|
||||
IEnumerable<UIElement> elements = Children.Where(static e => e.Visibility == Visibility.Visible);
|
||||
foreach (UIElement child in elements)
|
||||
{
|
||||
child.Arrange(new Rect(x, 0, maxItemWidth, maxItemHeight));
|
||||
x += maxItemWidth + Spacing;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
(d as EqualPanel)?.InvalidateMeasure();
|
||||
}
|
||||
|
||||
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
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"
|
||||
Style="{StaticResource DefaultSegmentedStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<cwc:SegmentedItem
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
@@ -19,11 +20,11 @@ internal sealed partial class PanelSelector : Segmented
|
||||
public const string List = nameof(List);
|
||||
public const string Grid = nameof(Grid);
|
||||
|
||||
private static readonly Dictionary<int, string> IndexTypeMap = new()
|
||||
{
|
||||
[0] = List,
|
||||
[1] = Grid,
|
||||
};
|
||||
private static readonly FrozenDictionary<int, string> IndexTypeMap = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(0, List),
|
||||
KeyValuePair.Create(1, Grid),
|
||||
]);
|
||||
|
||||
private readonly RoutedEventHandler loadedEventHandler;
|
||||
private readonly RoutedEventHandler unloadedEventHandler;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Query.Internal;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
@@ -109,7 +108,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
else
|
||||
{
|
||||
// Make lighter in light mode
|
||||
Hsl32 hsl = color.ToHsl();
|
||||
Hsla32 hsl = color.ToHsl();
|
||||
hsl.L *= 0.3;
|
||||
targetColor = Rgba32.FromHsl(hsl);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ internal sealed partial class HtmlDescriptionTextBlock : ContentControl
|
||||
else
|
||||
{
|
||||
// Make lighter in light mode
|
||||
Hsl32 hsl = color.ToHsl();
|
||||
Hsla32 hsl = color.ToHsl();
|
||||
hsl.L *= 0.3;
|
||||
targetColor = Rgba32.FromHsl(hsl);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ internal sealed class MiHoYoColorTextSyntax : MiHoYoXmlElementSyntax
|
||||
ColorKind = colorKind;
|
||||
}
|
||||
|
||||
public MiHoYoColorTextSyntax(MiHoYoColorKind colorKind, string text, TextPosition position)
|
||||
public MiHoYoColorTextSyntax(MiHoYoColorKind colorKind, string text, in TextPosition position)
|
||||
: base(MiHoYoSyntaxKind.ColorText, text, position)
|
||||
{
|
||||
ColorKind = colorKind;
|
||||
|
||||
@@ -10,7 +10,7 @@ internal sealed class MiHoYoItalicTextSyntax : MiHoYoXmlElementSyntax
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoItalicTextSyntax(string text, TextPosition position)
|
||||
public MiHoYoItalicTextSyntax(string text, in TextPosition position)
|
||||
: base(MiHoYoSyntaxKind.ItalicText, text, position)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ internal sealed class MiHoYoPlainTextSyntax : MiHoYoSyntaxNode
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoPlainTextSyntax(string text, TextPosition position)
|
||||
public MiHoYoPlainTextSyntax(string text, in TextPosition position)
|
||||
: base(MiHoYoSyntaxKind.PlainText, text, position)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ internal abstract class MiHoYoSyntaxNode : SyntaxNode<MiHoYoSyntaxNode, MiHoYoSy
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoSyntaxNode(MiHoYoSyntaxKind kind, string text, TextPosition position)
|
||||
public MiHoYoSyntaxNode(MiHoYoSyntaxKind kind, string text, in TextPosition position)
|
||||
: base(kind, text, position)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ internal abstract class MiHoYoXmlElementSyntax : MiHoYoSyntaxNode
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoXmlElementSyntax(MiHoYoSyntaxKind kind, string text, TextPosition position)
|
||||
public MiHoYoXmlElementSyntax(MiHoYoSyntaxKind kind, string text, in TextPosition position)
|
||||
: base(kind, text, position)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ internal abstract class SyntaxNode<TSelf, TKind>
|
||||
Position = new(start, end);
|
||||
}
|
||||
|
||||
public SyntaxNode(TKind kind, string text, TextPosition position)
|
||||
public SyntaxNode(TKind kind, string text, in TextPosition position)
|
||||
{
|
||||
Kind = kind;
|
||||
Text = text;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
Offset="0,4,0"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style x:Key="BorderCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
@@ -25,20 +26,20 @@
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicBaseHighBorderCardStyle"
|
||||
BasedOn="{StaticResource BorderCardStyle}"
|
||||
TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBaseHighAcrylicElementBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicBorderCardStyle"
|
||||
BasedOn="{StaticResource BorderCardStyle}"
|
||||
TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicSecondaryBorderCardStyle"
|
||||
BasedOn="{StaticResource BorderCardStyle}"
|
||||
TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlChromeMediumAcrylicElementMediumBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GridCardStyle" TargetType="Grid">
|
||||
@@ -53,14 +54,14 @@
|
||||
BasedOn="{StaticResource GridCardStyle}"
|
||||
TargetType="Grid">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicBaseHighGridCardStyle"
|
||||
x:Key="AcrylicSecondaryGridCardStyle"
|
||||
BasedOn="{StaticResource GridCardStyle}"
|
||||
TargetType="Grid">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBaseHighAcrylicElementBrush}"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlChromeMediumAcrylicElementMediumBrush}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -17,6 +17,9 @@
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing2Template">
|
||||
<StackPanel Orientation="Horizontal" Spacing="2"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing4Template">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
|
||||
<StackPanel Spacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
16
src/Snap.Hutao/Snap.Hutao/Control/Theme/KnownColors.cs
Normal file
16
src/Snap.Hutao/Snap.Hutao/Control/Theme/KnownColors.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Theme;
|
||||
|
||||
internal static class KnownColors
|
||||
{
|
||||
public static readonly Color Orange = StructMarshal.Color(0xFFBC6932);
|
||||
public static readonly Color Purple = StructMarshal.Color(0xFFA156E0);
|
||||
public static readonly Color Blue = StructMarshal.Color(0xFF5180CB);
|
||||
public static readonly Color Green = StructMarshal.Color(0xFF2A8F72);
|
||||
public static readonly Color White = StructMarshal.Color(0xFF72778B);
|
||||
}
|
||||
@@ -193,6 +193,379 @@
|
||||
<CompositeTransform x:Name="PivotLayoutElementTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
|
||||
<ItemsPresenter x:Name="PivotItemPresenter" Grid.Row="1">
|
||||
<ItemsPresenter.RenderTransform>
|
||||
<TransformGroup>
|
||||
<TranslateTransform x:Name="ItemsPresenterTranslateTransform"/>
|
||||
<CompositeTransform x:Name="ItemsPresenterCompositeTransform"/>
|
||||
</TransformGroup>
|
||||
</ItemsPresenter.RenderTransform>
|
||||
</ItemsPresenter>
|
||||
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Margin="16,16,16,0"
|
||||
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Grid Style="{ThemeResource AcrylicSecondaryGridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter
|
||||
x:Name="LeftHeaderPresenter"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding LeftHeader}"
|
||||
ContentTemplate="{TemplateBinding LeftHeaderTemplate}"/>
|
||||
<ContentControl
|
||||
x:Name="HeaderClipper"
|
||||
Grid.Column="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
UseSystemFocusVisuals="{StaticResource UseSystemFocusVisuals}">
|
||||
<ContentControl.Clip>
|
||||
<RectangleGeometry x:Name="HeaderClipperGeometry"/>
|
||||
</ContentControl.Clip>
|
||||
<Grid>
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform x:Name="HeaderOffsetTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
<PivotHeaderPanel x:Name="StaticHeader" Visibility="Collapsed">
|
||||
<PivotHeaderPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="StaticHeaderTranslateTransform"/>
|
||||
</PivotHeaderPanel.RenderTransform>
|
||||
</PivotHeaderPanel>
|
||||
<PivotHeaderPanel x:Name="Header">
|
||||
<PivotHeaderPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="HeaderTranslateTransform"/>
|
||||
</PivotHeaderPanel.RenderTransform>
|
||||
</PivotHeaderPanel>
|
||||
<Rectangle
|
||||
x:Name="FocusFollower"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
Fill="Transparent"
|
||||
IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
<Button
|
||||
x:Name="PreviousButton"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="36"
|
||||
Margin="{ThemeResource PivotNavButtonMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
IsEnabled="False"
|
||||
IsTabStop="False"
|
||||
Opacity="0"
|
||||
Template="{StaticResource PreviousTemplate}"
|
||||
UseSystemFocusVisuals="False"/>
|
||||
<Button
|
||||
x:Name="NextButton"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="36"
|
||||
Margin="{ThemeResource PivotNavButtonMargin}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
IsEnabled="False"
|
||||
IsTabStop="False"
|
||||
Opacity="0"
|
||||
Template="{StaticResource NextTemplate}"
|
||||
UseSystemFocusVisuals="False"/>
|
||||
<ContentPresenter
|
||||
x:Name="RightHeaderPresenter"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding RightHeader}"
|
||||
ContentTemplate="{TemplateBinding RightHeaderTemplate}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</PivotPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="Orientation">
|
||||
<VisualState x:Name="Portrait">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPortraitThemePadding}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Landscape">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotLandscapeThemePadding}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="NavigationButtonsVisibility">
|
||||
<VisualState x:Name="NavigationButtonsHidden"/>
|
||||
<VisualState x:Name="NavigationButtonsVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PreviousButtonVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NextButtonVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="HeaderStates">
|
||||
<VisualState x:Name="HeaderDynamic"/>
|
||||
<VisualState x:Name="HeaderStatic">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Header" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StaticHeader" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CardPivotStyle2" TargetType="Pivot">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource PivotBackground}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Pivot">
|
||||
<Grid
|
||||
x:Name="RootElement"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="BaseContentControlStyle" TargetType="ContentControl">
|
||||
<Setter Property="FontFamily" Value="XamlAutoFontFamily"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
OpticalMarginAlignment="TrimSideBearings"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="TitleContentControlStyle"
|
||||
BasedOn="{StaticResource BaseContentControlStyle}"
|
||||
TargetType="ContentControl">
|
||||
<Setter Property="FontFamily" Value="{ThemeResource PivotTitleFontFamily}"/>
|
||||
<Setter Property="FontWeight" Value="{ThemeResource PivotTitleThemeFontWeight}"/>
|
||||
<Setter Property="FontSize" Value="{ThemeResource PivotTitleFontSize}"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
x:Name="TitleContentControl"
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource PivotPortraitThemePadding}"
|
||||
Content="{TemplateBinding Title}"
|
||||
ContentTemplate="{TemplateBinding TitleTemplate}"
|
||||
IsTabStop="False"
|
||||
Style="{StaticResource TitleContentControlStyle}"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.Resources>
|
||||
<ControlTemplate x:Key="NextTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="{ThemeResource PivotNextButtonBackground}"
|
||||
BorderBrush="{ThemeResource PivotNextButtonBorderBrush}"
|
||||
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
|
||||
<FontIcon
|
||||
x:Name="Arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource PivotNextButtonForeground}"
|
||||
Glyph=""
|
||||
MirroredWhenRightToLeft="True"
|
||||
UseLayoutRounding="False"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal"/>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="PreviousTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="{ThemeResource PivotPreviousButtonBackground}"
|
||||
BorderBrush="{ThemeResource PivotPreviousButtonBorderBrush}"
|
||||
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
|
||||
<FontIcon
|
||||
x:Name="Arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource PivotPreviousButtonForeground}"
|
||||
Glyph=""
|
||||
MirroredWhenRightToLeft="True"
|
||||
UseLayoutRounding="False"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal"/>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Grid.Resources>
|
||||
<ScrollViewer
|
||||
x:Name="ScrollViewer"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
BringIntoViewOnFocusChange="False"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
HorizontalSnapPointsAlignment="Center"
|
||||
HorizontalSnapPointsType="MandatorySingle"
|
||||
Template="{StaticResource ScrollViewerScrollBarlessTemplate}"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
VerticalScrollMode="Disabled"
|
||||
VerticalSnapPointsType="None"
|
||||
ZoomMode="Disabled">
|
||||
<PivotPanel x:Name="Panel" VerticalAlignment="Stretch">
|
||||
<Grid x:Name="PivotLayoutElement">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform x:Name="PivotLayoutElementTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
|
||||
|
||||
|
||||
<ItemsPresenter x:Name="PivotItemPresenter" Grid.Row="1">
|
||||
@@ -208,7 +581,7 @@
|
||||
Grid.Row="0"
|
||||
Margin="16,16,16,0"
|
||||
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Grid Style="{ThemeResource AcrylicBaseHighGridCardStyle}">
|
||||
<Grid Style="{ThemeResource AcrylicSecondaryGridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
|
||||
115
src/Snap.Hutao/Snap.Hutao/Control/Theme/SegmentedOverride.xaml
Normal file
115
src/Snap.Hutao/Snap.Hutao/Control/Theme/SegmentedOverride.xaml
Normal file
@@ -0,0 +1,115 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cw="using:CommunityToolkit.WinUI"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/SegmentedItem/SegmentedItem.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="SegmentedBackground" ResourceKey="ControlAltFillColorSecondaryBrush"/>
|
||||
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="ControlStrokeColorDefaultBrush"/>
|
||||
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="SegmentedBackground" ResourceKey="ControlAltFillColorSecondaryBrush"/>
|
||||
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="ControlStrokeColorDefaultBrush"/>
|
||||
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="SegmentedBackground" ResourceKey="SystemColorButtonFaceColor"/>
|
||||
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="SystemColorHighlightColorBrush"/>
|
||||
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<x:Double x:Key="SegmentedItemSpacing">1</x:Double>
|
||||
<x:Double x:Key="ButtonItemSpacing">2</x:Double>
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultSegmentedStyle}" TargetType="cwc:Segmented"/>
|
||||
|
||||
<Style x:Key="DefaultSegmentedStyle" TargetType="cwc:Segmented">
|
||||
<Style.Setters>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SegmentedBackground}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SegmentedBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SegmentedBorderThickness}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="SelectionMode" Value="Single"/>
|
||||
<Setter Property="IsItemClickEnabled" Value="False"/>
|
||||
<win:Setter Property="SingleSelectionFollowsFocus" Value="False"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="TabNavigation" Value="Once"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<shcp:EqualPanel
|
||||
HorizontalAlignment="{Binding (cw:FrameworkElementExtensions.Ancestor).HorizontalAlignment, RelativeSource={RelativeSource Self}}"
|
||||
cw:FrameworkElementExtensions.AncestorType="cwc:Segmented"
|
||||
Spacing="{ThemeResource SegmentedItemSpacing}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="cwc:Segmented">
|
||||
<Grid>
|
||||
<Border
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"/>
|
||||
<ItemsPresenter Margin="{TemplateBinding Padding}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="PivotSegmentedStyle"
|
||||
BasedOn="{StaticResource DefaultSegmentedStyle}"
|
||||
TargetType="cwc:Segmented">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource PivotSegmentedItemStyle}"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="{ThemeResource SegmentedItemSpacing}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="ButtonSegmentedStyle"
|
||||
BasedOn="{StaticResource DefaultSegmentedStyle}"
|
||||
TargetType="cwc:Segmented">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource ButtonSegmentedItemStyle}"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="{ThemeResource ButtonItemSpacing}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,7 +1,4 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls">
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Settings -->
|
||||
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
@@ -24,6 +21,14 @@
|
||||
<Setter Property="Margin" Value="1,29,0,5"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingsCardHeaderTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="1,0,0,5"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingsContentComboBoxStyle"
|
||||
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||
|
||||
5
src/Snap.Hutao/Snap.Hutao/Control/Theme/Thickness.xaml
Normal file
5
src/Snap.Hutao/Snap.Hutao/Control/Theme/Thickness.xaml
Normal file
@@ -0,0 +1,5 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Thickness x:Key="ListViewInSplitPanePadding">0,2</Thickness>
|
||||
<!-- https://github.com/microsoft/microsoft-ui-xaml/issues/4811 -->
|
||||
<x:Int32 x:Key="__DiscardPageOverride">0</x:Int32>
|
||||
</ResourceDictionary>
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using Snap.Hutao.Core.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
@@ -10,7 +9,7 @@ namespace Snap.Hutao.Core.Caching;
|
||||
/// 为图像缓存提供抽象
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IImageCache : ICastService
|
||||
internal interface IImageCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the file path containing cached item for given Uri
|
||||
|
||||
@@ -26,12 +26,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
private const string CacheFolderName = nameof(ImageCache);
|
||||
|
||||
private readonly FrozenDictionary<int, TimeSpan> retryCountToDelay = new Dictionary<int, TimeSpan>()
|
||||
{
|
||||
[0] = TimeSpan.FromSeconds(4),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
[2] = TimeSpan.FromSeconds(64),
|
||||
}.ToFrozenDictionary();
|
||||
private readonly FrozenDictionary<int, TimeSpan> retryCountToDelay = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(0, TimeSpan.FromSeconds(4)),
|
||||
KeyValuePair.Create(1, TimeSpan.FromSeconds(16)),
|
||||
KeyValuePair.Create(2, TimeSpan.FromSeconds(64)),
|
||||
]);
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
@@ -42,10 +42,20 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
private string? baseFolder;
|
||||
private string? cacheFolder;
|
||||
|
||||
private string CacheFolder
|
||||
{
|
||||
get => LazyInitializer.EnsureInitialized(ref cacheFolder, () =>
|
||||
{
|
||||
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
|
||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||
return info.FullName;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveInvalid()
|
||||
{
|
||||
RemoveInternal(Directory.GetFiles(GetCacheFolder()).Where(file => IsFileInvalid(file, false)));
|
||||
RemoveCore(Directory.GetFiles(CacheFolder).Where(file => IsFileInvalid(file, false)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -62,7 +72,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
return;
|
||||
}
|
||||
|
||||
string folder = GetCacheFolder();
|
||||
string folder = CacheFolder;
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
|
||||
List<string> filesToDelete = [];
|
||||
@@ -75,16 +85,16 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
}
|
||||
}
|
||||
|
||||
RemoveInternal(filesToDelete);
|
||||
RemoveCore(filesToDelete);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
string fileName = GetCacheFileName(uri);
|
||||
string filePath = Path.Combine(GetCacheFolder(), fileName);
|
||||
string filePath = Path.Combine(CacheFolder, fileName);
|
||||
|
||||
if (File.Exists(filePath) && new FileInfo(filePath).Length != 0)
|
||||
if (!IsFileInvalid(filePath))
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
@@ -94,10 +104,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
||||
{
|
||||
logger.LogDebug("Begin downloading image file from '{Uri}' to '{File}'", uri, filePath);
|
||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||
}
|
||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
{
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", uri);
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -115,7 +127,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
public ValueFile GetFileFromCategoryAndName(string category, string fileName)
|
||||
{
|
||||
Uri dummyUri = Web.HutaoEndpoints.StaticRaw(category, fileName).ToUri();
|
||||
return Path.Combine(GetCacheFolder(), GetCacheFileName(dummyUri));
|
||||
return Path.Combine(CacheFolder, GetCacheFileName(dummyUri));
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
@@ -137,17 +149,18 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
return fileInfo.Length == 0;
|
||||
}
|
||||
|
||||
private void RemoveInternal(IEnumerable<string> filePaths)
|
||||
private void RemoveCore(IEnumerable<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
logger.LogInformation("Remove cached image succeed:{File}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Remove Cache Image Failed:{File}", filePath);
|
||||
logger.LogWarning(ex, "Remove cached image failed:{File}", filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,14 +168,17 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
[SuppressMessage("", "SH003")]
|
||||
private async Task DownloadFileAsync(Uri uri, string baseFile)
|
||||
{
|
||||
logger.LogInformation("Begin downloading for {Uri}", uri);
|
||||
|
||||
int retryCount = 0;
|
||||
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
@@ -181,7 +197,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry {Uri} after {Delay}.", uri, delay);
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
@@ -192,18 +208,4 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCacheFolder()
|
||||
{
|
||||
if (cacheFolder is not null)
|
||||
{
|
||||
return cacheFolder;
|
||||
}
|
||||
|
||||
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
|
||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||
cacheFolder = info.FullName;
|
||||
|
||||
return cacheFolder;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Snap.Hutao.Core.Database;
|
||||
internal static class QueryableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// source.Where(predicate).ExecuteDelete()
|
||||
/// <code>source.Where(predicate).ExecuteDelete()</code>
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
@@ -27,7 +27,7 @@ internal static class QueryableExtension
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// source.Where(predicate).ExecuteDeleteAsync(token)
|
||||
/// <code>source.Where(predicate).ExecuteDeleteAsync(token)</code>
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
/// <summary>
|
||||
/// 可转换类型服务
|
||||
/// </summary>
|
||||
[Obsolete("Not useful anymore")]
|
||||
internal interface ICastService
|
||||
{
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 有名称的对象
|
||||
/// 指示该对象可通过名称区分
|
||||
/// </summary>
|
||||
[Obsolete("无意义的接口")]
|
||||
[HighQuality]
|
||||
internal interface INamedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 海外服/HoYoLAB 可区分
|
||||
/// </summary>
|
||||
[Obsolete("Use IOverseaSupportFactory instead")]
|
||||
internal interface IOverseaSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为 海外服/HoYoLAB
|
||||
/// </summary>
|
||||
public bool IsOversea { get; }
|
||||
}
|
||||
@@ -17,6 +17,7 @@ internal static class CastServiceExtension
|
||||
/// <typeparam name="T">目标转换类型</typeparam>
|
||||
/// <param name="service">对象</param>
|
||||
/// <returns>转换类型后的对象</returns>
|
||||
[Obsolete("Not useful anymore")]
|
||||
public static T? As<T>(this ICastService service)
|
||||
where T : class
|
||||
{
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Snap.Hutao.Core.IO.Http.Proxy;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Globalization;
|
||||
|
||||
@@ -54,8 +51,13 @@ internal static class DependencyInjection
|
||||
CultureOptions cultureOptions = serviceProvider.GetRequiredService<CultureOptions>();
|
||||
cultureOptions.SystemCulture = CultureInfo.CurrentCulture;
|
||||
|
||||
ILogger<CultureOptions> logger = serviceProvider.GetRequiredService<ILogger<CultureOptions>>();
|
||||
logger.LogDebug("System Culture: {System}", cultureOptions.SystemCulture);
|
||||
|
||||
CultureInfo cultureInfo = cultureOptions.CurrentCulture;
|
||||
|
||||
logger.LogDebug("Current Culture: {Current}", cultureInfo);
|
||||
|
||||
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
|
||||
CultureInfo.CurrentCulture = cultureInfo;
|
||||
@@ -66,6 +68,7 @@ internal static class DependencyInjection
|
||||
SH.Culture = cultureInfo;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeConsoleWindow(this IServiceProvider serviceProvider)
|
||||
{
|
||||
_ = serviceProvider.GetRequiredService<ConsoleWindowLifeTime>();
|
||||
|
||||
@@ -47,17 +47,13 @@ internal static class IocConfiguration
|
||||
{
|
||||
if (context.Database.GetPendingMigrations().Any())
|
||||
{
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.WriteLine("[Database] Performing AppDbContext Migrations");
|
||||
#endif
|
||||
context.Database.Migrate();
|
||||
}
|
||||
}
|
||||
|
||||
builder
|
||||
#if DEBUG
|
||||
.EnableSensitiveDataLogging()
|
||||
#endif
|
||||
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
|
||||
.UseSqlite(sqlConnectionString);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
|
||||
{
|
||||
HttpClientHandler clientHandler = (HttpClientHandler)handler;
|
||||
clientHandler.AllowAutoRedirect = true;
|
||||
clientHandler.UseProxy = true;
|
||||
clientHandler.Proxy = provider.GetRequiredService<DynamicHttpProxy>();
|
||||
});
|
||||
|
||||
@@ -17,6 +17,6 @@ internal readonly struct MeasureExecutionToken : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
logger.LogInformation("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 数据库损坏异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal sealed class DatabaseCorruptedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -23,8 +23,16 @@ internal sealed partial class ExceptionRecorder
|
||||
public void Record(Application app)
|
||||
{
|
||||
app.UnhandledException += OnAppUnhandledException;
|
||||
app.DebugSettings.FailFastOnErrors = false;
|
||||
|
||||
app.DebugSettings.IsBindingTracingEnabled = true;
|
||||
app.DebugSettings.BindingFailed += OnXamlBindingFailed;
|
||||
|
||||
app.DebugSettings.IsXamlResourceReferenceTracingEnabled = true;
|
||||
app.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
|
||||
|
||||
app.DebugSettings.LayoutCycleTracingLevel = LayoutCycleTracingLevel.High;
|
||||
app.DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA2012")]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Numerics;
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
internal sealed class HutaoException : Exception
|
||||
@@ -18,6 +20,12 @@ internal sealed class HutaoException : Exception
|
||||
|
||||
public HutaoExceptionKind Kind { get; private set; }
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException Throw(HutaoExceptionKind kind, string message, Exception? innerException = default)
|
||||
{
|
||||
throw new HutaoException(kind, message, innerException);
|
||||
}
|
||||
|
||||
public static void ThrowIf(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
|
||||
{
|
||||
if (condition)
|
||||
@@ -25,4 +33,16 @@ internal sealed class HutaoException : Exception
|
||||
throw new HutaoException(kind, message, innerException);
|
||||
}
|
||||
}
|
||||
|
||||
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
|
||||
}
|
||||
|
||||
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
|
||||
{
|
||||
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
|
||||
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,8 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
internal enum HutaoExceptionKind
|
||||
{
|
||||
None,
|
||||
ServiceTypeCastFailed,
|
||||
FileSystemCreateFileInsufficientPermissions,
|
||||
PrivateNamedPipeContentHashIncorrect,
|
||||
GachaStatisticsInvalidItemId,
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 用户的计算机中的某些设置不符合要求
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal sealed class RuntimeEnvironmentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[System.Diagnostics.StackTraceHidden]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
[DoesNotReturn]
|
||||
@@ -73,6 +74,15 @@ internal static class ThrowHelper
|
||||
throw new NotSupportedException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void NotSupportedIf(bool condition, string message)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
throw new NotSupportedException(message);
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 用户数据损坏异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal sealed class UserdataCorruptedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -29,17 +29,19 @@ internal sealed unsafe class LoopbackManager : ObservableObject
|
||||
INET_FIREWALL_APP_CONTAINER* pContainers = default;
|
||||
try
|
||||
{
|
||||
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
|
||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||
for (uint i = 0; i < acCount; i++)
|
||||
{
|
||||
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
|
||||
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
|
||||
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
|
||||
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
|
||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||
for (uint i = 0; i < acCount; i++)
|
||||
{
|
||||
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
|
||||
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
|
||||
break;
|
||||
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
|
||||
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
|
||||
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
|
||||
{
|
||||
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
|
||||
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,16 +51,18 @@ internal sealed unsafe class LoopbackManager : ObservableObject
|
||||
_ = NetworkIsolationFreeAppContainers(pContainers);
|
||||
}
|
||||
|
||||
WIN32_ERROR error2 = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
|
||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error2));
|
||||
for (uint i = 0; i < accCount; i++)
|
||||
{
|
||||
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
|
||||
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
|
||||
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
|
||||
WIN32_ERROR error = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
|
||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||
for (uint i = 0; i < accCount; i++)
|
||||
{
|
||||
IsLoopbackEnabled = true;
|
||||
break;
|
||||
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
|
||||
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
|
||||
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
|
||||
{
|
||||
IsLoopbackEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ internal readonly struct TempFile : IDisposable
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
ThrowHelper.RuntimeEnvironment(SH.CoreIOTempFileCreateFail, ex);
|
||||
HutaoException.Throw(HutaoExceptionKind.FileSystemCreateFileInsufficientPermissions, SH.CoreIOTempFileCreateFail, ex);
|
||||
}
|
||||
|
||||
if (delete)
|
||||
|
||||
@@ -148,17 +148,15 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.As<IMetadataServiceInitialization>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget();
|
||||
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
|
||||
{
|
||||
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IHutaoUserService>()
|
||||
.As<IHutaoUserServiceInitialization>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget();
|
||||
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
|
||||
{
|
||||
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IDiscordService>()
|
||||
|
||||
@@ -49,7 +49,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
{
|
||||
byte[] content = new byte[header->ContentLength];
|
||||
serverStream.ReadAtLeast(content, header->ContentLength, false);
|
||||
ThrowHelper.InvalidDataIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
|
||||
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, HutaoExceptionKind.PrivateNamedPipeContentHashIncorrect, "PipePacket Content Hash incorrect");
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
@@ -9,6 +8,7 @@ using System.IO;
|
||||
using System.Security.Principal;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
@@ -88,7 +88,7 @@ internal sealed class RuntimeOptions
|
||||
|
||||
private bool isToastAvailable;
|
||||
private bool isToastAvailableInitialized;
|
||||
private object locker = new();
|
||||
private object isToastAvailableLock = new();
|
||||
|
||||
public RuntimeOptions(IServiceProvider serviceProvider, ILogger<RuntimeOptions> logger)
|
||||
{
|
||||
@@ -121,13 +121,13 @@ internal sealed class RuntimeOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return LazyInitializer.EnsureInitialized(ref isToastAvailable, ref isToastAvailableInitialized, ref locker, () =>
|
||||
return LazyInitializer.EnsureInitialized(ref isToastAvailable, ref isToastAvailableInitialized, ref isToastAvailableLock, GetIsToastAvailable);
|
||||
|
||||
bool GetIsToastAvailable()
|
||||
{
|
||||
return serviceProvider.GetRequiredService<ITaskContext>().InvokeOnMainThread(() =>
|
||||
{
|
||||
return ToastNotificationManagerCompat.CreateToastNotifier().Setting is Windows.UI.Notifications.NotificationSetting.Enabled;
|
||||
});
|
||||
});
|
||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
return taskContext.InvokeOnMainThread(() => ToastNotificationManager.CreateToastNotifier().Setting is NotificationSetting.Enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,9 @@ internal static class RuntimeOptionsExtension
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "ServerCache");
|
||||
}
|
||||
|
||||
public static string GetDataFolderBackgroundFolder(this RuntimeOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "Background");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Collections.Frozen;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
@@ -14,25 +15,25 @@ internal static class TypeNameHelper
|
||||
{
|
||||
private const char DefaultNestedTypeDelimiter = '+';
|
||||
|
||||
private static readonly Dictionary<Type, string> BuiltInTypeNames = new()
|
||||
{
|
||||
{ typeof(void), "void" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
};
|
||||
private static readonly FrozenDictionary<Type, string> BuiltInTypeNames = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(typeof(void), "void"),
|
||||
KeyValuePair.Create(typeof(bool), "bool"),
|
||||
KeyValuePair.Create(typeof(byte), "byte"),
|
||||
KeyValuePair.Create(typeof(char), "char"),
|
||||
KeyValuePair.Create(typeof(decimal), "decimal"),
|
||||
KeyValuePair.Create(typeof(double), "double"),
|
||||
KeyValuePair.Create(typeof(float), "float"),
|
||||
KeyValuePair.Create(typeof(int), "int"),
|
||||
KeyValuePair.Create(typeof(long), "long"),
|
||||
KeyValuePair.Create(typeof(object), "object"),
|
||||
KeyValuePair.Create(typeof(sbyte), "sbyte"),
|
||||
KeyValuePair.Create(typeof(short), "short"),
|
||||
KeyValuePair.Create(typeof(string), "string"),
|
||||
KeyValuePair.Create(typeof(uint), "uint"),
|
||||
KeyValuePair.Create(typeof(ulong), "ulong"),
|
||||
KeyValuePair.Create(typeof(ushort), "ushort"),
|
||||
]);
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象类型的显示名称
|
||||
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
|
||||
using System.Text;
|
||||
@@ -17,6 +18,7 @@ namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
internal sealed class HotKeyCombination : ObservableObject
|
||||
{
|
||||
private readonly ICurrentWindowReference currentWindowReference;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
|
||||
private readonly string settingKey;
|
||||
@@ -37,6 +39,7 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
public HotKeyCombination(IServiceProvider serviceProvider, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey)
|
||||
{
|
||||
currentWindowReference = serviceProvider.GetRequiredService<ICurrentWindowReference>();
|
||||
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
this.settingKey = settingKey;
|
||||
@@ -186,6 +189,12 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
HWND hwnd = currentWindowReference.GetWindowHandle();
|
||||
BOOL result = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key);
|
||||
registered = result;
|
||||
|
||||
if (!result)
|
||||
{
|
||||
infoBarService.Warning(SH.FormatCoreWindowHotkeyCombinationRegisterFailed(SH.ViewPageSettingKeyShortcutAutoClickingHeader, DisplayName));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,9 +69,12 @@ internal sealed class WindowController
|
||||
window.Activate();
|
||||
options.BringToForeground();
|
||||
|
||||
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
||||
UpdateSystemBackdrop(appOptions.BackdropType);
|
||||
appOptions.PropertyChanged += OnOptionsPropertyChanged;
|
||||
if (options.UseSystemBackdrop)
|
||||
{
|
||||
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
||||
UpdateSystemBackdrop(appOptions.BackdropType);
|
||||
appOptions.PropertyChanged += OnOptionsPropertyChanged;
|
||||
}
|
||||
|
||||
subclass.Initialize();
|
||||
|
||||
|
||||
@@ -41,25 +41,21 @@ internal readonly struct WindowOptions
|
||||
/// </summary>
|
||||
public readonly bool PersistSize;
|
||||
|
||||
public readonly bool UseSystemBackdrop;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用 Win UI 3 自带的拓展标题栏实现
|
||||
/// </summary>
|
||||
public readonly bool UseLegacyDragBarImplementation = !AppWindowTitleBar.IsCustomizationSupported();
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的窗体选项
|
||||
/// </summary>
|
||||
/// <param name="window">窗体</param>
|
||||
/// <param name="titleBar">标题栏</param>
|
||||
/// <param name="initSize">初始尺寸</param>
|
||||
/// <param name="persistSize">持久化尺寸</param>
|
||||
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false)
|
||||
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false, bool useSystemBackdrop = true)
|
||||
{
|
||||
Hwnd = WindowNative.GetWindowHandle(window);
|
||||
InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id);
|
||||
TitleBar = titleBar;
|
||||
InitSize = initSize;
|
||||
PersistSize = persistSize;
|
||||
UseSystemBackdrop = useSystemBackdrop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,20 +18,11 @@ internal static class DateTimeOffsetExtension
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try
|
||||
return value switch
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(value);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(value);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
>= -62135596800 and <= 253402300799 => DateTimeOffset.FromUnixTimeSeconds(value),
|
||||
>= -62135596800000 and <= 253402300799999 => DateTimeOffset.FromUnixTimeMilliseconds(value),
|
||||
_ => defaultValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,12 @@ namespace Snap.Hutao.Extension;
|
||||
[HighQuality]
|
||||
internal static partial class EnumerableExtension
|
||||
{
|
||||
public static void Deconstruct<TKey, TElement>(this IGrouping<TKey, TElement> grouping, out TKey key, out IEnumerable<TElement> elements)
|
||||
{
|
||||
key = grouping.Key;
|
||||
elements = grouping;
|
||||
}
|
||||
|
||||
public static TElement? ElementAtOrLastOrDefault<TElement>(this IEnumerable<TElement> source, int index)
|
||||
{
|
||||
return source.ElementAtOrDefault(index) ?? source.LastOrDefault();
|
||||
@@ -46,34 +52,6 @@ internal static partial class EnumerableExtension
|
||||
return first;
|
||||
}
|
||||
|
||||
public static string JoinToString<T>(this IEnumerable<T> source, char separator, Action<StringBuilder, T> selector)
|
||||
{
|
||||
StringBuilder resultBuilder = new();
|
||||
|
||||
IEnumerator<T> enumerator = source.GetEnumerator();
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
T first = enumerator.Current;
|
||||
selector(resultBuilder, first);
|
||||
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
resultBuilder.Append(separator);
|
||||
selector(resultBuilder, enumerator.Current);
|
||||
}
|
||||
while (enumerator.MoveNext());
|
||||
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
|
||||
public static string JoinToString<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, char separator, Action<StringBuilder, TKey, TValue> selector)
|
||||
{
|
||||
StringBuilder resultBuilder = new();
|
||||
|
||||
@@ -41,6 +41,30 @@
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "FurnitureId",
|
||||
"Documentation": "家具 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "FurnitureMakeId",
|
||||
"Documentation": "家具配方 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "FurnitureSuiteId",
|
||||
"Documentation": "家具套装 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "FurnitureTypeId",
|
||||
"Documentation": "家具分类 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "Level",
|
||||
"Documentation": "等级 1 - 90",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
internal sealed class BackgroundImageTypeChangedMessage;
|
||||
627
src/Snap.Hutao/Snap.Hutao/Migrations/20240219020258_AddPerferredUidOnUser.Designer.cs
generated
Normal file
627
src/Snap.Hutao/Snap.Hutao/Migrations/20240219020258_AddPerferredUidOnUser.Designer.cs
generated
Normal file
@@ -0,0 +1,627 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20240219020258_AddPerferredUidOnUser")]
|
||||
partial class AddPerferredUidOnUser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CalculatorRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("GameRecordRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ShowcaseRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("cultivate_entries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("AvatarLevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("AvatarLevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("EntryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("SkillALevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillALevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillELevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillELevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillQLevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillQLevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("WeaponLevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("WeaponLevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("EntryId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("cultivate_entry_level_informations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("EntryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("EntryId");
|
||||
|
||||
b.ToTable("cultivate_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachedUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("cultivate_projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("RefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppendPropIdList")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MainPropId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_reliquaries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PromoteLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_weapons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpireTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("object_cache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("ScheduleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SpiralAbyss")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("spiral_abysses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Fingerprint")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsOversea")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Ltoken");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Stoken");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
|
||||
.WithOne("LevelInformation")
|
||||
.HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.Navigation("LevelInformation");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPerferredUidOnUser : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PreferredUid",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PreferredUid",
|
||||
table: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
@@ -503,6 +503,9 @@ namespace Snap.Hutao.Migrations
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Stoken");
|
||||
|
||||
@@ -11,10 +11,16 @@ internal static class CollectionsNameValue
|
||||
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(x.ToString(), x))];
|
||||
}
|
||||
|
||||
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> codiction)
|
||||
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> condition)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return [.. Enum.GetValues<TEnum>().Where(codiction).Select(x => new NameValue<TEnum>(x.ToString(), x))];
|
||||
return [.. Enum.GetValues<TEnum>().Where(condition).Select(x => new NameValue<TEnum>(x.ToString(), x))];
|
||||
}
|
||||
|
||||
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, string> nameSelector)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(nameSelector(x), x))];
|
||||
}
|
||||
|
||||
public static List<NameValue<TSource>> From<TSource>(IEnumerable<TSource> sources, Func<TSource, string> nameSelector)
|
||||
|
||||
@@ -42,14 +42,14 @@ internal sealed partial class GachaItem
|
||||
/// <summary>
|
||||
/// 祈愿记录分类
|
||||
/// </summary>
|
||||
public GachaConfigType GachaType { get; set; }
|
||||
public GachaType GachaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录查询分类
|
||||
/// 合并保底的卡池使用此属性
|
||||
/// 仅4种(不含400)
|
||||
/// </summary>
|
||||
public GachaConfigType QueryType { get; set; }
|
||||
public GachaType QueryType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品Id
|
||||
|
||||
@@ -13,6 +13,7 @@ internal sealed partial class SettingEntry
|
||||
public const string Culture = "Culture";
|
||||
|
||||
public const string SystemBackdropType = "SystemBackdropType";
|
||||
public const string BackgroundImageType = "BackgroundImageType";
|
||||
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
|
||||
@@ -42,6 +43,7 @@ internal sealed partial class SettingEntry
|
||||
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
|
||||
public const string LaunchIsWindowsHDREnabled = "Launch.IsWindowsHDREnabled";
|
||||
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
|
||||
public const string LaunchUseBetterGenshinImpactAutomation = "Launch.UseBetterGenshinImpactAutomation";
|
||||
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
|
||||
|
||||
[Obsolete("不再支持多开")]
|
||||
@@ -49,4 +51,4 @@ internal sealed partial class SettingEntry
|
||||
|
||||
[Obsolete("不再使用 PowerShell")]
|
||||
public const string PowerShellPath = "PowerShellPath";
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,8 @@ internal sealed class User : ISelectable, IReorderable, IMappingFrom<User, Cooki
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public string? PreferredUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的用户
|
||||
/// </summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ internal sealed class UIGFItem : GachaLogItem, IMappingFrom<UIGFItem, GachaItem,
|
||||
/// </summary>
|
||||
[JsonPropertyName("uigf_gacha_type")]
|
||||
[JsonEnum(JsonSerializeType.NumberString)]
|
||||
public GachaConfigType UIGFGachaType { get; set; } = default!;
|
||||
public GachaType UIGFGachaType { get; set; } = default!;
|
||||
|
||||
public static UIGFItem From(GachaItem item, INameQuality nameQuality)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
internal enum FurnitureDeploySurfaceType
|
||||
{
|
||||
Ground = 0,
|
||||
Wall = 1,
|
||||
Ceil = 2,
|
||||
StackObjPlane = 3,
|
||||
Door = 4,
|
||||
Chandelier = 5,
|
||||
Floor = 6,
|
||||
WallBody = 7,
|
||||
Carpet = 8,
|
||||
LegoRockery = 9,
|
||||
Stair = 10,
|
||||
NPC = 11,
|
||||
Animal = 12,
|
||||
Apartment = 13,
|
||||
FurnitureSuite = 14,
|
||||
Road = 15,
|
||||
Terrain = 16,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
internal enum FurnitureDeployType
|
||||
{
|
||||
Interior,
|
||||
Exterior,
|
||||
InteriorRoom,
|
||||
InteriorHall,
|
||||
Skybox,
|
||||
}
|
||||
14
src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/GroupRecordType.cs
Normal file
14
src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/GroupRecordType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
internal enum GroupRecordType
|
||||
{
|
||||
None,
|
||||
Racing,
|
||||
Balloon,
|
||||
Stake,
|
||||
Seek,
|
||||
Explosion,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
internal enum SpecialFurnitureType
|
||||
{
|
||||
NormalFurniture,
|
||||
BlockDependent,
|
||||
FarmField,
|
||||
TeleportPoint,
|
||||
Fishpond,
|
||||
Npc,
|
||||
Apartment,
|
||||
FurnitureSuite,
|
||||
Paimon,
|
||||
Fish,
|
||||
CustomBaseFurniture,
|
||||
CustomNodeFurniture,
|
||||
VirtualFurniture,
|
||||
GroupFurniture,
|
||||
CoopPictureFrame,
|
||||
ChangeBgmFurniture,
|
||||
ServerGadget,
|
||||
Fishtank,
|
||||
}
|
||||
@@ -8,15 +8,4 @@ namespace Snap.Hutao.Model.Metadata.Achievement;
|
||||
/// <summary>
|
||||
/// 奖励
|
||||
/// </summary>
|
||||
internal sealed class Reward
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public MaterialId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数量
|
||||
/// </summary>
|
||||
public uint Count { get; set; }
|
||||
}
|
||||
internal sealed class Reward : IdCount;
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
internal sealed class AssociationTypeIconConverter : ValueConverter<AssociationType, Uri?>
|
||||
{
|
||||
private static readonly FrozenDictionary<string, AssociationType> LocalizedNameToAssociationType = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeMondstadt, AssociationType.ASSOC_TYPE_MONDSTADT),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeLiyue, AssociationType.ASSOC_TYPE_LIYUE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFatui, AssociationType.ASSOC_TYPE_FATUI),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeInazuma, AssociationType.ASSOC_TYPE_INAZUMA),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeRanger, AssociationType.ASSOC_TYPE_RANGER),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSumeru, AssociationType.ASSOC_TYPE_SUMERU),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFontaine, AssociationType.ASSOC_TYPE_FONTAINE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeNatlan, AssociationType.ASSOC_TYPE_NATLAN),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSnezhnaya, AssociationType.ASSOC_TYPE_SNEZHNAYA),
|
||||
]);
|
||||
|
||||
public static Uri? AssociationTypeNameToIconUri(string associationTypeName)
|
||||
{
|
||||
return AssociationTypeToIconUri(LocalizedNameToAssociationType.GetValueOrDefault(associationTypeName));
|
||||
}
|
||||
|
||||
public static Uri? AssociationTypeToIconUri(AssociationType type)
|
||||
{
|
||||
string? association = type switch
|
||||
{
|
||||
AssociationType.ASSOC_TYPE_MONDSTADT => "Mengde",
|
||||
AssociationType.ASSOC_TYPE_LIYUE => "Liyue",
|
||||
AssociationType.ASSOC_TYPE_FATUI => default,
|
||||
AssociationType.ASSOC_TYPE_INAZUMA => "Inazuma",
|
||||
AssociationType.ASSOC_TYPE_RANGER => default,
|
||||
AssociationType.ASSOC_TYPE_SUMERU => "Sumeru",
|
||||
AssociationType.ASSOC_TYPE_FONTAINE => "Fontaine",
|
||||
AssociationType.ASSOC_TYPE_NATLAN => default,
|
||||
AssociationType.ASSOC_TYPE_SNEZHNAYA => default,
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
|
||||
return association is null
|
||||
? default
|
||||
: Web.HutaoEndpoints.StaticRaw("ChapterIcon", $"UI_ChapterIcon_{association}.png").ToUri();
|
||||
}
|
||||
|
||||
public override Uri? Convert(AssociationType from)
|
||||
{
|
||||
return AssociationTypeToIconUri(from);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Control.Text;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
@@ -12,27 +13,27 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
[HighQuality]
|
||||
internal sealed class ElementNameIconConverter : ValueConverter<string, Uri>
|
||||
{
|
||||
private static readonly Dictionary<string, string> LocalizedNameToElementIconName = new()
|
||||
{
|
||||
[SH.ModelIntrinsicElementNameElec] = "Electric",
|
||||
[SH.ModelIntrinsicElementNameFire] = "Fire",
|
||||
[SH.ModelIntrinsicElementNameGrass] = "Grass",
|
||||
[SH.ModelIntrinsicElementNameIce] = "Ice",
|
||||
[SH.ModelIntrinsicElementNameRock] = "Rock",
|
||||
[SH.ModelIntrinsicElementNameWater] = "Water",
|
||||
[SH.ModelIntrinsicElementNameWind] = "Wind",
|
||||
};
|
||||
private static readonly FrozenDictionary<string, string> LocalizedNameToElementIconName = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameElec, "Electric"),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameFire, "Fire"),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameGrass, "Grass"),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameIce, "Ice"),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameRock, "Rock"),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameWater, "Water"),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameWind, "Wind"),
|
||||
]);
|
||||
|
||||
private static readonly Dictionary<string, ElementType> LocalizedNameToElementType = new()
|
||||
{
|
||||
[SH.ModelIntrinsicElementNameElec] = ElementType.Electric,
|
||||
[SH.ModelIntrinsicElementNameFire] = ElementType.Fire,
|
||||
[SH.ModelIntrinsicElementNameGrass] = ElementType.Grass,
|
||||
[SH.ModelIntrinsicElementNameIce] = ElementType.Ice,
|
||||
[SH.ModelIntrinsicElementNameRock] = ElementType.Rock,
|
||||
[SH.ModelIntrinsicElementNameWater] = ElementType.Water,
|
||||
[SH.ModelIntrinsicElementNameWind] = ElementType.Wind,
|
||||
};
|
||||
private static readonly FrozenDictionary<string, ElementType> LocalizedNameToElementType = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameElec, ElementType.Electric),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameFire, ElementType.Fire),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameGrass, ElementType.Grass),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameIce, ElementType.Ice),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameRock, ElementType.Rock),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameWater, ElementType.Water),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicElementNameWind, ElementType.Wind),
|
||||
]);
|
||||
|
||||
/// <summary>
|
||||
/// 将中文元素名称转换为图标链接
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
using Microsoft.UI;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Control.Theme;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Win32;
|
||||
using System.Collections.Frozen;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
@@ -15,17 +16,39 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
[HighQuality]
|
||||
internal sealed class QualityColorConverter : ValueConverter<QualityType, Color>
|
||||
{
|
||||
private static readonly FrozenDictionary<string, QualityType> LocalizedNameToQualityType = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(SH.ModelIntrinsicItemQualityWhite, QualityType.QUALITY_WHITE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicItemQualityGreen, QualityType.QUALITY_GREEN),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicItemQualityBlue, QualityType.QUALITY_BLUE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicItemQualityPurple, QualityType.QUALITY_PURPLE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicItemQualityOrange, QualityType.QUALITY_ORANGE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicItemQualityRed, QualityType.QUALITY_ORANGE_SP),
|
||||
]);
|
||||
|
||||
private static readonly FrozenDictionary<QualityType, Color> QualityTypeToColor = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(QualityType.QUALITY_WHITE, KnownColors.White),
|
||||
KeyValuePair.Create(QualityType.QUALITY_GREEN, KnownColors.Green),
|
||||
KeyValuePair.Create(QualityType.QUALITY_BLUE, KnownColors.Blue),
|
||||
KeyValuePair.Create(QualityType.QUALITY_PURPLE, KnownColors.Purple),
|
||||
KeyValuePair.Create(QualityType.QUALITY_ORANGE, KnownColors.Orange),
|
||||
KeyValuePair.Create(QualityType.QUALITY_ORANGE_SP, KnownColors.Orange),
|
||||
]);
|
||||
|
||||
public static Color QualityNameToColor(string qualityName)
|
||||
{
|
||||
return QualityToColor(LocalizedNameToQualityType.GetValueOrDefault(qualityName));
|
||||
}
|
||||
|
||||
public static Color QualityToColor(QualityType quality)
|
||||
{
|
||||
return QualityTypeToColor.GetValueOrDefault(quality, Colors.Transparent);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Color Convert(QualityType from)
|
||||
{
|
||||
return from switch
|
||||
{
|
||||
QualityType.QUALITY_WHITE => StructMarshal.Color(0xFF72778B),
|
||||
QualityType.QUALITY_GREEN => StructMarshal.Color(0xFF2A8F72),
|
||||
QualityType.QUALITY_BLUE => StructMarshal.Color(0xFF5180CB),
|
||||
QualityType.QUALITY_PURPLE => StructMarshal.Color(0xFFA156E0),
|
||||
QualityType.QUALITY_ORANGE or QualityType.QUALITY_ORANGE_SP => StructMarshal.Color(0xFFBC6932),
|
||||
_ => Colors.Transparent,
|
||||
};
|
||||
return QualityToColor(from);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
@@ -12,6 +13,20 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
[HighQuality]
|
||||
internal sealed class WeaponTypeIconConverter : ValueConverter<WeaponType, Uri>
|
||||
{
|
||||
private static readonly FrozenDictionary<string, WeaponType> LocalizedNameToWeaponType = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeSwordOneHand, WeaponType.WEAPON_SWORD_ONE_HAND),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeBow, WeaponType.WEAPON_BOW),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypePole, WeaponType.WEAPON_POLE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeClaymore, WeaponType.WEAPON_CLAYMORE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeCatalyst, WeaponType.WEAPON_CATALYST),
|
||||
]);
|
||||
|
||||
public static Uri WeaponTypeNameToIconUri(string weaponTypeName)
|
||||
{
|
||||
return WeaponTypeToIconUri(LocalizedNameToWeaponType.GetValueOrDefault(weaponTypeName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将武器类型转换为图标链接
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user