Compare commits

..

112 Commits

Author SHA1 Message Date
qhy040404
3b86783493 migrate to TokenizingTextBox 2024-02-23 18:08:30 +08:00
qhy040404
e3adc2e595 finish suggestion methods 2024-02-23 14:10:09 +08:00
Lightczx
c17798a8c9 code style 2024-02-23 10:22:24 +08:00
DismissedLight
fa9b39d134 Merge pull request #1412 from DGP-Studio/fix/hotkey_notify 2024-02-23 09:42:10 +08:00
qhy040404
8dc85c7a25 prompt if hotkey not registered 2024-02-23 09:39:50 +08:00
DismissedLight
5845c718e1 Merge pull request #1419 from DGP-Studio/feat/real_time_bg 2024-02-23 09:36:25 +08:00
Lightczx
0c093851ed code style 2024-02-23 09:33:55 +08:00
qhy040404
04c187aa16 real time refresh background 2024-02-22 22:34:12 +08:00
Lightczx
25cf9f5356 code style 2024-02-22 10:26:43 +08:00
DismissedLight
c30c8b114a re-enable backdrop 2024-02-21 23:38:57 +08:00
DismissedLight
41f5a04e5a Merge branch 'main' into develop 2024-02-21 20:35:38 +08:00
DismissedLight
4f26d6efc2 update package version 2024-02-21 20:33:18 +08:00
Masterain
0d6b5e595e New Crowdin updates (#1413) 2024-02-21 20:29:41 +08:00
DismissedLight
e30bebd1a3 Update StatisticsCard.xaml 2024-02-21 20:25:56 +08:00
Masterain
ffb34c0e93 New Crowdin updates (#1411) 2024-02-21 20:25:12 +08:00
Lightczx
35b35ab649 minor fix 5 2024-02-21 16:29:30 +08:00
Lightczx
f067350cd8 minor fix 2024-02-21 15:28:05 +08:00
Lightczx
14648dafe8 adjust language order 2024-02-21 14:04:24 +08:00
Lightczx
7a7d81cfee add static resource hint for more situation 2024-02-21 13:55:07 +08:00
Lightczx
30c055e7ba impl #1373 #1389 2024-02-21 11:57:41 +08:00
qhy040404
6a922d9db6 更新 alpha.yml 2024-02-21 09:52:20 +08:00
DismissedLight
02a6e64a8c Merge pull request #1408 from DGP-Studio/fix/launch 2024-02-20 14:40:57 +08:00
qhy040404
51c4e66472 reorder launch pipeline 2024-02-20 14:37:29 +08:00
Lightczx
48875195bf add wallpaper api 2024-02-20 14:25:48 +08:00
qhy040404
a179e0e838 Update build.cake (#1405) 2024-02-20 11:54:15 +08:00
qhy040404
00c3e94e97 Update alpha.yml 2024-02-20 11:27:14 +08:00
qhy040404
eec7224c07 Update alpha.yml 2024-02-20 11:27:13 +08:00
qhy040404
85bcf37b1b Update alpha.yml 2024-02-20 10:43:27 +08:00
qhy040404
465c7035c0 Update alpha.yml 2024-02-20 10:31:23 +08:00
DismissedLight
11620816ec Merge pull request #1403 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-a995a009f5 2024-02-20 09:07:19 +08:00
dependabot[bot]
c5b75d6f82 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [MSTest.TestAdapter](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.0 to 3.2.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 09:59:22 +00:00
qhy040404
6241bbc997 Setup dependabot for actions 2024-02-19 17:57:17 +08:00
qhy040404
afbd8ec6ad Revert "Update alpha.yml"
This reverts commit 288cc841ac.
2024-02-19 17:41:32 +08:00
qhy040404
288cc841ac Update alpha.yml 2024-02-19 17:26:00 +08:00
Lightczx
a9295c0a37 impl #1388 2024-02-19 16:58:55 +08:00
DismissedLight
29cd690032 Merge pull request #1402 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-f23264728b 2024-02-19 15:45:00 +08:00
dependabot[bot]
78d8539ae2 Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx) and [MSTest.TestFramework](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.0 to 3.2.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

Updates `MSTest.TestFramework` from 3.2.0 to 3.2.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:44:34 +00:00
Lightczx
7dec87586b fix #1233 2024-02-19 11:33:27 +08:00
Lightczx
d169f355f3 impl #1233 2024-02-19 10:33:25 +08:00
qhy040404
e1784e2078 Update issue templates 2024-02-18 21:16:44 +08:00
Lightczx
32b1b698df update logging message 2024-02-18 17:30:24 +08:00
Lightczx
d6c7df1593 update gacha statistics card style 2024-02-18 15:30:05 +08:00
Lightczx
904fdf7fc9 fix #1391 2024-02-18 09:43:51 +08:00
Lightczx
b3e4ebb5d3 bump nuget package version 2024-02-18 09:33:11 +08:00
DismissedLight
09b9af4575 Merge pull request #1401 from DGP-Studio/fix/1400
fix #1400
2024-02-18 09:18:48 +08:00
Lightczx
8930548f44 code style 2024-02-18 09:19:09 +08:00
DismissedLight
34dbcc6f5d Merge pull request #1392 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dd5dd83d2f 2024-02-18 09:00:11 +08:00
qhy040404
75c25cec53 show warning if discord is not available 2024-02-17 14:00:23 +08:00
qhy040404
22251ca937 fix #1400 2024-02-17 11:28:36 +08:00
qhy040404
57c9531db8 fix #1365 again 2024-02-16 22:19:55 +08:00
dependabot[bot]
fd73743159 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).


Updates `Microsoft.NET.Test.Sdk` from 17.8.0 to 17.9.0
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 07:47:05 +00:00
qhy040404
6594d9032d fix wrong setting state 2024-02-12 11:29:25 +08:00
DismissedLight
ae1b452697 minor fix 2024-02-09 15:40:48 +08:00
DismissedLight
bc3df782e4 Merge pull request #1380 from DGP-Studio/fix/1379
fix #1379
2024-02-09 14:16:39 +08:00
qhy040404
3ef117e683 fix #1379 2024-02-09 12:22:52 +08:00
qhy040404
8b1613d46f fix #1316 2 2024-02-09 09:23:10 +08:00
qhy040404
c2708aaedd minor fix 2024-02-09 09:19:30 +08:00
DismissedLight
db3de01eec impl #1375 2024-02-08 17:04:31 +08:00
qhy040404
635b52980b Update .gitlab-ci.yml 2024-02-07 21:13:59 +08:00
Lightczx
4988c1bd47 fixup image scale logic 2024-02-07 16:51:12 +08:00
Lightczx
fd499e372c fix #1316 2024-02-07 16:36:24 +08:00
Lightczx
a6c376cd44 fix background image switch crash 2024-02-07 16:24:56 +08:00
Lightczx
425b3789bf bump version 2024-02-07 15:08:53 +08:00
DismissedLight
a6986bc201 Merge pull request #1370 from DGP-Studio/develop
Co-authored-by: t0piy <t.tony.br01@gmail.com>
Co-authored-by: Tony <66571593+t0piy@users.noreply.github.com>
Co-authored-by: Masterain <i@irain.in>
Co-authored-by: qhy040404 <qhy040404@163.com>
Co-authored-by: qhy040404 <45379733+qhy040404@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Scighost <scighost@outlook.com>
2024-02-07 14:09:49 +08:00
Masterain
301a14611a New Crowdin updates (#1339) 2024-02-07 14:05:17 +08:00
Lightczx
59db7d968a clear multiple user selection & ignore same background 2024-02-07 13:34:55 +08:00
Lightczx
45f4b46e9e add default background 2024-02-07 12:02:38 +08:00
Lightczx
82b5d9b12a revert client convertion permission check for special folder 2024-02-07 10:33:08 +08:00
DismissedLight
6fb5cfe25d Merge branch 'main' into develop 2024-02-07 09:58:21 +08:00
Lightczx
1df78345c5 fix AdvancedCollectionView compare 2024-02-07 09:12:02 +08:00
DismissedLight
fd817af9c3 refine ui 2024-02-06 23:31:24 +08:00
Lightczx
3f6efe2e24 custom background image 2024-02-06 17:31:19 +08:00
DismissedLight
ef6d88ba2e Merge pull request #1366 from DGP-Studio/refine/1334 2024-02-06 11:16:58 +08:00
Lightczx
26748e5885 generic AdvancedCollectionView 2024-02-06 11:16:59 +08:00
DismissedLight
5f180846f6 refactor 2024-02-05 23:08:00 +08:00
DismissedLight
57ea6bb34e minor style 2024-02-05 22:03:08 +08:00
qhy040404
95024e4107 refine #1334 2024-02-05 21:47:52 +08:00
DismissedLight
678ec191a6 Merge pull request #1364 from DGP-Studio/feat/notification_permission_disabled 2024-02-05 17:26:11 +08:00
Lightczx
40b26d9d3c code style 2024-02-05 17:25:59 +08:00
qhy040404
e5677e7de4 detect notification permission
And prevent the crash caused by lack of permission when sending toast
2024-02-05 16:53:50 +08:00
Lightczx
9ae8b182e7 fix empty ui alignment 2024-02-05 16:43:29 +08:00
DismissedLight
02fe498cce Merge pull request #1363 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dd9cc865bd 2024-02-05 15:52:16 +08:00
Lightczx
f924f78ee2 code style 2024-02-05 15:52:00 +08:00
Lightczx
977e749939 rename style 2024-02-05 15:35:02 +08:00
dependabot[bot]
f8ad3b98fc Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [MSTest.TestAdapter](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.1.1 to 3.2.0
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v.3.2.0)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 07:13:34 +00:00
Lightczx
ee39f7fec5 refine all page ui 2024-02-05 13:55:16 +08:00
DismissedLight
c8c1d21c9b refine wiki avatar page 2024-02-04 23:29:01 +08:00
Lightczx
f06825f246 refine cultivation page 2024-02-04 11:23:52 +08:00
qhy040404
72395fdb89 say goodbye to azp 2024-02-04 11:20:36 +08:00
Lightczx
8954f7e325 support Asia length 10 uid 2024-02-04 09:30:07 +08:00
DismissedLight
976441de18 refine spiralabyss page 2024-02-03 23:13:25 +08:00
DismissedLight
60a49971f6 Merge pull request #1356 from DGP-Studio/feat/reorder 2024-02-03 21:50:20 +08:00
DismissedLight
fb5de92283 code style 2024-02-03 21:49:51 +08:00
qhy040404
046f3ace94 reorderable user 2024-02-03 17:28:50 +08:00
qhy040404
4c369ad0ad impl #1334 2024-02-03 17:28:50 +08:00
DismissedLight
7efaaae3e1 fix syntax tree parsing 2024-02-03 17:26:17 +08:00
Lightczx
446bdb2b49 syntax tree incompleted 2024-02-02 17:12:41 +08:00
Lightczx
20277b8b79 Add callconv on win32 apis 2024-02-02 10:38:40 +08:00
Lightczx
7baf125f88 Impl IReorderable 2024-02-02 10:03:46 +08:00
Lightczx
a4e782da78 refine #1316 2024-02-01 16:17:49 +08:00
Lightczx
d5551e5cdf fix #1316 2024-02-01 14:30:50 +08:00
Lightczx
f016a4a27f refine #1347 2024-02-01 09:42:55 +08:00
DismissedLight
8b931b6d89 Merge pull request #1349 from DGP-Studio/fix/1347
fix #1347
2024-02-01 09:23:40 +08:00
Masterain
31670953b0 Update issue_similarity.yml 2024-01-30 19:00:42 -08:00
Masterain
ae2415dbca Create issue_similarity.yml 2024-01-29 18:17:37 -08:00
Masterain
5b109013a0 add workflows for stale issues (#1330) 2024-01-28 01:40:54 -08:00
qhy040404
279e107919 Update bug report template (#1317)
* 更新 CHS-bug-report.yml

* 更新 ENG-bug-report.yml
2024-01-19 01:38:17 -08:00
DismissedLight
94a5e71130 Update translations (#1294)
Co-authored-by: Masterain <i@irain.in>
2024-01-11 19:20:34 +08:00
DismissedLight
07cdfcea28 Merge pull request #1291 from DGP-Studio/develop 2024-01-11 19:16:57 +08:00
Masterain
a93eb505d6 Update issue template 2024-01-06 22:56:17 -08:00
DismissedLight
aa46b6531b Merge pull request #1264 from DGP-Studio/Masterain98-patch-1 2024-01-04 09:37:08 +08:00
Masterain
b36399f572 Update .gitlab-ci.yml 2024-01-03 17:36:45 -08:00
234 changed files with 8009 additions and 2644 deletions

View File

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

View File

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

View File

@@ -13,4 +13,8 @@ updates:
groups:
packages:
patterns:
- "*"
- "*"
- package-ecosystem: "github-actions"
directory: "/.github/workflows" # GitHub Workflows
schedule:
interval: "weekly"

View File

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

View 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

View File

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

View File

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

View File

@@ -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");
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
@@ -12,9 +12,9 @@
<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="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -23,6 +23,7 @@
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/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"/>

View File

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

View File

@@ -0,0 +1,89 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
[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;
}
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(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);
}
}
}

View File

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

View File

@@ -9,5 +9,5 @@ internal interface IColorSegment
{
Color Color { get; }
double Value { get; }
double Value { get; set; }
}

View File

@@ -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, });
}
}
}

View File

@@ -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();
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
internal interface IXamlElementAccessor;

View File

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

View File

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

View File

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

View File

@@ -46,13 +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)
{
// RRGGBBAA -> AABBGGRR
// uint layout: 0xRRGGBBAA is AABBGGRR
// AABBGGRR -> RRGGBBAA
fixed (Rgba32* pSelf = &this)
{
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(code);
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(xrgbaCode);
}
}
@@ -66,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;
@@ -84,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;
@@ -141,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;
@@ -174,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;

View File

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

View File

@@ -41,12 +41,14 @@ internal class ScopedPage : Page
/// 应当在 InitializeComponent() 前调用
/// </summary>
/// <typeparam name="TViewModel">视图模型类型</typeparam>
protected void InitializeWith<TViewModel>()
protected TViewModel InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
return (TViewModel)viewModel;
}
/// <inheritdoc/>

View File

@@ -7,7 +7,9 @@ using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Control.Text.Syntax.MiHoYo;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Metadata;
using Windows.Foundation;
using Windows.UI;
@@ -15,20 +17,12 @@ namespace Snap.Hutao.Control.Text;
/// <summary>
/// 专用于呈现描述文本的文本块
/// Some part of this file came from:
/// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
/// </summary>
[HighQuality]
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
internal sealed partial class DescriptionTextBlock : ContentControl
{
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".Length;
private static readonly int ItalicTagFullLength = "<i></i>".Length;
private static readonly int ItalicTagLeftLength = "<i>".Length;
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
/// <summary>
@@ -51,9 +45,15 @@ internal sealed partial class DescriptionTextBlock : ContentControl
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
ReadOnlySpan<char> description = MetadataSpecialNames.Handle((string)e.NewValue);
UpdateDescription(textBlock, description);
try
{
UpdateDescription(textBlock, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle((string)e.NewValue)));
}
catch (Exception ex)
{
_ = ex;
}
}
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -62,101 +62,106 @@ internal sealed partial class DescriptionTextBlock : ContentControl
textBlock.Style = (Style)e.NewValue;
}
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
private static void UpdateDescription(TextBlock textBlock, MiHoYoSyntaxTree syntaxTree)
{
textBlock.Inlines.Clear();
AppendNode(textBlock, textBlock.Inlines, syntaxTree.Root);
}
int last = 0;
for (int i = 0; i < description.Length;)
private static void AppendNode(TextBlock textBlock, InlineCollection inlines, MiHoYoSyntaxNode node)
{
switch (node.Kind)
{
// newline
if (description[i..].StartsWith(@"\n"))
{
AppendText(textBlock, description[last..i]);
AppendLineBreak(textBlock);
i += 2;
last = i;
}
case MiHoYoSyntaxKind.Root:
foreach (MiHoYoSyntaxNode child in ((MiHoYoRootSyntax)node).Children)
{
AppendNode(textBlock, inlines, child);
}
// color tag
else if (description[i..].StartsWith("<c"))
{
AppendText(textBlock, description[last..i]);
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
i += length + ColorTagFullLength;
last = i;
}
// italic
else if (description[i..].StartsWith("<i"))
{
AppendText(textBlock, description[last..i]);
int length = description[(i + ItalicTagLeftLength)..].IndexOf('<');
AppendItalicText(textBlock, description.Slice(i + ItalicTagLeftLength, length));
i += length + ItalicTagFullLength;
last = i;
}
else
{
i += 1;
}
if (i == description.Length - 1)
{
AppendText(textBlock, description[last..(i + 1)]);
}
break;
case MiHoYoSyntaxKind.PlainText:
AppendPlainText(textBlock, inlines, (MiHoYoPlainTextSyntax)node);
break;
case MiHoYoSyntaxKind.ColorText:
AppendColorText(textBlock, inlines, (MiHoYoColorTextSyntax)node);
break;
case MiHoYoSyntaxKind.ItalicText:
AppendItalicText(textBlock, inlines, (MiHoYoItalicTextSyntax)node);
break;
}
}
private static void AppendText(TextBlock text, in ReadOnlySpan<char> slice)
private static void AppendPlainText(TextBlock textBlock, InlineCollection inlines, MiHoYoPlainTextSyntax plainText)
{
text.Inlines.Add(new Run { Text = slice.ToString() });
// PlainText doesn't have children
inlines.Add(new Run { Text = plainText.Span.ToString() });
}
private static void AppendColorText(TextBlock text, in ReadOnlySpan<char> slice, Rgba32 color)
private static void AppendColorText(TextBlock textBlock, InlineCollection inlines, MiHoYoColorTextSyntax colorText)
{
Rgba32 color = new(colorText.ColorSpan.ToString());
Color targetColor;
if (ThemeHelper.IsDarkMode(text.ActualTheme))
if (ThemeHelper.IsDarkMode(textBlock.ActualTheme))
{
targetColor = color;
}
else
{
// Make lighter in light mode
Hsl32 hsl = color.ToHsl();
Hsla32 hsl = color.ToHsl();
hsl.L *= 0.3;
targetColor = Rgba32.FromHsl(hsl);
}
text.Inlines.Add(new Run
if (colorText.Children.Count > 1)
{
Text = slice.ToString(),
Foreground = new SolidColorBrush(targetColor),
});
Span span = new()
{
Foreground = new SolidColorBrush(targetColor),
};
foreach (MiHoYoSyntaxNode child in colorText.Children)
{
AppendNode(textBlock, span.Inlines, child);
}
}
else
{
inlines.Add(new Run
{
Text = colorText.ContentSpan.ToString(),
Foreground = new SolidColorBrush(targetColor),
});
}
}
private static void AppendItalicText(TextBlock text, in ReadOnlySpan<char> slice)
private static void AppendItalicText(TextBlock textBlock, InlineCollection inlines, MiHoYoItalicTextSyntax italicText)
{
text.Inlines.Add(new Run
if (italicText.Children.Count > 1)
{
Text = slice.ToString(),
FontStyle = Windows.UI.Text.FontStyle.Italic,
});
}
Span span = new()
{
FontStyle = Windows.UI.Text.FontStyle.Italic,
};
private static void AppendLineBreak(TextBlock text)
{
text.Inlines.Add(new LineBreak());
foreach (MiHoYoSyntaxNode child in italicText.Children)
{
AppendNode(textBlock, span.Inlines, child);
}
}
else
{
inlines.Add(new Run
{
Text = italicText.ContentSpan.ToString(),
FontStyle = Windows.UI.Text.FontStyle.Italic,
});
}
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
// Simply re-apply texts
UpdateDescription((TextBlock)Content, Description);
UpdateDescription((TextBlock)Content, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle(Description)));
}
}

View File

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

View File

@@ -1,34 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text;
using System.Text.RegularExpressions;
namespace Snap.Hutao.Control.Text;
internal static partial class MetadataSpecialNames
{
public static string Handle(string input)
{
if (input.AsSpan()[0] is not '#')
{
return input;
}
StringBuilder resultBuilder = new(input);
resultBuilder
.Replace("{MATEAVATAR#SEXPRO[INFO_MALE_PRONOUN_BOYD|INFO_FEMALE_PRONOUN_GIRLD]}", SH.ControlTextMetadataSpecialNameMetaAvatarSexProD)
.Replace("{PLAYERAVATAR#SEXPRO[INFO_MALE_PRONOUN_HE|INFO_FEMALE_PRONOUN_SHE]}", SH.ControlTextMetadataSpecialNamePlayerAvatarSexPro)
.Replace("{REALNAME[ID(1)]}", SH.ControlTextMetadataSpecialNameRealNameId1);
input = resultBuilder.ToString();
// {M#.}{F#.}
input = MaleFemaleRegex().Replace(input, SH.ControlTextMetadataSpecialNameMaleFemale);
return input[1..];
}
[GeneratedRegex("\\{M#(.*?)\\}\\{F#(.*?)\\}")]
private static partial Regex MaleFemaleRegex();
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal enum MiHoYoColorKind
{
None,
Rgba,
Rgb,
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal sealed class MiHoYoColorTextSyntax : MiHoYoXmlElementSyntax
{
public MiHoYoColorTextSyntax(MiHoYoColorKind colorKind, string text, int start, int end)
: base(MiHoYoSyntaxKind.ColorText, text, start, end)
{
ColorKind = colorKind;
}
public MiHoYoColorTextSyntax(MiHoYoColorKind colorKind, string text, in TextPosition position)
: base(MiHoYoSyntaxKind.ColorText, text, position)
{
ColorKind = colorKind;
}
public MiHoYoColorKind ColorKind { get; }
public override TextPosition ContentPosition
{
get
{
return ColorKind switch
{
MiHoYoColorKind.Rgba => new(Position.Start + 17, Position.End - 8),
MiHoYoColorKind.Rgb => new(Position.Start + 15, Position.End - 8),
_ => throw Must.NeverHappen(),
};
}
}
public TextPosition ColorPosition
{
get
{
return ColorKind switch
{
MiHoYoColorKind.Rgba => new(Position.Start + 8, Position.Start + 16),
MiHoYoColorKind.Rgb => new(Position.Start + 8, Position.Start + 14),
_ => throw Must.NeverHappen(),
};
}
}
public ReadOnlySpan<char> ColorSpan { get => Text.AsSpan()[ColorPosition.Start..ColorPosition.End]; }
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal sealed class MiHoYoItalicTextSyntax : MiHoYoXmlElementSyntax
{
public MiHoYoItalicTextSyntax(string text, int start, int end)
: base(MiHoYoSyntaxKind.ItalicText, text, start, end)
{
}
public MiHoYoItalicTextSyntax(string text, in TextPosition position)
: base(MiHoYoSyntaxKind.ItalicText, text, position)
{
}
public override TextPosition ContentPosition { get => new(Position.Start + 3, Position.End - 4); }
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal sealed class MiHoYoPlainTextSyntax : MiHoYoSyntaxNode
{
public MiHoYoPlainTextSyntax(string text, int start, int end)
: base(MiHoYoSyntaxKind.PlainText, text, start, end)
{
}
public MiHoYoPlainTextSyntax(string text, in TextPosition position)
: base(MiHoYoSyntaxKind.PlainText, text, position)
{
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal sealed class MiHoYoRootSyntax : MiHoYoSyntaxNode
{
public MiHoYoRootSyntax(string text, int start, int end)
: base(MiHoYoSyntaxKind.Root, text, start, end)
{
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal enum MiHoYoSyntaxKind
{
None,
Root,
PlainText,
ColorText,
ItalicText,
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal abstract class MiHoYoSyntaxNode : SyntaxNode<MiHoYoSyntaxNode, MiHoYoSyntaxKind>
{
public MiHoYoSyntaxNode(MiHoYoSyntaxKind kind, string text, int start, int end)
: base(kind, text, start, end)
{
}
public MiHoYoSyntaxNode(MiHoYoSyntaxKind kind, string text, in TextPosition position)
: base(kind, text, position)
{
}
}

View File

@@ -0,0 +1,146 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal sealed class MiHoYoSyntaxTree
{
public MiHoYoSyntaxNode Root { get; set; } = default!;
public string Text { get; set; } = default!;
public static MiHoYoSyntaxTree Parse(string text)
{
MiHoYoRootSyntax root = new(text, 0, text.Length);
ParseComponents(text, root);
MiHoYoSyntaxTree tree = new()
{
Text = text,
Root = root,
};
return tree;
}
private static void ParseComponents(string text, MiHoYoSyntaxNode syntax)
{
TextPosition contentPosition = syntax switch
{
MiHoYoXmlElementSyntax xmlSyntax => xmlSyntax.ContentPosition,
_ => syntax.Position,
};
ReadOnlySpan<char> contentSpan = text.AsSpan().Slice(contentPosition.Start, contentPosition.Length);
int endOfProcessedAtContent = 0;
while (true)
{
if (endOfProcessedAtContent >= contentSpan.Length)
{
break;
}
int indexOfXmlLeftOpeningAtUnprocessedContent = contentSpan[endOfProcessedAtContent..].IndexOf('<');
// End of content
if (indexOfXmlLeftOpeningAtUnprocessedContent < 0)
{
TextPosition position = new(contentPosition.Start + endOfProcessedAtContent, contentPosition.End);
MiHoYoPlainTextSyntax plainText = new(text, position);
syntax.Children.Add(plainText);
break;
}
// We have plain text between xml elements
if (indexOfXmlLeftOpeningAtUnprocessedContent > 0)
{
TextPosition position = new(0, indexOfXmlLeftOpeningAtUnprocessedContent);
TextPosition positionAtContent = position.RightShift(endOfProcessedAtContent);
TextPosition positionAtText = positionAtContent.RightShift(contentPosition.Start);
MiHoYoPlainTextSyntax plainText = new(text, positionAtText);
syntax.Children.Add(plainText);
endOfProcessedAtContent = positionAtContent.End;
continue;
}
// Peek the next character after '<'
int indexOfXmlLeftOpeningAtContent = endOfProcessedAtContent + indexOfXmlLeftOpeningAtUnprocessedContent;
switch (contentSpan[indexOfXmlLeftOpeningAtContent + 1])
{
case 'c':
{
int endOfXmlColorRightClosingAtUnprocessedContent = EndOfXmlClosing(contentSpan[indexOfXmlLeftOpeningAtContent..], out int endOfXmlColorLeftClosingAtUnprocessedContent);
MiHoYoColorKind colorKind = endOfXmlColorLeftClosingAtUnprocessedContent switch
{
17 => MiHoYoColorKind.Rgba,
15 => MiHoYoColorKind.Rgb,
_ => throw Must.NeverHappen(),
};
TextPosition position = new(0, endOfXmlColorRightClosingAtUnprocessedContent);
TextPosition positionAtContent = position.RightShift(endOfProcessedAtContent);
TextPosition positionAtText = positionAtContent.RightShift(contentPosition.Start);
MiHoYoColorTextSyntax colorText = new(colorKind, text, positionAtText);
ParseComponents(text, colorText);
syntax.Children.Add(colorText);
endOfProcessedAtContent = positionAtContent.End;
break;
}
case 'i':
{
int endOfXmlItalicRightClosingAtUnprocessedContent = EndOfXmlClosing(contentSpan[indexOfXmlLeftOpeningAtContent..], out _);
TextPosition position = new(0, endOfXmlItalicRightClosingAtUnprocessedContent);
TextPosition positionAtContent = position.RightShift(endOfProcessedAtContent);
TextPosition positionAtText = positionAtContent.RightShift(contentPosition.Start);
MiHoYoItalicTextSyntax italicText = new(text, positionAtText);
ParseComponents(text, italicText);
syntax.Children.Add(italicText);
endOfProcessedAtContent = positionAtContent.End;
break;
}
}
}
}
private static int EndOfXmlClosing(in ReadOnlySpan<char> span, out int endOfLeftClosing)
{
endOfLeftClosing = 0;
int openingCount = 0;
int closingCount = 0;
int current = 0;
// Considering <i>text1</i>text2<i>text3</i>
// Considering <i>text1<span>text2</span>text3</i>
while (true)
{
int leftMarkIndex = span[current..].IndexOf('<');
if (span[current..][leftMarkIndex + 1] is '/')
{
closingCount++;
}
else
{
openingCount++;
}
current += span[current..].IndexOf('>') + 1;
if (openingCount is 1 && closingCount is 0)
{
endOfLeftClosing = current;
}
if (openingCount == closingCount)
{
return current;
}
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
internal abstract class MiHoYoXmlElementSyntax : MiHoYoSyntaxNode
{
public MiHoYoXmlElementSyntax(MiHoYoSyntaxKind kind, string text, int start, int end)
: base(kind, text, start, end)
{
}
public MiHoYoXmlElementSyntax(MiHoYoSyntaxKind kind, string text, in TextPosition position)
: base(kind, text, position)
{
}
public abstract TextPosition ContentPosition { get; }
public ReadOnlySpan<char> ContentSpan { get => Text.AsSpan(ContentPosition.Start, ContentPosition.Length); }
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Text.Syntax;
internal abstract class SyntaxNode<TSelf, TKind>
where TSelf : SyntaxNode<TSelf, TKind>
where TKind : struct, Enum
{
public SyntaxNode(TKind kind, string text, int start, int end)
{
Kind = kind;
Text = text;
Position = new(start, end);
}
public SyntaxNode(TKind kind, string text, in TextPosition position)
{
Kind = kind;
Text = text;
Position = position;
}
public TKind Kind { get; protected set; }
public List<TSelf> Children { get; } = [];
public TextPosition Position { get; protected set; }
public ReadOnlySpan<char> Span { get => Text.AsSpan().Slice(Position.Start, Position.Length); }
protected string Text { get; set; } = default!;
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics;
namespace Snap.Hutao.Control.Text.Syntax;
[DebuggerDisplay("[{Start}..{End}]")]
internal readonly struct TextPosition
{
public readonly int Start;
public readonly int End;
public TextPosition(int start, int end)
{
Start = start;
End = end;
}
public readonly int Length
{
get => End - Start;
}
public TextPosition LeftShift(int offset)
{
return new(Start - offset, End - offset);
}
public TextPosition RightShift(int offset)
{
return new(Start + offset, End + offset);
}
}

View File

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

View File

@@ -0,0 +1,14 @@
// 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);
}

View File

@@ -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="&#xE76C;"
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="&#xE76B;"
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="*"/>

View File

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

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

View File

@@ -34,5 +34,6 @@
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>
<x:String x:Key="UI_EmotionIcon272">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.png</x:String>
<x:String x:Key="UI_EmotionIcon293">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon293.png</x:String>
<x:String x:Key="UI_EmotionIcon433">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon433.png</x:String>
<x:String x:Key="UI_EmotionIcon445">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon445.png</x:String>
</ResourceDictionary>

View File

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

View File

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

View File

@@ -1,25 +1,29 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Database;
internal sealed class ObservableReorderableDbCollection<T> : ObservableCollection<T>
where T : class, IReorderable
internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCollection<TEntity>
where TEntity : class, IReorderable
{
private readonly DbContext dbContext;
private bool previousChangeIsRemoved;
private readonly IServiceProvider serviceProvider;
public ObservableReorderableDbCollection(List<T> items, DbContext dbContext)
: base(AdjustIndex(items))
public ObservableReorderableDbCollection(List<TEntity> items, IServiceProvider serviceProvider)
: base(AdjustIndex(items.SortBy(x => x.Index)))
{
this.dbContext = dbContext;
this.serviceProvider = serviceProvider;
}
public IAdvancedCollectionView? View { get; set; }
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
@@ -27,26 +31,18 @@ internal sealed class ObservableReorderableDbCollection<T> : ObservableCollectio
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
previousChangeIsRemoved = true;
break;
case NotifyCollectionChangedAction.Add:
if (!previousChangeIsRemoved)
{
return;
}
OnReorder();
previousChangeIsRemoved = false;
break;
}
}
private static List<T> AdjustIndex(List<T> list)
private static List<TEntity> AdjustIndex(List<TEntity> list)
{
Span<T> span = CollectionsMarshal.AsSpan(list);
Span<TEntity> span = CollectionsMarshal.AsSpan(list);
for (int i = 0; i < list.Count; i++)
{
ref readonly T item = ref span[i];
ref readonly TEntity item = ref span[i];
item.Index = i;
}
@@ -55,12 +51,79 @@ internal sealed class ObservableReorderableDbCollection<T> : ObservableCollectio
private void OnReorder()
{
AdjustIndex((List<T>)Items);
DbSet<T> dbSet = dbContext.Set<T>();
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
using (View?.DeferRefresh())
{
dbSet.UpdateAndSave(item);
AdjustIndex((List<TEntity>)Items);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
foreach (ref readonly TEntity item in CollectionsMarshal.AsSpan((List<TEntity>)Items))
{
dbSet.UpdateAndSave(item);
}
}
}
}
}
[SuppressMessage("", "SA1402")]
internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> : ObservableCollection<TEntityOnly>
where TEntityOnly : class, IEntityOnly<TEntity>
where TEntity : class, IReorderable
{
private readonly IServiceProvider serviceProvider;
public ObservableReorderableDbCollection(List<TEntityOnly> items, IServiceProvider serviceProvider)
: base(AdjustIndex(items.SortBy(x => x.Entity.Index)))
{
this.serviceProvider = serviceProvider;
}
public IAdvancedCollectionView? View { get; set; }
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Add:
OnReorder();
break;
}
}
private static List<TEntityOnly> AdjustIndex(List<TEntityOnly> list)
{
Span<TEntityOnly> span = CollectionsMarshal.AsSpan(list);
for (int i = 0; i < list.Count; i++)
{
ref readonly TEntityOnly item = ref span[i];
item.Entity.Index = i;
}
return list;
}
private void OnReorder()
{
using (View?.DeferRefresh())
{
AdjustIndex((List<TEntityOnly>)Items);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
foreach (ref readonly TEntityOnly item in CollectionsMarshal.AsSpan((List<TEntityOnly>)Items))
{
dbSet.UpdateAndSave(item.Entity);
}
}
}
}
}

View File

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

View File

@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// <summary>
/// 可转换类型服务
/// </summary>
[Obsolete("Not useful anymore")]
internal interface ICastService
{
}

View File

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

View File

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

View File

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

View File

@@ -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>();

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 数据库损坏异常
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal sealed class DatabaseCorruptedException : Exception
{
/// <summary>

View File

@@ -18,6 +18,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 +31,10 @@ 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);
}
}

View File

@@ -6,4 +6,7 @@ namespace Snap.Hutao.Core.ExceptionService;
internal enum HutaoExceptionKind
{
None,
ServiceTypeCastFailed,
FileSystemCreateFileInsufficientPermissions,
PrivateNamedPipeContentHashIncorrect,
}

View File

@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 用户的计算机中的某些设置不符合要求
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal sealed class RuntimeEnvironmentException : Exception
{
/// <summary>

View File

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

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 用户数据损坏异常
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal sealed class UserdataCorruptedException : Exception
{
/// <summary>

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,12 +8,15 @@ using System.IO;
using System.Security.Principal;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.UI.Notifications;
namespace Snap.Hutao.Core;
[Injection(InjectAs.Singleton)]
internal sealed class RuntimeOptions
{
private readonly IServiceProvider serviceProvider;
private readonly Lazy<(Version Version, string UserAgent)> lazyVersionAndUserAgent = new(() =>
{
Version version = Package.Current.Id.Version.ToVersion();
@@ -83,8 +86,14 @@ internal sealed class RuntimeOptions
private readonly Lazy<string> lazyInstalledLocation = new(() => Package.Current.InstalledLocation.Path);
private readonly Lazy<string> lazyFamilyName = new(() => Package.Current.Id.FamilyName);
public RuntimeOptions(ILogger<RuntimeOptions> logger)
private bool isToastAvailable;
private bool isToastAvailableInitialized;
private object isToastAvailableLock = new();
public RuntimeOptions(IServiceProvider serviceProvider, ILogger<RuntimeOptions> logger)
{
this.serviceProvider = serviceProvider;
AppLaunchTime = DateTimeOffset.UtcNow;
}
@@ -108,5 +117,19 @@ internal sealed class RuntimeOptions
public bool IsElevated { get => lazyElevated.Value; }
public bool IsToastAvailable
{
get
{
return LazyInitializer.EnsureInitialized(ref isToastAvailable, ref isToastAvailableInitialized, ref isToastAvailableLock, GetIsToastAvailable);
bool GetIsToastAvailable()
{
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
return taskContext.InvokeOnMainThread(() => ToastNotificationManager.CreateToastNotifier().Setting is NotificationSetting.Enabled);
}
}
}
public DateTimeOffset AppLaunchTime { get; }
}

View File

@@ -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");
}
}

View File

@@ -48,4 +48,45 @@ internal static class DispatcherQueueExtension
exceptionDispatchInfo?.Throw();
}
}
/// <summary>
/// 在调度器队列同步调用,直到执行结束,会持续阻塞当前线程
/// </summary>
/// <param name="dispatcherQueue">调度器队列</param>
/// <param name="action">执行的回调</param>
/// <typeparam name="T">返回类型</typeparam>
/// <returns>回调返回值</returns>
public static T Invoke<T>(this DispatcherQueue dispatcherQueue, Func<T> action)
{
T result = default!;
if (dispatcherQueue.HasThreadAccess)
{
return action();
}
ExceptionDispatchInfo? exceptionDispatchInfo = null;
using (ManualResetEventSlim blockEvent = new(false))
{
dispatcherQueue.TryEnqueue(() =>
{
try
{
result = action();
}
catch (Exception ex)
{
exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
}
finally
{
blockEvent.Set();
}
});
blockEvent.Wait();
exceptionDispatchInfo?.Throw();
return result;
}
}
}

View File

@@ -12,6 +12,8 @@ internal interface ITaskContext
void InvokeOnMainThread(Action action);
T InvokeOnMainThread<T>(Func<T> action);
ThreadPoolSwitchOperation SwitchToBackgroundAsync();
DispatcherQueueSwitchOperation SwitchToMainThreadAsync();

View File

@@ -44,6 +44,12 @@ internal sealed class TaskContext : ITaskContext, ITaskContextUnsafe
dispatcherQueue.Invoke(action);
}
/// <inheritdoc/>
public T InvokeOnMainThread<T>(Func<T> action)
{
return dispatcherQueue.Invoke(action);
}
public void BeginInvokeOnMainThread(Action action)
{
dispatcherQueue.TryEnqueue(() => action());

View File

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

View File

@@ -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();

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Text;
@@ -44,34 +46,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();
@@ -113,6 +87,25 @@ internal static partial class EnumerableExtension
return new ObservableCollection<T>(source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ObservableReorderableDbCollection<TEntity> ToObservableReorderableDbCollection<TEntity>(this IEnumerable<TEntity> source, IServiceProvider serviceProvider)
where TEntity : class, IReorderable
{
return source is List<TEntity> list
? new ObservableReorderableDbCollection<TEntity>(list, serviceProvider)
: new ObservableReorderableDbCollection<TEntity>([.. source], serviceProvider);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ObservableReorderableDbCollection<TEntityOnly, TEntity> ToObservableReorderableDbCollection<TEntityOnly, TEntity>(this IEnumerable<TEntityOnly> source, IServiceProvider serviceProvider)
where TEntityOnly : class, IEntityOnly<TEntity>
where TEntity : class, IReorderable
{
return source is List<TEntityOnly> list
? new ObservableReorderableDbCollection<TEntityOnly, TEntity>(list, serviceProvider)
: new ObservableReorderableDbCollection<TEntityOnly, TEntity>([.. source], serviceProvider);
}
/// <summary>
/// Concatenates each element from the collection into single string.
/// </summary>

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Message;
internal sealed class BackgroundImageTypeChangedMessage;

View File

@@ -0,0 +1,624 @@
// <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("20240202015857_ImplReorderableOnUserAndGameAccount")]
partial class ImplReorderableOnUserAndGameAccount
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
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>("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
}
}
}

View File

@@ -0,0 +1,60 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class ImplReorderableOnUserAndGameAccount : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations");
migrationBuilder.AddColumn<int>(
name: "Index",
table: "users",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "Index",
table: "game_accounts",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations",
column: "EntryId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations");
migrationBuilder.DropColumn(
name: "Index",
table: "users");
migrationBuilder.DropColumn(
name: "Index",
table: "game_accounts");
migrationBuilder.CreateIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations",
column: "EntryId");
}
}
}

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

View File

@@ -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");
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
@@ -42,7 +42,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("achievements", (string)null);
b.ToTable("achievements");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
@@ -60,7 +60,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("achievement_archives", (string)null);
b.ToTable("achievement_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
@@ -88,7 +88,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("avatar_infos", (string)null);
b.ToTable("avatar_infos");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
@@ -110,7 +110,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries", (string)null);
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
@@ -154,9 +154,10 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.HasIndex("EntryId");
b.HasIndex("EntryId")
.IsUnique();
b.ToTable("cultivate_entry_level_informations", (string)null);
b.ToTable("cultivate_entry_level_informations");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
@@ -181,7 +182,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("EntryId");
b.ToTable("cultivate_items", (string)null);
b.ToTable("cultivate_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
@@ -202,7 +203,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("cultivate_projects", (string)null);
b.ToTable("cultivate_projects");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
@@ -258,7 +259,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("UserId");
b.ToTable("daily_notes", (string)null);
b.ToTable("daily_notes");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
@@ -276,7 +277,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("gacha_archives", (string)null);
b.ToTable("gacha_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
@@ -307,7 +308,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("gacha_items", (string)null);
b.ToTable("gacha_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
@@ -319,6 +320,9 @@ namespace Snap.Hutao.Migrations
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
@@ -332,7 +336,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("game_accounts", (string)null);
b.ToTable("game_accounts");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
@@ -354,7 +358,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_items", (string)null);
b.ToTable("inventory_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
@@ -383,7 +387,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries", (string)null);
b.ToTable("inventory_reliquaries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
@@ -408,7 +412,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons", (string)null);
b.ToTable("inventory_weapons");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
@@ -424,7 +428,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("object_cache", (string)null);
b.ToTable("object_cache");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
@@ -437,7 +441,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("settings", (string)null);
b.ToTable("settings");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
@@ -459,7 +463,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("spiral_abysses", (string)null);
b.ToTable("spiral_abysses");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
@@ -483,6 +487,9 @@ namespace Snap.Hutao.Migrations
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<bool>("IsOversea")
.HasColumnType("INTEGER");
@@ -496,13 +503,16 @@ namespace Snap.Hutao.Migrations
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", (string)null);
b.ToTable("users");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
@@ -530,8 +540,8 @@ namespace Snap.Hutao.Migrations
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithMany()
.HasForeignKey("EntryId")
.WithOne("LevelInformation")
.HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -603,6 +613,11 @@ namespace Snap.Hutao.Migrations
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Navigation("LevelInformation");
});
#pragma warning restore 612, 618
}
}

View File

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

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Primitive;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -14,7 +15,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("game_accounts")]
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>
internal sealed class GameAccount : ObservableObject, IReorderable, IMappingFrom<GameAccount, string, string, SchemeType>
{
/// <summary>
/// 内部Id
@@ -44,6 +45,8 @@ internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount,
/// </summary>
public string MihoyoSDK { get; set; } = default!;
public int Index { get; set; }
public static GameAccount From(string name, string sdk, SchemeType type)
{
return new()

View File

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

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("users")]
internal sealed class User : ISelectable, IMappingFrom<User, Cookie, bool>
internal sealed class User : ISelectable, IReorderable, IMappingFrom<User, Cookie, bool>
{
/// <summary>
/// 内部Id
@@ -69,6 +69,10 @@ internal sealed class User : ISelectable, IMappingFrom<User, Cookie, bool>
public DateTimeOffset CookieTokenLastUpdateTime { get; set; }
public int Index { get; set; }
public string? PreferredUid { get; set; }
/// <summary>
/// 创建一个新的用户
/// </summary>

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Control;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Primitive;
using System.Globalization;
@@ -50,8 +51,20 @@ internal sealed partial class DescriptionsParametersDescriptor : ValueConverter<
{
if (desc.AsSpan().TrySplitIntoTwo('|', out ReadOnlySpan<char> description, out ReadOnlySpan<char> format))
{
string resultFormatted = ParamRegex().Replace(format.ToString(), match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = description.ToString(), Parameter = resultFormatted });
if (description[0] is not '#')
{
// Fast path
string resultFormatted = ParamRegex().Replace(format.ToString(), match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = description.ToString(), Parameter = resultFormatted });
}
else
{
string descriptionString = SpecialNameHandler.Handle(description.ToString());
string formatString = SpecialNameHandler.Handle(format.ToString());
string resultFormatted = ParamRegex().Replace(formatString, match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = descriptionString, Parameter = resultFormatted });
}
}
else
{

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text;
using System.Text.RegularExpressions;
namespace Snap.Hutao.Metadata;
internal static partial class SpecialNameHandler
{
// Use this regex to query special names in metadata
// "#(?!.*(?:F#|M#|NON_BREAK_SPACE|REALNAME\[ID\(1\)(\|HOSTONLY\(true\)|)\]|\{LAYOUT_MOBILE#.+?\}\{LAYOUT_PC#.+?\}\{LAYOUT_PS#.+?\})).*
public static string Handle(string input)
{
if (string.IsNullOrEmpty(input))
{
return input;
}
if (input.AsSpan()[0] is not '#')
{
return input;
}
StringBuilder resultBuilder = new(input);
resultBuilder
.Replace("{MATEAVATAR#SEXPRO[INFO_MALE_PRONOUN_BOYFIRST|INFO_FEMALE_PRONOUN_GIRLFIRST]}", SH.MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst)
.Replace("{MATEAVATAR#SEXPRO[INFO_MALE_PRONOUN_BOYD|INFO_FEMALE_PRONOUN_GIRLD]}", SH.MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD)
.Replace("{PLAYERAVATAR#SEXPRO[INFO_MALE_PRONOUN_HE|INFO_FEMALE_PRONOUN_SHE]}", SH.MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe)
.Replace("{REALNAME[ID(1)|HOSTONLY(true)]}", SH.MetadataSpecialNameRealNameId1)
.Replace("{REALNAME[ID(1)]}", SH.MetadataSpecialNameRealNameId1)
.Replace("{NICKNAME}", SH.MetadataSpecialNameNickname)
.Replace("{NON_BREAK_SPACE}", "\u00A0");
input = resultBuilder.ToString();
input = MaleFemaleRegex().Replace(input, "<color=#1E90FF>$1</color>/<color=#FFB6C1>$2</color>");
input = FemaleMaleRegex().Replace(input, "<color=#FFB6C1>$1</color>/<color=#1E90FF>$2</color>");
input = LayoutRegex().Replace(input, "$2");
return input[1..];
}
[GeneratedRegex("\\{M#(.*?)\\}\\{F#(.*?)\\}")]
private static partial Regex MaleFemaleRegex();
[GeneratedRegex("\\{F#(.*?)\\}\\{M#(.*?)\\}")]
private static partial Regex FemaleMaleRegex();
[GeneratedRegex("\\{LAYOUT_MOBILE#(.+?)\\}\\{LAYOUT_PC#(.+?)\\}\\{LAYOUT_PS#(.+?)\\}")]
private static partial Regex LayoutRegex();
}

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.5.0" />
Version="1.9.7.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutaoDev"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.5.0" />
Version="1.9.7.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>Select Account to Launch</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;Prince&lt;/color&gt;/&lt;color=#FFB6C1&gt;Princess&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;I&lt;/color&gt;/&lt;color=#FFB6C1&gt;I&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>Traveler</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;He&lt;/color&gt;/&lt;color=#FFB6C1&gt;She&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>Wanderer</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>Refine {0}</value>
</data>
@@ -270,7 +285,7 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicAssociationTypeInazuma" xml:space="preserve">
<value>Inadzuma</value>
<value>Inazuma</value>
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicAssociationTypeLiyue" xml:space="preserve">
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>Wave 4: A wave will spawn only after killing all enemies in the previous wave</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>Remain 4 Treasure Hoarders on the field, instantly respawn on death. 12 in total.</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>Please update showcase data</value>
</data>
@@ -510,7 +528,7 @@
<value>Must sign in to your MiHoYo BBS account and select a user</value>
</data>
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
<value>Deleted {1} wish record from Uid: {0}</value>
<value>Deleted {1} wish records from UID: {0}</value>
</data>
<data name="ServerGachaLogServiceInsufficientRecordSlot" xml:space="preserve">
<value>Reached max allowed number of wish history archives on Snap Hutao Cloud</value>
@@ -525,10 +543,10 @@
<value>Found abnormal data, unable to upload to Snap Hutao Cloud. Please do not upload across accounts or you can attempt to delete cloud data and try again.</value>
</data>
<data name="ServerGachaLogServiceUploadEntrySucceed" xml:space="preserve">
<value>Uploaded {1} wish records of Uid: {0}, stored {2}</value>
<value>Uploaded {1} wish records of UID: {0}, stored {2}</value>
</data>
<data name="ServerPassportLoginRequired" xml:space="preserve">
<value>Please login or register Hutao account first</value>
<value>Please login or register Snap Hutao account first</value>
</data>
<data name="ServerPassportLoginSucceed" xml:space="preserve">
<value>Login successfully</value>
@@ -540,7 +558,7 @@
<value>Password has been set successfully</value>
</data>
<data name="ServerPassportServiceEmailHasNotRegistered" xml:space="preserve">
<value>Current email adress is not registered</value>
<value>Current email address is not registered</value>
</data>
<data name="ServerPassportServiceEmailHasRegistered" xml:space="preserve">
<value>Current emaill address is registered</value>
@@ -573,7 +591,7 @@
<value>The verification request failed, the current email address has been registered</value>
</data>
<data name="ServerPassportVerifyTooFrequent" xml:space="preserve">
<value>Validation request is too frequent. Please try again in 1 minute.</value>
<value>Verification request is too frequent. Please try again in 1 minute.</value>
</data>
<data name="ServerRecordBannedUid" xml:space="preserve">
<value>Failed to upload Sprial Abyss record, current UID is banned by Hutao Database</value>
@@ -597,7 +615,7 @@
<value>Failed to upload Spiral Abyss record. It is not data for the current schedule.</value>
</data>
<data name="ServerRecordPreviousRequestNotCompleted" xml:space="preserve">
<value>Failed to upload Sprial Abyss record. The record for the current Uid is still being processed. Please do not repeat the operation.</value>
<value>Failed to upload Sprial Abyss record. The record for the current UID is still being processed. Please do not repeat the operation.</value>
</data>
<data name="ServerRecordUploadSuccessAndGachaLogServiceTimeExtended" xml:space="preserve">
<value>Uploaded Spiral Abyss record successfully. Received a privilege extension for Snap Hutao Cloud service.</value>
@@ -609,7 +627,7 @@
<value>Uploaded abyss record successfully. No Snap Hutao Cloud privilege received as no Snap Hutao account found.</value>
</data>
<data name="ServerRecordUploadSuccessButNotFirstTimeAtCurrentSchedule" xml:space="preserve">
<value>Uploaded abyss record successfully. No Snap Hutao Cloud privilege received as there is not first upload of current schedule.</value>
<value>Uploaded abyss record successfully. No Snap Hutao Cloud privilege received as this is not the first upload of current schedule.</value>
</data>
<data name="ServiceAchievementImportResultFormat" xml:space="preserve">
<value>New: {0} Achievements | Updated: {1} Achievements | Delete: {2} Achievements</value>
@@ -732,10 +750,10 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ServiceAvatarInfoSummaryCalculatorNotRefreshed" xml:space="preserve">
<value>Dev Plan: N/A</value>
<value>Enhancement Progression Calculator: N/A</value>
</data>
<data name="ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat" xml:space="preserve">
<value>Avatar Calculator: {0:MM-dd HH:mm}</value>
<value>Enhancement Progression Calculator: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>Battle Chronicle: Not Refreshed</value>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Character Showcase: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>Bing Daily Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>Hutao Daily Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>Genshin Official Launcher Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>Local Random Image</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>No Wallpaper</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Failed to save development plan status</value>
</data>
@@ -806,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Parametric Transformer is ready</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>Missing permission, unable to set your Discord Activity.</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Exploring in Teyvat</value>
</data>
@@ -855,7 +891,7 @@
<value>SToken refresh does not support HoYoLab account</value>
</data>
<data name="ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale" xml:space="preserve">
<value>UIGF file language: {0} does not match Snap Hutao language: {1}, please retry after switching client language to target UIGF file language</value>
<value>URL language: {0} does not match Snap Hutao language: {1}, please retry after switching client language to target URL language</value>
</data>
<data name="ServiceGachaStatisticsFactoryItemIdInvalid" xml:space="preserve">
<value>Unsupported Item ID: {0}</value>
@@ -987,7 +1023,7 @@
<value>Failed to download Metadata validation file</value>
</data>
<data name="ServiceMetadataVersionNotSupported" xml:space="preserve">
<value>Your Snap Hutao version is too low. Please update as soon as possible.</value>
<value>Your Snap Hutao is outdated. Please update as soon as possible.</value>
</data>
<data name="ServiceSignInClaimRewardFailedFormat" xml:space="preserve">
<value>Check-in failed, {0}</value>
@@ -999,7 +1035,7 @@
<value>Fetch Signin reward list failed</value>
</data>
<data name="ServiceSignInRiskVerificationFailed" xml:space="preserve">
<value>Verification failed, please visit MiYouShe checkin page to manually claim reward</value>
<value>Verification failed, please visit miHoYo BBS checkin page to manually claim reward</value>
</data>
<data name="ServiceSignInSuccessRewardFormat" xml:space="preserve">
<value>Check-in successfully, {0} x {1}</value>
@@ -1077,10 +1113,10 @@
<value>4 Stars</value>
</data>
<data name="ViewControlStatisticsCardToLastOrangeText" xml:space="preserve">
<value>Last 5 Stars</value>
<value>Last 5 Star</value>
</data>
<data name="ViewControlStatisticsCardToLastPurpleText" xml:space="preserve">
<value>Last 4 Stars</value>
<value>Last 4 Star</value>
</data>
<data name="ViewControlStatisticsCardUpAveragePullText" xml:space="preserve">
<value>Luckiness Avg</value>
@@ -1158,19 +1194,19 @@
<value>Batch add or update to current Dev Plan</value>
</data>
<data name="ViewDialogCultivatePromotionDeltaTitle" xml:space="preserve">
<value>Add or update to current Dev Plan</value>
<value>Add or update to current Enhancement Progression Plan</value>
</data>
<data name="ViewDialogDailyNoteNotificationDailyTaskNotify" xml:space="preserve">
<value>Daily Commission Availability Notification</value>
</data>
<data name="ViewDialogDailyNoteNotificationExpeditionNotify" xml:space="preserve">
<value>Expedition Reward Notification</value>
<value>Expedition Completion Notification</value>
</data>
<data name="ViewDialogDailyNoteNotificationHomeCoinNotifyThreshold" xml:space="preserve">
<value>Realm Currency Alarm Threshold</value>
<value>Realm Currency Notification Threshold</value>
</data>
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
<value>Original Resin Alarm Threshold</value>
<value>Original Resin Notification Threshold</value>
</data>
<data name="ViewDialogDailyNoteNotificationShowInHomeWidget" xml:space="preserve">
<value>Show widget on Home page</value>
@@ -1224,10 +1260,10 @@
<value>Returned data</value>
</data>
<data name="ViewDialogGeetestCustomUrlSampleDescription1" xml:space="preserve">
<value>{0} will be replaced with gt during a request</value>
<value>{0} will be replaced with "gt" during a request</value>
</data>
<data name="ViewDialogGeetestCustomUrlSampleDescription2" xml:space="preserve">
<value>{1} will be replaced with challenge during a request</value>
<value>{1} will be replaced with "challenge" during a request</value>
</data>
<data name="ViewDialogGeetestCustomUrlSampleDescription3" xml:space="preserve">
<value>Requests will be sent to interfaces via GET</value>
@@ -1281,7 +1317,7 @@
<value>Name the account</value>
</data>
<data name="ViewDialogLaunchGamePackageConvertHint" xml:space="preserve">
<value>Conversion may take some time. Do not turn off the software.</value>
<value>Conversion may take some time. Please don't close HuTao.</value>
</data>
<data name="ViewDialogLaunchGamePackageConvertTitle" xml:space="preserve">
<value>Converting Game Client</value>
@@ -1290,7 +1326,7 @@
<value>Scan the QR code with MiHoYo BBS App</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>To avoid enable in a mistake, please input &lt;b&gt;title name&lt;/b&gt; of feature you are enabling</value>
<value>To avoid enabling unintentionally, please input the &lt;b&gt;title name&lt;/b&gt; of feature you are enabling</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>You are Enabling a Dangerous Feature</value>
@@ -1332,13 +1368,13 @@
<value>Snap Hutao Open Source License</value>
</data>
<data name="ViewGuideStepAgreementPrivacyPolicy" xml:space="preserve">
<value>User Data and Privacy Notice</value>
<value>User Data and Privacy Policy</value>
</data>
<data name="ViewGuideStepAgreementTermOfService" xml:space="preserve">
<value>User Agreement and Legal Notices</value>
</data>
<data name="ViewGuideStepDocument" xml:space="preserve">
<value>Documentation</value>
<value>Document</value>
</data>
<data name="ViewGuideStepEnvironment" xml:space="preserve">
<value>Environments</value>
@@ -1373,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>Cannot reorder in Admin Mode</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Archive [{0}] added successfully</value>
</data>
@@ -1425,7 +1464,7 @@
<value>No Artifact Set</value>
</data>
<data name="ViewModelCultivationAddWarning" xml:space="preserve">
<value>Failed to add development plan</value>
<value>Failed to add Enhancement Progression plan</value>
</data>
<data name="ViewModelCultivationBatchAddCompletedFormat" xml:space="preserve">
<value>Operation completed: added/updated {0}, skipped {1}</value>
@@ -1437,7 +1476,7 @@
<value>Successfully added to current plan</value>
</data>
<data name="ViewModelCultivationEntryAddWarning" xml:space="preserve">
<value>Please create a plan and select it in the Development Plan feature</value>
<value>Please create a plan and select it in the Enhancement Progression Plan feature</value>
</data>
<data name="ViewModelCultivationEntryViewDescriptionDefault" xml:space="preserve">
<value>Add item again to view cultivation description</value>
@@ -1572,7 +1611,7 @@
<value>Identify Monitors</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>Unable to read game config file: {0}, file may be not exist not lack of user permission</value>
<value>Unable to read game config file: {0}, file may be not exist or insufficient permission</value>
</data>
<data name="ViewModelLaunchGamePathInvalid" xml:space="preserve">
<value>Game program path is incorrect. Change it in the Settings Page.</value>
@@ -1584,7 +1623,7 @@
<value>Switch game account failed</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>Cannot select account [{1}] for Uid [{0}], the account does not belong to current server</value>
<value>Cannot select account [{1}] for UID [{0}], the account does not belong to current server</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>Operation completed</value>
@@ -1638,7 +1677,7 @@
<value>User [{0}] removed successfully</value>
</data>
<data name="ViewModelUserUpdated" xml:space="preserve">
<value>Successfully update Cookie for user [{0}]</value>
<value>Successfully updated Cookie for user [{0}]</value>
</data>
<data name="ViewModelViewDisposedOperationCancel" xml:space="preserve">
<value>View Resource has been disposed, operation cancelled.</value>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Completed</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>Response stream does not contain valid content type</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Queued</value>
</data>
@@ -1824,7 +1866,7 @@
<value>Refresh</value>
</data>
<data name="ViewPageDailyNoteRefreshTime" xml:space="preserve">
<value>Refresh Duration</value>
<value>Refresh Interval</value>
</data>
<data name="ViewPageDailyNoteReminderDescription" xml:space="preserve">
<value>Causes the notification to stay on-screen and expanded until the user takes action</value>
@@ -1920,7 +1962,7 @@
<value>Buy or Renew Cloud Service</value>
</data>
<data name="ViewPageGachaLogHutaoCloudDelete" xml:space="preserve">
<value>Delete the Cloud Archive of This Uid</value>
<value>Delete the Cloud Archive of This UID</value>
</data>
<data name="ViewPageGachaLogHutaoCloudDeveloperHint" xml:space="preserve">
<value>Lifetime developer license</value>
@@ -1929,7 +1971,7 @@
<value>Snap Hutao Cloud Service Expired</value>
</data>
<data name="ViewPageGachaLogHutaoCloudRetrieve" xml:space="preserve">
<value>Download the Cloud Archive of This Uid</value>
<value>Download the Cloud Archive of this UID</value>
</data>
<data name="ViewPageGachaLogHutaoCloudSpiralAbyssActivityDescription" xml:space="preserve">
<value>Free 3-day license after uploading current schedule Abyss record</value>
@@ -1992,7 +2034,7 @@
<value>Overview</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>Statistics</value>
<value>Global Wish Stats</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Weapon</value>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Launch Parameters</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>Auto start Better GUI for automation tasks after game launched</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>General</value>
</data>
@@ -2187,10 +2235,10 @@
<value>InterProcess</value>
</data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>Run the software on the selected display</value>
<value>Run the software on the selected monitor</value>
</data>
<data name="ViewPageLaunchGameMonitorsHeader" xml:space="preserve">
<value>Display</value>
<value>Monitor</value>
</data>
<data name="ViewPageLaunchGameMultipleInstancesDescription" xml:space="preserve">
<value>Run multiple game processes simultaneously</value>
@@ -2202,7 +2250,7 @@
<value>Game Options</value>
</data>
<data name="ViewPageLaunchGamePlayTimeDescription" xml:space="preserve">
<value>Try to start the game after the game is started and use Starward for game duration statistics</value>
<value>Try to start Starward after the game is started for game duration statistics</value>
</data>
<data name="ViewPageLaunchGamePlayTimeHeader" xml:space="preserve">
<value>Hours Played</value>
@@ -2271,7 +2319,7 @@
<value>Enabled</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>Take advantage of displays that support HDR for brighter, more vivid, and more detailed pictures</value>
<value>Take advantage of monitors that support HDR for brighter, more vivid, and more detailed pictures</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>Image Copyright Information</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>Change the source of wallpaper, restart Snap Hutao to apply the change</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>Wallpaper Image</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Images cache are saved here</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>Cache Folder</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>Hutao Passport</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>Copy</value>
</data>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>Official Website</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>Customized background image, support bmp/gif/ico/jpg/jpeg/png/tiff/webp format</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>Open Background Folder</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>Reset</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>Reset Image Resource</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>Add Unlock Frame Rate Limit Option in Game Launcher Process Section</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>Game Launcher - Unlock Frame Rate Limit</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>You need to move data in the directory manually, otherwise new user data will be created.</value>
</data>
@@ -2673,7 +2745,7 @@
<value>Battles Fought</value>
</data>
<data name="ViewSpiralAbyssDamage" xml:space="preserve">
<value>Most Damage Taken</value>
<value>Most Damage Dealt</value>
</data>
<data name="ViewSpiralAbyssDefaultDescription" xml:space="preserve">
<value>No Spiral Abyss record</value>
@@ -2775,7 +2847,7 @@
<value>Document</value>
</data>
<data name="ViewUserNoUserHint" xml:space="preserve">
<value>No Login</value>
<value>Haven't logged in</value>
</data>
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
<value>Refresh CookieToken successfully</value>
@@ -2799,7 +2871,7 @@
<value>You can continue to use your computer without any impact</value>
</data>
<data name="ViewWelcomeSubtitle" xml:space="preserve">
<value>Please do not turn off the software</value>
<value>Please do not close the software</value>
</data>
<data name="ViewWelcomeTitle" xml:space="preserve">
<value>Welcome to use Snap Hutao</value>
@@ -2823,7 +2895,7 @@
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】).*?(\d\.\dAfter the Version update).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Update Maintenance Duration.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>〓Update Maintenance Duration.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve">
<value>Version \d\.\d Update Details</value>
@@ -2889,7 +2961,7 @@
<value>Daily Commission Reward claimed</value>
</data>
<data name="WebDailyNoteHomeCoinRecoveryFormat" xml:space="preserve">
<value>Will be full in {0} {1:HH:mm}</value>
<value>Full in {0} {1:HH:mm}</value>
</data>
<data name="WebDailyNoteHomeLocked" xml:space="preserve">
<value>Serenitea Pot not Unlocked</value>
@@ -2949,7 +3021,7 @@
<value>Wrong UID format</value>
</data>
<data name="WebEnkaResponseStatusCode404" xml:space="preserve">
<value>Role UID does not exist, please try again later</value>
<value>Character UID does not exist, please try again later</value>
</data>
<data name="WebEnkaResponseStatusCode424" xml:space="preserve">
<value>Game in maintenance</value>
@@ -3026,4 +3098,4 @@
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>Monitor ID</value>
</data>
</root>
</root>

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>Pilih akun untuk memulai</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>Pengembara</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>Perbaiki {0}</value>
</data>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>Gelombang 4: Gelombang akan muncul hanya setelah membunuh semua musuh di gelombang sebelumnya</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 个</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>Silakan perbarui data tampilan.</value>
</data>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Pameran Karakter: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Gagal menyimpan status rencana pengembangan</value>
</data>
@@ -806,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Parametric Transformer telah siap</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Menjelajahi di Teyvat</value>
</data>
@@ -1373,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arsip [{0}] berhasil ditambahkan</value>
</data>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Selesai</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Mengantre</value>
</data>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Argumen Awalan</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Umum</value>
</data>
@@ -2220,7 +2268,7 @@
<value>Pengunduhan Sumber Daya</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>完整包</value>
<value>Klien</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Prapengunduhan</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Cache gambar disimpan di sini</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>Berkas Cache</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>Salin</value>
</data>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>Website Resmi</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>Reset</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>Setel ulang Sumber Daya Gambar</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>Anda perlu memindahkan data di direktori secara manual, jika tidak, data pengguna baru akan dibuat.</value>
</data>

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>アカウントを選択して開始します</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>旅人</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>放浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>精錬ランク {0}</value>
</data>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>ウェーブ 4: すべての敵を倒すと、次のウェーブの敵が出現する。</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 个</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>キャラクターラインナップを更新する</value>
</data>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>キャラクターラインナップ:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>育成計画のステータスを保存できません</value>
</data>
@@ -806,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量物質変化器は使用可能</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>テイワット大陸を探索中</value>
</data>
@@ -1373,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>ゲームランチャー</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>アーカイブ [{0}] を作成しました</value>
</data>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完了</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待機中</value>
</data>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>コマンドラインパラメーター</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
</data>
@@ -2220,7 +2268,7 @@
<value>リソースダウンロード</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>完整包</value>
<value>クライアント</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>事前ダウンロード</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>テーマ</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>イメージキャッシュはここに格納されます</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>キャッシュフォルダ</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>コピー</value>
</data>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>公式サイト</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>リセット</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>画像リソースをリセット</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>ディレクトリを変更した場合、フォルダ内のデータを手動で移動する必要があります。移動しない場合はユーザーデータが新規作成されます。</value>
</data>

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>계정 선택 및 시작</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>旅行者</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>재련 {0}</value>
</data>
@@ -253,13 +268,13 @@
<value>제 {0} 호</value>
</data>
<data name="ModelInterchangeUIGFItemTypeAvatar" xml:space="preserve">
<value>角色</value>
<value>캐릭터</value>
</data>
<data name="ModelInterchangeUIGFItemTypeUnknown" xml:space="preserve">
<value>未知</value>
</data>
<data name="ModelInterchangeUIGFItemTypeWeapon" xml:space="preserve">
<value>武器</value>
<value>무기</value>
</data>
<data name="ModelIntrinsicAssociationTypeFatui" xml:space="preserve">
<value>우인단</value>
@@ -307,7 +322,7 @@
<value>여성</value>
</data>
<data name="ModelIntrinsicBodyTypeLoli" xml:space="preserve">
<value>萝莉</value>
<value>로리</value>
</data>
<data name="ModelIntrinsicBodyTypeMale" xml:space="preserve">
<value>남성</value>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>第四波:击败所有怪物,下一波才会出现</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 个</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>请更新角色橱窗数据</value>
</data>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>육성 계획 상태를 저장하지 못했습니다</value>
</data>
@@ -806,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>매개 변수 변환기가 준비되었습니다</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陆中探索</value>
</data>
@@ -1167,7 +1203,7 @@
<value>탐색 파견 완료 알림</value>
</data>
<data name="ViewDialogDailyNoteNotificationHomeCoinNotifyThreshold" xml:space="preserve">
<value>洞天宝钱提醒阈值</value>
<value>原粹树脂提醒阈值</value>
</data>
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
<value>선계 화폐 한도 알림</value>
@@ -1182,7 +1218,7 @@
<value>매개 변수 변환기 알림</value>
</data>
<data name="ViewDialogDailyNoteWebhookUrlInputPlaceholder" xml:space="preserve">
<value>请输入 Url</value>
<value>Url 입력</value>
</data>
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>实时便笺 Webhook Url</value>
@@ -1338,7 +1374,7 @@
<value>用户使用协议与法律声明</value>
</data>
<data name="ViewGuideStepDocument" xml:space="preserve">
<value>文档</value>
<value>문서</value>
</data>
<data name="ViewGuideStepEnvironment" xml:space="preserve">
<value>环境</value>
@@ -1359,7 +1395,7 @@
<value>下载并自行安装运行时</value>
</data>
<data name="ViewGuideStepLanguage" xml:space="preserve">
<value>语言</value>
<value>언어</value>
</data>
<data name="ViewGuideStepStaticResource" xml:space="preserve">
<value>资源</value>
@@ -1373,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>게임 시작</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>아카이브 [{0}]가 추가되었습니다</value>
</data>
@@ -1542,7 +1581,7 @@
<value>我已阅读并同意上方的条款</value>
</data>
<data name="ViewModelGuideActionComplete" xml:space="preserve">
<value>完成</value>
<value>완료</value>
</data>
<data name="ViewModelGuideActionNext" xml:space="preserve">
<value>下一步</value>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>완료</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>대기 중</value>
</data>
@@ -1848,7 +1890,7 @@
<value>这些选项仅允许在非管理员模式下更改</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshHeader" xml:space="preserve">
<value>刷新</value>
<value>동기화</value>
</data>
<data name="ViewPageDailyNoteSlientModeDescription" xml:space="preserve">
<value>원신을 플레이 할 때 알리지 않음</value>
@@ -1962,10 +2004,10 @@
<value>가져오기</value>
</data>
<data name="ViewPageGachaLogRefreshByManualInput" xml:space="preserve">
<value>手动输入 Url</value>
<value>Url 수동 입력</value>
</data>
<data name="ViewPageGachaLogRefreshByManualInputDescription" xml:space="preserve">
<value>使用由你提供的 Url 刷新祈愿记录</value>
<value>제공된 Url을 이용해 기원 기록을 동기화합니다</value>
</data>
<data name="ViewPageGachaLogRefreshBySToken" xml:space="preserve">
<value>SToken 동기화</value>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>보통</value>
</data>
@@ -2220,7 +2268,7 @@
<value>리소스 다운로드</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>完整包</value>
<value>클라이언트</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>사전 다운로드</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>배경 테마</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>여기에 저장된 이미지 캐시</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>缓存 文件夹</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>복사</value>
</data>
@@ -2424,25 +2484,25 @@
<value>主页卡片</value>
</data>
<data name="ViewpageSettingHomeCardItemAchievementHeader" xml:space="preserve">
<value>成就管理</value>
<value>업적 관리</value>
</data>
<data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve">
<value>实时便笺</value>
<value>실시간 메모</value>
</data>
<data name="ViewpageSettingHomeCardItemgachaStatisticsHeader" xml:space="preserve">
<value>祈愿记录</value>
<value>기원 기록</value>
</data>
<data name="ViewpageSettingHomeCardItemLaunchGameHeader" xml:space="preserve">
<value>启动游戏</value>
<value>게임 시작</value>
</data>
<data name="ViewPageSettingHomeCardOff" xml:space="preserve">
<value>隐藏</value>
<value>숨김</value>
</data>
<data name="ViewPageSettingHomeCardOn" xml:space="preserve">
<value>显示</value>
<value>표시</value>
</data>
<data name="ViewpageSettingHomeHeader" xml:space="preserve">
<value>主页</value>
<value>홈페이지</value>
</data>
<data name="ViewPageSettingHutaoPassportDangerZoneDescription" xml:space="preserve">
<value>三思而后行</value>
@@ -2463,7 +2523,7 @@
<value>已认证的合作开发者</value>
</data>
<data name="ViewPageSettingHutaoPassportLoginAction" xml:space="preserve">
<value>登录</value>
<value>로그인</value>
</data>
<data name="ViewPageSettingHutaoPassportLogoutAction" xml:space="preserve">
<value>退出登录</value>
@@ -2481,7 +2541,7 @@
<value>使用兑换码</value>
</data>
<data name="ViewPageSettingHutaoPassportRegisterAction" xml:space="preserve">
<value>注册</value>
<value>회원가입</value>
</data>
<data name="ViewPageSettingHutaoPassportResetPasswordAction" xml:space="preserve">
<value>修改密码</value>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>공식 홈페이지로 이동</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>초기화</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>이미지 리소스 재설정</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>경로를 변경한 후 경로 내의 데이터를 수동으로 이동해야 합니다. 그렇지 않으면 사용자 데이터가 재생성됩니다</value>
</data>
@@ -2628,7 +2700,7 @@
<value>드롭 아이템</value>
</data>
<data name="ViewPageWiKiWeaponAfterAscensionTitle" xml:space="preserve">
<value>突破后</value>
<value>돌파 후</value>
</data>
<data name="ViewPageWiKiWeaponAutoSuggestBoxPlaceHolder" xml:space="preserve">
<value>筛选武器</value>
@@ -2772,7 +2844,7 @@
<value>먼저 로그인하세요</value>
</data>
<data name="ViewUserDocumentationHeader" xml:space="preserve">
<value>文档</value>
<value>문서</value>
</data>
<data name="ViewUserNoUserHint" xml:space="preserve">
<value>尚未登录</value>
@@ -2841,7 +2913,7 @@
<value>{0}시간 후 종료</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>打开剪贴板失败</value>
<value>클립보드를 열지 못했습니다</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已复制到剪贴板</value>
@@ -2856,7 +2928,7 @@
<value>进行中</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>已完成</value>
<value>완료됨</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusForbid" xml:space="preserve">
<value>禁止领取</value>

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>Selecionar conta para iniciar</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>Viajante</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>Refinar {0}</value>
</data>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>Onda 4: Uma onda surgirá somente depois de matar todos os inimigos da onda anterior</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 个</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>Atualize os dados de exibição</value>
</data>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Exibição de personagens: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Falha ao salvar o status do planejamento</value>
</data>
@@ -806,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>O Transformador Paramétrico está pronto</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Explorando em Teyvat</value>
</data>
@@ -1373,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Inicializador</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arquivo [{0}] adicionado com sucesso</value>
</data>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Concluído</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Em fila</value>
</data>
@@ -1992,7 +2034,7 @@
<value>Visão geral</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>Estatísticas</value>
<value>全球祈愿统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Arma</value>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Argumentos de inicialização</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Geral</value>
</data>
@@ -2220,7 +2268,7 @@
<value>Download de recurso</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>完整包</value>
<value>Cliente</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Pré-download</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Material de pano de fundo</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>O cache de imagens é salvo aqui</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>Pasta de cache</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>Copiar</value>
</data>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>Site oficial</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>Abrir pasta do fundo</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>Resetar</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>Redefinir recurso de imagem</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>É necessário mover os dados no diretório manualmente, caso contrário, serão criados novos dados de usuário.</value>
</data>

View File

@@ -159,18 +159,6 @@
<data name="ControlPanelPanelSelectorDropdownListName" xml:space="preserve">
<value>列表</value>
</data>
<data name="ControlTextMetadataSpecialNameMaleFemale" xml:space="preserve">
<value>旅行者(男):$1\r\n旅行者$2</value>
</data>
<data name="ControlTextMetadataSpecialNameMetaAvatarSexProD" xml:space="preserve">
<value>王子/公主</value>
</data>
<data name="ControlTextMetadataSpecialNamePlayerAvatarSexPro" xml:space="preserve">
<value>他/她</value>
</data>
<data name="ControlTextMetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="CoreExceptionServiceDatabaseCorruptedMessage" xml:space="preserve">
<value>数据库已损坏:{0}</value>
</data>
@@ -198,6 +186,9 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>未检测到 WebView2 运行时</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>导出</value>
</data>
@@ -210,6 +201,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>选择账号并启动</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>旅行者</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>精炼 {0}</value>
</data>
@@ -764,6 +770,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存养成计划状态失败</value>
</data>
@@ -821,6 +842,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量质变仪已准备完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陆中探索</value>
</data>
@@ -1388,6 +1412,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>启动游戏</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存档 [{0}] 添加成功</value>
</data>
@@ -1667,6 +1694,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>等待中</value>
</data>
@@ -2183,6 +2213,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
</data>
@@ -2324,12 +2360,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>缓存 文件夹</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>复制</value>
</data>
@@ -2522,6 +2570,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>前往官网</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>重置</value>
</data>
@@ -2531,6 +2585,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>重置图片资源</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>更改目录后需要手动移动目录内的数据,否则会重新创建用户数据</value>
</data>

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>Выберите учетную запись для запуска</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;Принц&lt;/color&gt;/&lt;color=#FFB6C1&gt;Принцесса&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;I&lt;/color&gt;/&lt;color=#FFB6C1&gt;I&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>Путешественник</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;He&lt;/color&gt;/&lt;color=#FFB6C1&gt;She&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>Путник</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>Уточнить {0}</value>
</data>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>Четвертая волна: Победите всех монстров, прежде чем появится следующая волна</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>Remain 4 Treasure Hoarders on the field, instantly respawn on death. 12 in total.</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>Обновите данные витрины персонажей</value>
</data>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Демонстрация персонажей: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Не удалось сохранить статус плана разработки.</value>
</data>
@@ -795,28 +828,31 @@
<value>Текущая оригинальная смола:{0}</value>
</data>
<data name="ServiceDailyNoteNotifierTitle" xml:space="preserve">
<value>实时便笺提醒</value>
<value>Уведомление в реальном времени</value>
</data>
<data name="ServiceDailyNoteNotifierTransformer" xml:space="preserve">
<value>参量质变仪</value>
<value>Преобразователь</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerAdaptiveHint" xml:space="preserve">
<value>Готово</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量质变仪已准备完成</value>
<value>Преобразователь готов</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陆中探索</value>
<value>Исследование Тейвата</value>
</data>
<data name="ServiceDiscordGameLaunchedBy" xml:space="preserve">
<value>Started by {0}</value>
</data>
<data name="ServiceGachaLogArchiveCollectionUserdataCorruptedMessage" xml:space="preserve">
<value>无法获取祈愿记录:{0}</value>
<value>Не удалось получить историю: {0}</value>
</data>
<data name="ServiceGachaLogEndIdUserdataCorruptedMessage" xml:space="preserve">
<value>无法获取祈愿记录 End Id</value>
<value>Unable to resolve wish history End Id</value>
</data>
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>角色活动</value>
@@ -837,19 +873,19 @@
<value>数据包含异常物品, Id{0}</value>
</data>
<data name="ServiceGachaLogUrlProviderAuthkeyRequestFailed" xml:space="preserve">
<value>请求验证密钥失败</value>
<value>Не удалось получить ключ авторизации</value>
</data>
<data name="ServiceGachaLogUrlProviderCachePathInvalid" xml:space="preserve">
<value>未正确提供原神路径,或当前设置的路径不正确</value>
<value>Путь к Genshin Impact не установлен или содержит ошибки</value>
</data>
<data name="ServiceGachaLogUrlProviderCachePathNotFound" xml:space="preserve">
<value>找不到原神内置浏览器缓存路径:\n{0}</value>
</data>
<data name="ServiceGachaLogUrlProviderCacheUrlNotFound" xml:space="preserve">
<value>未找到可用的 Url</value>
<value>Can't find available URL</value>
</data>
<data name="ServiceGachaLogUrlProviderManualInputInvalid" xml:space="preserve">
<value>提供的 Url 无效</value>
<value>Указан неверный Url-адрес</value>
</data>
<data name="ServiceGachaLogUrlProviderStokenUnsupported" xml:space="preserve">
<value>SToken refresh не поддерживает учетную запись HoYoLab</value>
@@ -864,43 +900,43 @@
<value>Язык файла UIGF: {0} не соответствует языку Snap Hutao: {1}, пожалуйста, повторите попытку после переключения языка клиента на целевой язык файла UIGF</value>
</data>
<data name="ServiceGameDetectGameAccountMultiMatched" xml:space="preserve">
<value>存在多个匹配账号,请删除重复的账号</value>
<value>Обнаружено несколько совпадающих учетных записей. Удалите повторяющиеся учетные записи</value>
</data>
<data name="ServiceGameEnsureGameResourceInsufficientDirectoryPermissions" xml:space="preserve">
<value>文件系统权限不足,无法转换服务器</value>
<value>Недостаточно разрешений файловой системы для запуска сервера</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>下载游戏资源索引</value>
<value>Скачать игровые ресурсы</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>游戏文件操作失败:{0}</value>
<value>Не удалось выполнить операцию с файлом: {0}</value>
</data>
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
<value>解锁帧率上限失败</value>
<value>Не удалось разблокировать ограничение FPS</value>
</data>
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
<value>游戏进程运行中</value>
<value>Процесс игры запущен</value>
</data>
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
<value>请选择游戏路径</value>
<value>Выберите игровой путь</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>下载游戏资源索引失败: {0}</value>
<value>Не удалось загрузить игровые ресурсы: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>游戏进程已退出</value>
<value>Процесс игры закрыт</value>
</data>
<data name="ServiceGameLaunchPhaseProcessInitializing" xml:space="preserve">
<value>正在初始化游戏进程</value>
<value>Инициализация игрового процесса</value>
</data>
<data name="ServiceGameLaunchPhaseProcessStarted" xml:space="preserve">
<value>Игра запускается...</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsFailed" xml:space="preserve">
<value>解锁帧率上限失败,正在结束游戏进程</value>
<value>Не удалось разблокировать ограничение FPS, закрытие игрового процесса…</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsSucceed" xml:space="preserve">
<value>解锁帧率上限成功</value>
<value>Ограничение FPS успешно разблокировано.</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockingFps" xml:space="preserve">
<value>正在尝试解锁帧率上限</value>
@@ -1371,7 +1407,10 @@
<value>有新的通知</value>
</data>
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>启动游戏</value>
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存档 [{0}] 添加成功</value>
@@ -1416,7 +1455,7 @@
<value>获取数据中</value>
</data>
<data name="ViewModelAvatarPropertyOpenClipboardFail" xml:space="preserve">
<value>打开剪贴板失败</value>
<value>Не удалось открыть буфер обмена</value>
</data>
<data name="ViewModelAvatarPropertyShowcaseNotOpen" xml:space="preserve">
<value>角色展柜尚未开启,请前往游戏操作后重试</value>
@@ -1506,7 +1545,7 @@
<value>导入数据中包含了不支持的物品</value>
</data>
<data name="ViewModelGachaLogImportWarningTitle" xml:space="preserve">
<value>导入失败</value>
<value>Ошибка загрузки</value>
</data>
<data name="ViewModelGachaLogPredictedPullLeftToOrange" xml:space="preserve">
<value>{1:P3} 概率 {0} 抽后获得五星物品</value>
@@ -1524,7 +1563,7 @@
<value>该操作是不可逆的,该存档和其内的所有祈愿数据会丢失</value>
</data>
<data name="ViewModelGachaLogRemoveArchiveTitle" xml:space="preserve">
<value>确定要删除存档 {0} 吗?</value>
<value>Вы уверены, что хотите удалить архив {0}?</value>
</data>
<data name="ViewModelGachaLogRetrieveFromHutaoCloudProgress" xml:space="preserve">
<value>从胡桃云服务同步祈愿记录</value>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Завершено</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>В процессе</value>
</data>
@@ -1941,7 +1983,7 @@
<value>上传当前的祈愿存档</value>
</data>
<data name="ViewPageGachaLogImportAction" xml:space="preserve">
<value>导入</value>
<value>Импорт</value>
</data>
<data name="ViewPageGachaLogImportDescription" xml:space="preserve">
<value>导入来自其它 App 的数据</value>
@@ -1956,7 +1998,7 @@
<value>从胡桃云恢复祈愿记录</value>
</data>
<data name="ViewPageGachaLogRefresh" xml:space="preserve">
<value>刷新</value>
<value>Обновить</value>
</data>
<data name="ViewPageGachaLogRefreshAction" xml:space="preserve">
<value>获取</value>
@@ -1983,7 +2025,7 @@
<value>删除当前存档</value>
</data>
<data name="ViewPageGahcaLogPivotAvatar" xml:space="preserve">
<value>角色</value>
<value>Персонаж</value>
</data>
<data name="ViewPageGahcaLogPivotHistory" xml:space="preserve">
<value>历史</value>
@@ -1992,7 +2034,7 @@
<value>总览</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>Статистика</value>
<value>统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Оружие</value>
@@ -2112,10 +2154,10 @@
<value>请输入验证码</value>
</data>
<data name="ViewPageLaunchGameAction" xml:space="preserve">
<value>启动游戏</value>
<value>Game Launcher</value>
</data>
<data name="ViewPageLaunchGameAdvanceHeader" xml:space="preserve">
<value>高级功能</value>
<value>启动高级功能</value>
</data>
<data name="ViewPageLaunchGameAppearanceAspectRatioDescription" xml:space="preserve">
<value>快速切换到指定的分辨率</value>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
</data>
@@ -2220,7 +2268,7 @@
<value>资源下载</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>完整包</value>
<value>客户端</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>预下载</value>
@@ -2301,7 +2349,7 @@
<value>设置呈现语言</value>
</data>
<data name="ViewPageSettingApperanceLanguageHeader" xml:space="preserve">
<value>语言</value>
<value>Язык</value>
</data>
<data name="ViewPageSettingBackdropMaterialDescription" xml:space="preserve">
<value>更改窗体的背景材质</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>缓存 文件夹</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>复制</value>
</data>
@@ -2424,7 +2484,7 @@
<value>主页卡片</value>
</data>
<data name="ViewpageSettingHomeCardItemAchievementHeader" xml:space="preserve">
<value>成就管理</value>
<value>Достижения</value>
</data>
<data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve">
<value>实时便笺</value>
@@ -2433,7 +2493,7 @@
<value>祈愿记录</value>
</data>
<data name="ViewpageSettingHomeCardItemLaunchGameHeader" xml:space="preserve">
<value>启动游戏</value>
<value>Game Launcher</value>
</data>
<data name="ViewPageSettingHomeCardOff" xml:space="preserve">
<value>隐藏</value>
@@ -2442,7 +2502,7 @@
<value>显示</value>
</data>
<data name="ViewpageSettingHomeHeader" xml:space="preserve">
<value>主页</value>
<value>Главная</value>
</data>
<data name="ViewPageSettingHutaoPassportDangerZoneDescription" xml:space="preserve">
<value>三思而后行</value>
@@ -2463,7 +2523,7 @@
<value>已认证的合作开发者</value>
</data>
<data name="ViewPageSettingHutaoPassportLoginAction" xml:space="preserve">
<value>登录</value>
<value>Войти</value>
</data>
<data name="ViewPageSettingHutaoPassportLogoutAction" xml:space="preserve">
<value>退出登录</value>
@@ -2481,7 +2541,7 @@
<value>使用兑换码</value>
</data>
<data name="ViewPageSettingHutaoPassportRegisterAction" xml:space="preserve">
<value>注册</value>
<value>Регистрация</value>
</data>
<data name="ViewPageSettingHutaoPassportResetPasswordAction" xml:space="preserve">
<value>修改密码</value>
@@ -2499,7 +2559,7 @@
<value>更改自动连点功能的快捷键</value>
</data>
<data name="ViewPageSettingKeyShortcutAutoClickingHeader" xml:space="preserve">
<value>自动连点</value>
<value>Авто Клики</value>
</data>
<data name="ViewPageSettingKeyShortcutHeader" xml:space="preserve">
<value>快捷键</value>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>前往官网</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>重置</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>重置图片资源</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>更改目录后需要手动移动目录内的数据,否则会重新创建用户数据</value>
</data>
@@ -2628,7 +2700,7 @@
<value>掉落物品</value>
</data>
<data name="ViewPageWiKiWeaponAfterAscensionTitle" xml:space="preserve">
<value>突破后</value>
<value>After Ascension</value>
</data>
<data name="ViewPageWiKiWeaponAutoSuggestBoxPlaceHolder" xml:space="preserve">
<value>筛选武器</value>

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>選擇帳號並啟動</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>旅人</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>精煉 {0}</value>
</data>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>第四波:擊敗所有怪物,下一波才會出現</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 个</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>請更新角色櫥窗數據</value>
</data>
@@ -749,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色櫥窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存養成計劃狀態失敗</value>
</data>
@@ -806,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>參量質變儀已準備完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陸中探索</value>
</data>
@@ -1373,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>啟動遊戲</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存檔 [{0}] 添加成功</value>
</data>
@@ -1652,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待處理</value>
</data>
@@ -2168,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>啟動參數</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
</data>
@@ -2220,7 +2268,7 @@
<value>資源下載</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>完整包</value>
<value>用戶端</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>預下載</value>
@@ -2309,12 +2357,24 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材質</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>圖片暫存存放在此</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>暫存檔案夾</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>複製</value>
</data>
@@ -2507,6 +2567,12 @@
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>前往官網</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>重設</value>
</data>
@@ -2516,6 +2582,12 @@
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>重設圖片資源</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>更改目錄后需要手動移動目錄内的數據,否則會重新創建用戶數據</value>
</data>

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