diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 7c5052bb..1a07880d 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -17,6 +17,16 @@ trigger:
- azure-pipelines.yml
- .github/ISSUE_TEMPLATE/*.yml
- .github/workflows/*.yml
+pr:
+ branches:
+ include:
+ - main
+ paths:
+ exclude:
+ - README.md
+ - azure-pipelines.yml
+ - .github/ISSUE_TEMPLATE/*.yml
+ - .github/workflows/*.yml
pool:
@@ -28,7 +38,7 @@ variables:
project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
buildPlatform: 'x64'
buildConfiguration: 'Release'
- build_date: $[ format('{0:yyyy}.{0:MM}.{0:dd}', pipeline.startTime) ]
+ build_date: $[ format('{0:yyyy}.{0:M}.{0:d}', pipeline.startTime) ]
steps:
@@ -134,6 +144,7 @@ steps:
secureFile: 'Snap.Hutao.CI.cer'
- task: GitHubRelease@1
+ condition: or(eq(variables['Build.Reason'], 'Manual'), eq(variables['Build.Reason'], 'IndividualCI'), eq(variables['Build.Reason'], 'BatchedCI'))
inputs:
gitHubConnection: 'github.com_Masterain'
repositoryName: 'DGP-Studio/Snap.Hutao'
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml
index 75145202..b8ea48cd 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml
@@ -42,6 +42,23 @@
0,6,6,0
0,0,6,6
2
+
+ 212
+ 252
+ 252
+
+ https://hut.ao/features/mhy-account-switch.html#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-cookie
+ https://hut.ao/statements/bug-report.html
+ https://github.com/HolographicHat/GetToken/releases/latest
+
+ https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png
+ https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png
+ https://static.snapgenshin.com/AvatarCard/UI_AvatarIcon_Costume_Card.png
+ https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon25.png
+ https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon71.png
+ https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png
+ https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png
+ https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png
@@ -52,6 +69,7 @@
+
@@ -61,8 +79,16 @@
+
+
+
+
+
+
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+ Margin="0,16,0,0"
+ Style="{StaticResource CaptionTextBlockStyle}"
+ Text="强化词条"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
index 037c1923..66be4c43 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
@@ -12,9 +12,11 @@
xmlns:mxim="using:Microsoft.Xaml.Interactions.Media"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
+ xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.ViewModel"
xmlns:shvc="using:Snap.Hutao.View.Control"
+ xmlns:wsc="using:WinUICommunity.SettingsUI.Controls"
d:DataContext="{d:DesignInstance shv:CultivationViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
@@ -41,272 +43,334 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ Width="48"
+ Height="48"
+ Icon="{Binding Icon}"
+ Quality="{Binding Quality}"/>
-
+
+ Orientation="Horizontal"
+ Visibility="Collapsed">
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+ Height="32"
+ Margin="6,0,0,0"
+ HorizontalAlignment="Stretch"
+ HorizontalContentAlignment="Stretch"
+ Background="Transparent"
+ BorderBrush="{x:Null}"
+ BorderThickness="0"
+ Command="{Binding FinishStateCommand}">
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+ Visible
+
+
+
+
-
-
-
-
-
- Visible
-
-
-
-
+
+
+
+
+ Collapsed
+
+
+
+
+
-
-
-
-
- Collapsed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml
index 0ee77a01..eabc59d0 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml
@@ -106,6 +106,15 @@
+
+
+
-
+ Style="{StaticResource BorderCardStyle}">
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml
index d62da498..5eedd865 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml
@@ -11,6 +11,7 @@
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.ViewModel"
xmlns:shvc="using:Snap.Hutao.View.Control"
+ xmlns:wsc="using:WinUICommunity.SettingsUI.Controls"
d:DataContext="{d:DesignInstance shv:GachaLogViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
@@ -20,461 +21,489 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Command="{Binding ImportFromUIGFJsonCommand}"
+ Icon="{shcm:FontIcon Glyph=}"
+ Label="导入"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+ Background="{StaticResource CardBackgroundFillColorDefault}"
+ CornerRadius="{StaticResource CompatCornerRadiusSmall}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
index 204ffe55..291c7d37 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
@@ -8,6 +8,7 @@
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
+ xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shmbh="using:Snap.Hutao.Model.Binding.Hutao"
xmlns:shv="using:Snap.Hutao.ViewModel"
@@ -63,7 +64,7 @@
-
+
@@ -272,18 +273,11 @@
-
-
-
-
-
-
+
@@ -330,5 +324,6 @@
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
index 3f3ed7d9..780e8869 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
@@ -73,7 +73,7 @@
Description="Github 上反馈的问题会优先处理"
Header="反馈"
Icon="">
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SpiralAbyssRecordPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SpiralAbyssRecordPage.xaml
index 2ffceee1..3dd6c190 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SpiralAbyssRecordPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SpiralAbyssRecordPage.xaml
@@ -22,31 +22,13 @@
-
-
-
-
-
-
-
-
-
-
+ PaneBackground="Transparent"
+ Visibility="{Binding SpiralAbyssView, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
@@ -57,9 +39,21 @@
-
+
-
+
+
+
+
+
+
+
@@ -163,18 +157,21 @@
-
-
+
+
-
-
+
+
-
+
@@ -287,5 +284,27 @@
+
+
+
+
+
+
+
+
+
+
+
-
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml
new file mode 100644
index 00000000..0a2af1be
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml.cs
new file mode 100644
index 00000000..8a6ed07f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Control;
+using Snap.Hutao.ViewModel;
+
+namespace Snap.Hutao.View.Page;
+
+///
+/// ҳ
+///
+public sealed partial class TestPage : ScopedPage
+{
+ ///
+ /// һµIJҳ
+ ///
+ public TestPage()
+ {
+ InitializeWith();
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
index b77248aa..629beb23 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
@@ -59,548 +59,547 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
+
+
+
+
+ Margin="16,16,0,0"
+ Style="{StaticResource BaseTextBlockStyle}"
+ Text="原料理"/>
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml
index 12d10525..c5d2f83b 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml
@@ -32,215 +32,216 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Margin="16"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Bottom"
+ Style="{StaticResource SubtitleTextBlockStyle}"
+ Text="{Binding Selected.Name}"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index 8c251e47..bab287f3 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -62,128 +62,135 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- Visible
-
-
-
-
+
+
+
+
-
-
-
-
- Collapsed
-
-
-
-
-
+
+
+
+
+
+ Visible
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Collapsed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs
new file mode 100644
index 00000000..ad2f8abf
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml.Controls;
+using Snap.Hutao.ViewModel;
+
+namespace Snap.Hutao.View;
+
+///
+/// ӭͼ
+///
+public sealed partial class WelcomeView : UserControl
+{
+ ///
+ /// һµĻӭͼ
+ ///
+ public WelcomeView()
+ {
+ InitializeComponent();
+ DataContext = Ioc.Default.GetRequiredService();
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
index 88d0f343..3b86b0d5 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
@@ -21,7 +21,6 @@ using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
-using Windows.Storage;
using Windows.Storage.Pickers;
namespace Snap.Hutao.ViewModel;
@@ -42,6 +41,7 @@ internal class AchievementViewModel
private readonly IAchievementService achievementService;
private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService;
+ private readonly IContentDialogFactory contentDialogFactory;
private readonly JsonSerializerOptions options;
private readonly TaskCompletionSource openUICompletionSource = new();
@@ -64,7 +64,7 @@ internal class AchievementViewModel
/// 信息条服务
/// Json序列化选项
/// 异步命令工厂
- /// 范围工厂
+ /// 内容对话框工厂
/// 消息器
public AchievementViewModel(
IMetadataService metadataService,
@@ -72,11 +72,13 @@ internal class AchievementViewModel
IInfoBarService infoBarService,
JsonSerializerOptions options,
IAsyncRelayCommandFactory asyncRelayCommandFactory,
+ IContentDialogFactory contentDialogFactory,
IMessenger messenger)
{
this.metadataService = metadataService;
this.achievementService = achievementService;
this.infoBarService = infoBarService;
+ this.contentDialogFactory = contentDialogFactory;
this.options = options;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
@@ -241,36 +243,6 @@ internal class AchievementViewModel
return false;
}
- private static Task ShowImportResultDialogAsync(string title, string message)
- {
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- ContentDialog dialog = new()
- {
- Title = title,
- Content = message,
- PrimaryButtonText = "确认",
- DefaultButton = ContentDialogButton.Primary,
- XamlRoot = mainWindow.Content.XamlRoot,
- };
-
- return dialog.ShowAsync().AsTask();
- }
-
- private static Task ShowImportFailDialogAsync(string message)
- {
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- ContentDialog dialog = new()
- {
- Title = "导入失败",
- Content = message,
- PrimaryButtonText = "确认",
- DefaultButton = ContentDialogButton.Primary,
- XamlRoot = mainWindow.Content.XamlRoot,
- };
-
- return dialog.ShowAsync().AsTask();
- }
-
private async Task OpenUIAsync()
{
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
@@ -286,11 +258,6 @@ internal class AchievementViewModel
Archives = achievementService.GetArchiveCollection();
SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true);
-
- if (SelectedArchive == null)
- {
- infoBarService.Warning("请创建一个成就存档");
- }
}
catch (TaskCanceledException)
{
@@ -306,26 +273,33 @@ internal class AchievementViewModel
#region 存档操作
private async Task AddArchiveAsync()
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, string name) = await new AchievementArchiveCreateDialog(mainWindow).GetInputAsync().ConfigureAwait(false);
-
- if (isOk)
+ if (Archives != null)
{
- ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name)).ConfigureAwait(false);
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, string name) = await new AchievementArchiveCreateDialog().GetInputAsync().ConfigureAwait(false);
- switch (result)
+ if (isOk)
{
- case ArchiveAddResult.Added:
- infoBarService.Success($"存档 [{name}] 添加成功");
- break;
- case ArchiveAddResult.InvalidName:
- infoBarService.Information($"不能添加名称无效的存档");
- break;
- case ArchiveAddResult.AlreadyExists:
- infoBarService.Information($"不能添加名称重复的存档 [{name}]");
- break;
- default:
- throw Must.NeverHappen();
+ ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name)).ConfigureAwait(false);
+
+ switch (result)
+ {
+ case ArchiveAddResult.Added:
+ await ThreadHelper.SwitchToMainThreadAsync();
+ SelectedArchive = Archives.SingleOrDefault(a => a.Name == name);
+
+ infoBarService.Success($"存档 [{name}] 添加成功");
+ break;
+ case ArchiveAddResult.InvalidName:
+ infoBarService.Information($"不能添加名称无效的存档");
+ break;
+ case ArchiveAddResult.AlreadyExists:
+ infoBarService.Information($"不能添加名称重复的存档 [{name}]");
+ break;
+ default:
+ throw Must.NeverHappen();
+ }
}
}
}
@@ -334,23 +308,16 @@ internal class AchievementViewModel
{
if (Archives != null && SelectedArchive != null)
{
- ContentDialog dialog = new()
- {
- Title = $"确定要删除存档 {SelectedArchive.Name} 吗?",
- Content = "该操作是不可逆的,该存档和其内的所有成就状态会丢失。",
- PrimaryButtonText = "确认",
- CloseButtonText = "取消",
- DefaultButton = ContentDialogButton.Close,
- };
-
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- ContentDialogResult result = await dialog.InitializeWithWindow(mainWindow).ShowAsync();
+ ContentDialogResult result = await contentDialogFactory
+ .ConfirmCancelAsync($"确定要删除存档 {SelectedArchive.Name} 吗?", "该操作是不可逆的,该存档和其内的所有成就状态会丢失。")
+ .ConfigureAwait(false);
if (result == ContentDialogResult.Primary)
{
await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
- // reselect first archive
+ // Re-select first archive
+ await ThreadHelper.SwitchToMainThreadAsync();
SelectedArchive = Archives.FirstOrDefault();
}
}
@@ -386,32 +353,25 @@ internal class AchievementViewModel
{
if (SelectedArchive == null || Achievements == null)
{
- infoBarService.Information("必须选择一个存档才能导出成就");
return;
}
- await ThreadHelper.SwitchToMainThreadAsync();
- IPickerFactory pickerFactory = Ioc.Default.GetRequiredService();
- FileSavePicker picker = pickerFactory.GetFileSavePicker();
+ FileSavePicker picker = Ioc.Default.GetRequiredService().GetFileSavePicker();
picker.FileTypeChoices.Add("UIAF 文件", ".json".Enumerate().ToList());
picker.SuggestedStartLocation = PickerLocationId.Desktop;
picker.CommitButtonText = "导出";
picker.SuggestedFileName = $"{achievementService.CurrentArchive?.Name}.json";
- if (await picker.PickSaveFileAsync() is StorageFile file)
+ (bool isPickerOk, FilePath file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
+ if (isPickerOk)
{
UIAF uiaf = await achievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
bool isOk = await file.SerializeToJsonAsync(uiaf, options).ConfigureAwait(false);
- await ThreadHelper.SwitchToMainThreadAsync();
- if (isOk)
- {
- await ShowImportResultDialogAsync("导出成功", "成功保存到指定位置").ConfigureAwait(false);
- }
- else
- {
- await ShowImportResultDialogAsync("导出失败", "写入文件时遇到问题").ConfigureAwait(false);
- }
+ ValueTask dialogTask = isOk
+ ? contentDialogFactory.ConfirmAsync("导出成功", "成功保存到指定位置")
+ : contentDialogFactory.ConfirmAsync("导出失败", "写入文件时遇到问题");
+ await dialogTask.ConfigureAwait(false);
}
}
@@ -429,8 +389,7 @@ internal class AchievementViewModel
}
else
{
- await ThreadHelper.SwitchToMainThreadAsync();
- await ShowImportFailDialogAsync("数据格式不正确").ConfigureAwait(false);
+ await contentDialogFactory.ConfirmAsync("导入失败", "数据格式不正确").ConfigureAwait(false);
}
}
@@ -442,22 +401,21 @@ internal class AchievementViewModel
return;
}
- IPickerFactory pickerFactory = Ioc.Default.GetRequiredService();
- FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "导入", ".json");
+ FileOpenPicker picker = Ioc.Default.GetRequiredService()
+ .GetFileOpenPicker(PickerLocationId.Desktop, "导入", ".json");
+ (bool isPickerOk, FilePath file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
- if (await picker.PickSingleFileAsync() is StorageFile file)
+ if (isPickerOk)
{
(bool isOk, UIAF? uiaf) = await file.DeserializeFromJsonAsync(options).ConfigureAwait(false);
if (isOk)
{
- Must.NotNull(uiaf!);
- await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf).ConfigureAwait(false);
+ await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf!).ConfigureAwait(false);
}
else
{
- await ThreadHelper.SwitchToMainThreadAsync();
- await ShowImportFailDialogAsync("文件的数据格式不正确").ConfigureAwait(false);
+ await contentDialogFactory.ConfirmAsync("导入失败", "文件的数据格式不正确").ConfigureAwait(false);
}
}
}
@@ -479,20 +437,15 @@ internal class AchievementViewModel
{
if (uiaf.IsCurrentVersionSupported())
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ // ContentDialog must be created by main thread.
await ThreadHelper.SwitchToMainThreadAsync();
- (bool isOk, ImportStrategy strategy) = await new AchievementImportDialog(mainWindow, uiaf).GetImportStrategyAsync().ConfigureAwait(true);
+ (bool isOk, ImportStrategy strategy) = await new AchievementImportDialog(uiaf).GetImportStrategyAsync().ConfigureAwait(true);
if (isOk)
{
- ContentDialog importingDialog = new()
- {
- Title = "导入成就中",
- Content = new ProgressBar() { IsIndeterminate = true },
- };
-
ImportResult result;
- await using (await importingDialog.InitializeWithWindow(mainWindow).BlockAsync().ConfigureAwait(false))
+ ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync("导入成就中").ConfigureAwait(false);
+ await using (await dialog.BlockAsync().ConfigureAwait(false))
{
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
}
@@ -504,8 +457,7 @@ internal class AchievementViewModel
}
else
{
- await ThreadHelper.SwitchToMainThreadAsync();
- await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入").ConfigureAwait(false);
+ await contentDialogFactory.ConfirmAsync("导入失败", "数据的 UIAF 版本过低,无法导入").ConfigureAwait(false);
}
return false;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
index 758537d8..bd44028a 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
@@ -74,7 +74,6 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
}
catch (OperationCanceledException)
{
- logger.LogInformation($"{nameof(OpenUIAsync)} cancelled");
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs
index 87d52e09..93789dd9 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs
@@ -204,8 +204,9 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
if (userService.Current != null)
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(mainWindow, avatar.ToCalculable(), avatar.Weapon.ToCalculable())
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(avatar.ToCalculable(), avatar.Weapon.ToCalculable())
.GetPromotionDeltaAsync()
.ConfigureAwait(false);
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs
index 5cb2f72c..bcf1af0d 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs
@@ -10,6 +10,7 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Cultivation;
using Snap.Hutao.Service.Metadata;
+using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
@@ -58,6 +59,7 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation
RemoveEntryCommand = asyncRelayCommandFactory.Create(RemoveEntryAsync);
SaveInventoryItemCommand = new RelayCommand(SaveInventoryItem);
UpdateStatisticsItemsCommand = asyncRelayCommandFactory.Create(UpdateStatisticsItemsAsync);
+ NavigateToPageCommand = new RelayCommand(NavigateToPage);
}
///
@@ -131,6 +133,11 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation
///
public ICommand UpdateStatisticsItemsCommand { get; }
+ ///
+ /// 导航到指定的页面命令
+ ///
+ public ICommand NavigateToPageCommand { get; set; }
+
private async Task OpenUIAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(true))
@@ -143,8 +150,9 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation
private async Task AddProjectAsync()
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, CultivateProject project) = await new CultivateProjectDialog(mainWindow).CreateProjectAsync().ConfigureAwait(false);
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, CultivateProject project) = await new CultivateProjectDialog().CreateProjectAsync().ConfigureAwait(false);
if (isOk)
{
@@ -154,6 +162,9 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation
{
case ProjectAddResult.Added:
infoBarService.Success($"添加成功");
+
+ await ThreadHelper.SwitchToMainThreadAsync();
+ SelectedProject = project;
break;
case ProjectAddResult.InvalidName:
infoBarService.Information($"不能添加名称无效的计划");
@@ -228,4 +239,13 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation
}
}
}
+
+ private void NavigateToPage(string? typeString)
+ {
+ if (typeString != null)
+ {
+ Type? pageType = Type.GetType(typeString);
+ Ioc.Default.GetRequiredService().Navigate(pageType!, INavigationAwaiter.Default, true);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
index d26eb928..a55aca9b 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
@@ -44,7 +44,9 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
private SettingEntry? refreshSecondsEntry;
private SettingEntry? reminderNotifyEntry;
+ private SettingEntry? silentModeEntry;
private ObservableCollection? dailyNoteEntries;
+ private bool isSilentWhenPlayingGame;
///
/// 构造一个新的实时便笺视图模型
@@ -115,6 +117,22 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
}
}
+ ///
+ /// 是否开启免打扰模式
+ ///
+ public bool IsSilentWhenPlayingGame
+ {
+ get => isSilentWhenPlayingGame;
+ set
+ {
+ if (SetProperty(ref isSilentWhenPlayingGame, value))
+ {
+ silentModeEntry!.SetBoolean(value);
+ appDbContext.Settings.UpdateAndSave(silentModeEntry!);
+ }
+ }
+ }
+
///
/// 用户与角色集合
///
@@ -164,11 +182,17 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
ScheduleTaskHelper.RegisterForDailyNoteRefresh(480);
OnPropertyChanged(nameof(SelectedRefreshTime));
- reminderNotifyEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, false.ToString());
+ reminderNotifyEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString);
isReminderNotification = reminderNotifyEntry.GetBoolean();
OnPropertyChanged(nameof(IsReminderNotification));
- DailyNoteEntries = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(true);
+ silentModeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteSilentWhenPlayingGame, SettingEntryHelper.FalseString);
+ isSilentWhenPlayingGame = silentModeEntry.GetBoolean();
+ OnPropertyChanged(nameof(IsSilentWhenPlayingGame));
+
+ ObservableCollection temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false);
+ await ThreadHelper.SwitchToMainThreadAsync();
+ DailyNoteEntries = temp;
}
private async Task TrackRoleAsync(UserAndRole? role)
@@ -196,18 +220,20 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
{
if (entry != null)
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- await new DailyNoteNotificationDialog(mainWindow, entry).ShowAsync();
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await new DailyNoteNotificationDialog(entry).ShowAsync();
appDbContext.DailyNotes.UpdateAndSave(entry);
}
}
private async Task VerifyDailyNoteVerificationAsync()
{
- if (userService.Current != null && userService.Current.SelectedUserGameRole != null)
+ if (UserAndRole.TryFromUser(userService.Current, out UserAndRole? userAndRole))
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- await new DailyNoteVerificationDialog(mainWindow, userService.Current.Entity, userService.Current.SelectedUserGameRole).ShowAsync();
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await new DailyNoteVerificationDialog(userAndRole).ShowAsync();
}
else
{
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs
index 69677e07..3cb28db5 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs
@@ -5,13 +5,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
-using Snap.Hutao.Context.FileSystem.Location;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.Abstraction;
-using Snap.Hutao.Service.User;
-using Snap.Hutao.Web.Hutao;
-using Snap.Hutao.Web.Hutao.Model.Post;
using Windows.Storage;
using Windows.System;
@@ -23,19 +19,12 @@ namespace Snap.Hutao.ViewModel;
[Injection(InjectAs.Scoped)]
internal class ExperimentalFeaturesViewModel : ObservableObject
{
- private readonly IFileSystemLocation hutaoLocation;
-
///
/// 构造一个新的实验性功能视图模型
///
/// 异步命令工厂
- /// 数据文件夹
- public ExperimentalFeaturesViewModel(
- IAsyncRelayCommandFactory asyncRelayCommandFactory,
- HutaoLocation hutaoLocation)
+ public ExperimentalFeaturesViewModel(IAsyncRelayCommandFactory asyncRelayCommandFactory)
{
- this.hutaoLocation = hutaoLocation;
-
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
OpenDataFolderCommand = asyncRelayCommandFactory.Create(OpenDataFolderAsync);
DeleteUsersCommand = asyncRelayCommandFactory.Create(DangerousDeleteUsersAsync);
@@ -52,8 +41,6 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
///
public ICommand OpenDataFolderCommand { get; }
-
-
///
/// 清空用户命令
///
@@ -66,16 +53,14 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
private Task OpenCacheFolderAsync()
{
- return Launcher.LaunchFolderAsync(ApplicationData.Current.TemporaryFolder).AsTask();
+ return Launcher.LaunchFolderAsync(ApplicationData.Current.LocalCacheFolder).AsTask();
}
private Task OpenDataFolderAsync()
{
- return Launcher.LaunchFolderPathAsync(hutaoLocation.GetPath()).AsTask();
+ return Launcher.LaunchFolderPathAsync(Core.CoreEnvironment.DataFolder).AsTask();
}
-
-
private async Task DangerousDeleteUsersAsync()
{
using (IServiceScope scope = Ioc.Default.CreateScope())
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs
index 9fe0c129..583f0d4d 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs
@@ -6,7 +6,6 @@ using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.IO;
-using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Entity;
@@ -15,7 +14,6 @@ using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.GachaLog;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
-using Windows.Storage;
using Windows.Storage.Pickers;
namespace Snap.Hutao.ViewModel;
@@ -29,6 +27,7 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
private readonly IGachaLogService gachaLogService;
private readonly IInfoBarService infoBarService;
private readonly IPickerFactory pickerFactory;
+ private readonly IContentDialogFactory contentDialogFactory;
private readonly JsonSerializerOptions options;
private ObservableCollection? archives;
@@ -45,26 +44,27 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
/// 信息
/// Json序列化选项
/// 异步命令工厂
+ /// 内容对话框工厂
/// 文件选择器工厂
public GachaLogViewModel(
IGachaLogService gachaLogService,
IInfoBarService infoBarService,
JsonSerializerOptions options,
IAsyncRelayCommandFactory asyncRelayCommandFactory,
+ IContentDialogFactory contentDialogFactory,
IPickerFactory pickerFactory)
{
this.gachaLogService = gachaLogService;
this.infoBarService = infoBarService;
this.pickerFactory = pickerFactory;
+ this.contentDialogFactory = contentDialogFactory;
this.options = options;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
RefreshByWebCacheCommand = asyncRelayCommandFactory.Create(RefreshByWebCacheAsync);
RefreshByStokenCommand = asyncRelayCommandFactory.Create(RefreshByStokenAsync);
RefreshByManualInputCommand = asyncRelayCommandFactory.Create(RefreshByManualInputAsync);
- ImportFromUIGFExcelCommand = asyncRelayCommandFactory.Create(ImportFromUIGFExcelAsync);
ImportFromUIGFJsonCommand = asyncRelayCommandFactory.Create(ImportFromUIGFJsonAsync);
- ExportToUIGFExcelCommand = asyncRelayCommandFactory.Create(ExportToUIGFExcelAsync);
ExportToUIGFJsonCommand = asyncRelayCommandFactory.Create(ExportToUIGFJsonAsync);
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
}
@@ -137,21 +137,11 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
///
public ICommand RefreshByManualInputCommand { get; }
- ///
- /// 从 UIGF Excel 导入命令
- ///
- public ICommand ImportFromUIGFExcelCommand { get; }
-
///
/// 从 UIGF Json 导入命令
///
public ICommand ImportFromUIGFJsonCommand { get; }
- ///
- /// 导出到 UIGF Excel 命令
- ///
- public ICommand ExportToUIGFExcelCommand { get; }
-
///
/// 导出到 UIGF Json 命令
///
@@ -162,21 +152,6 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
///
public ICommand RemoveArchiveCommand { get; }
- [ThreadAccess(ThreadAccessState.MainThread)]
- private static Task ShowImportResultDialogAsync(string title, string message)
- {
- ContentDialog dialog = new()
- {
- Title = title,
- Content = message,
- PrimaryButtonText = "确认",
- DefaultButton = ContentDialogButton.Primary,
- };
-
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
- }
-
private async Task OpenUIAsync()
{
if (await gachaLogService.InitializeAsync().ConfigureAwait(true))
@@ -184,11 +159,6 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
Archives = gachaLogService.GetArchiveCollection();
SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true);
- if (SelectedArchive == null)
- {
- infoBarService.Information("请刷新或导入祈愿记录");
- }
-
await ThreadHelper.SwitchToMainThreadAsync();
IsInitialized = true;
}
@@ -221,9 +191,9 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
{
RefreshStrategy strategy = IsAggressiveRefresh ? RefreshStrategy.AggressiveMerge : RefreshStrategy.LazyMerge;
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ // ContentDialog must be created by main thread.
await ThreadHelper.SwitchToMainThreadAsync();
- GachaLogRefreshProgressDialog dialog = new(mainWindow);
+ GachaLogRefreshProgressDialog dialog = new();
IAsyncDisposable dialogHider = await dialog.BlockAsync().ConfigureAwait(false);
Progress progress = new(dialog.OnReport);
bool authkeyValid = await gachaLogService.RefreshGachaLogAsync(query, strategy, progress, default).ConfigureAwait(false);
@@ -251,36 +221,24 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
}
}
- private async Task ImportFromUIGFExcelAsync()
- {
- await Task.Yield();
- }
-
private async Task ImportFromUIGFJsonAsync()
{
FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "导入", ".json");
-
- if (await picker.PickSingleFileAsync() is StorageFile file)
+ (bool isPickerOk, FilePath file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
+ if (isPickerOk)
{
(bool isOk, UIGF? uigf) = await file.DeserializeFromJsonAsync(options).ConfigureAwait(false);
if (isOk)
{
- Must.NotNull(uigf!);
- await TryImportUIGFInternalAsync(uigf).ConfigureAwait(false);
+ await TryImportUIGFInternalAsync(uigf!).ConfigureAwait(false);
}
else
{
- await ThreadHelper.SwitchToMainThreadAsync();
- await ShowImportResultDialogAsync("导入失败", "文件的数据格式不正确").ConfigureAwait(false);
+ await contentDialogFactory.ConfirmAsync("导入失败", "文件的数据格式不正确").ConfigureAwait(false);
}
}
}
- private async Task ExportToUIGFExcelAsync()
- {
- await Task.Yield();
- }
-
private async Task ExportToUIGFJsonAsync()
{
if (SelectedArchive == null)
@@ -294,20 +252,17 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
picker.CommitButtonText = "导出";
picker.FileTypeChoices.Add("UIGF Json 文件", new List() { ".json" });
- if (await picker.PickSaveFileAsync() is StorageFile file)
+ (bool isPickerOk, FilePath file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
+
+ if (isPickerOk)
{
UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false);
bool isOk = await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false);
- await ThreadHelper.SwitchToMainThreadAsync();
- if (isOk)
- {
- await ShowImportResultDialogAsync("导出成功", "成功保存到指定位置").ConfigureAwait(false);
- }
- else
- {
- await ShowImportResultDialogAsync("导出失败", "写入文件时遇到问题").ConfigureAwait(false);
- }
+ ValueTask dialogTask = isOk
+ ? contentDialogFactory.ConfirmAsync("导出成功", "成功保存到指定位置")
+ : contentDialogFactory.ConfirmAsync("导出失败", "写入文件时遇到问题");
+ await dialogTask.ConfigureAwait(false);
}
}
@@ -315,17 +270,9 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
{
if (Archives != null && SelectedArchive != null)
{
- ContentDialog dialog = new()
- {
- Title = $"确定要删除存档 {SelectedArchive.Uid} 吗?",
- Content = "该操作是不可逆的,该存档和其内的所有祈愿数据会丢失。",
- PrimaryButtonText = "确认",
- CloseButtonText = "取消",
- DefaultButton = ContentDialogButton.Close,
- };
-
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- ContentDialogResult result = await dialog.InitializeWithWindow(mainWindow).ShowAsync();
+ ContentDialogResult result = await contentDialogFactory
+ .ConfirmCancelAsync($"确定要删除存档 {SelectedArchive.Uid} 吗?", "该操作是不可逆的,该存档和其内的所有祈愿数据会丢失。")
+ .ConfigureAwait(false);
if (result == ContentDialogResult.Primary)
{
@@ -337,7 +284,6 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
}
}
- [ThreadAccess(ThreadAccessState.MainThread)]
private void SetSelectedArchiveAndUpdateStatistics(GachaArchive? archive, bool forceUpdate = false)
{
bool changed = false;
@@ -362,7 +308,6 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
}
}
- [ThreadAccess(ThreadAccessState.MainThread)]
private async Task UpdateStatisticsAsync(GachaArchive? archive)
{
GachaStatistics temp = await gachaLogService.GetStatisticsAsync(archive).ConfigureAwait(false);
@@ -370,22 +315,16 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
Statistics = temp;
}
- [ThreadAccess(ThreadAccessState.AnyThread)]
private async Task TryImportUIGFInternalAsync(UIGF uigf)
{
if (uigf.IsCurrentVersionSupported())
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ // ContentDialog must be created by main thread.
await ThreadHelper.SwitchToMainThreadAsync();
- if (await new GachaLogImportDialog(mainWindow, uigf).GetShouldImportAsync().ConfigureAwait(true))
+ if (await new GachaLogImportDialog(uigf).GetShouldImportAsync().ConfigureAwait(true))
{
- ContentDialog importingDialog = new()
- {
- Title = "导入祈愿记录中",
- Content = new ProgressBar() { IsIndeterminate = true },
- };
-
- await using (await importingDialog.InitializeWithWindow(mainWindow).BlockAsync().ConfigureAwait(false))
+ ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync("导入祈愿记录中").ConfigureAwait(true);
+ await using (await dialog.BlockAsync().ConfigureAwait(false))
{
await gachaLogService.ImportFromUIGFAsync(uigf.List, uigf.Info.Uid).ConfigureAwait(false);
}
@@ -398,8 +337,7 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
}
else
{
- await ThreadHelper.SwitchToMainThreadAsync();
- await ShowImportResultDialogAsync("导入失败", "数据的 UIGF 版本过低,无法导入").ConfigureAwait(false);
+ await contentDialogFactory.ConfirmAsync("导入失败", "数据的 UIGF 版本过低,无法导入").ConfigureAwait(false);
}
return false;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
index 772214c6..b4f4186f 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
@@ -269,7 +269,8 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
private void SaveSetting()
{
DbSet settings = appDbContext.Settings;
- settings.SingleOrAdd(SettingEntry.LaunchIsFullScreen, TrueString).SetBoolean(IsFullScreen);
+ settings.SingleOrAdd(SettingEntry.LaunchIsExclusive, FalseString).SetBoolean(IsExclusive);
+ settings.SingleOrAdd(SettingEntry.LaunchIsFullScreen, FalseString).SetBoolean(IsFullScreen);
settings.SingleOrAdd(SettingEntry.LaunchIsBorderless, FalseString).SetBoolean(IsBorderless);
settings.SingleOrAdd(SettingEntry.LaunchScreenWidth, "1920").SetInt32(ScreenWidth);
settings.SingleOrAdd(SettingEntry.LaunchScreenHeight, "1080").SetInt32(ScreenHeight);
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
index d013e73c..442afe49 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
@@ -4,7 +4,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Factory.Abstraction;
@@ -14,11 +13,7 @@ using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.GachaLog;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
-using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
-using Snap.Hutao.Web.Hoyolab;
-using Snap.Hutao.Web.Hoyolab.Passport;
-using Snap.Hutao.Web.Response;
using System.IO;
namespace Snap.Hutao.ViewModel;
@@ -185,37 +180,6 @@ internal class SettingViewModel : ObservableObject
///
public ICommand ShowSignInWebViewDialogCommand { get; }
- private static async Task DangerousUnusedLoginMethodAsync()
- {
- LoginMihoyoBBSDialog dialog = ActivatorUtilities.CreateInstance(Ioc.Default);
- (bool isOk, Dictionary? data) = await dialog.GetInputAccountPasswordAsync().ConfigureAwait(false);
-
- if (isOk)
- {
- (Response? resp, Aigis? aigis) = await Ioc.Default
- .GetRequiredService()
- .LoginByPasswordAsync(data, CancellationToken.None)
- .ConfigureAwait(false);
-
- if (resp != null)
- {
- if (resp.IsOk())
- {
- Cookie cookie = Cookie.FromLoginResult(resp.Data);
-
- await Ioc.Default
- .GetRequiredService()
- .ProcessInputCookieAsync(cookie)
- .ConfigureAwait(false);
- }
-
- if (resp.ReturnCode == (int)KnownReturnCode.RET_NEED_AIGIS)
- {
- }
- }
- }
- }
-
private async Task SetGamePathAsync()
{
IGameLocator locator = Ioc.Default.GetRequiredService>()
@@ -245,15 +209,18 @@ internal class SettingViewModel : ObservableObject
private async Task ShowSignInWebViewDialogAsync()
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- await new SignInWebViewDialog(mainWindow).ShowAsync();
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await new SignInWebViewDialog().ShowAsync().AsTask().ConfigureAwait(false);
}
private async Task DebugThrowExceptionAsync()
{
#if DEBUG
- CommunityGameRecordDialog dialog = ActivatorUtilities.CreateInstance(Ioc.Default);
- await dialog.ShowAsync();
+ await Ioc.Default
+ .GetRequiredService()
+ .NavigateAsync(Service.Navigation.INavigationAwaiter.Default)
+ .ConfigureAwait(false);
#else
await Task.Yield();
#endif
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs
index a6129a49..2bdc6977 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs
@@ -2,35 +2,22 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using CommunityToolkit.WinUI.UI;
-using Microsoft.Extensions.Primitives;
+using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Control;
-using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction;
-using Snap.Hutao.Model.Binding.Cultivation;
-using Snap.Hutao.Model.Binding.Hutao;
+using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.SpiralAbyss;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
-using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
-using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Abstraction;
-using Snap.Hutao.Service.Cultivation;
-using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.SpiralAbyss;
using Snap.Hutao.Service.User;
-using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.Model.Post;
-using System.Collections.Immutable;
using System.Collections.ObjectModel;
-using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
-using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
-using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
namespace Snap.Hutao.ViewModel;
@@ -38,7 +25,7 @@ namespace Snap.Hutao.ViewModel;
/// 深渊记录视图模型
///
[Injection(InjectAs.Scoped)]
-internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellation
+internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellation, IRecipient
{
private readonly ISpiralAbyssRecordService spiralAbyssRecordService;
private readonly IMetadataService metadataService;
@@ -56,11 +43,13 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati
/// 元数据服务
/// 用户服务
/// 异步命令工厂
+ /// 消息器
public SpiralAbyssRecordViewModel(
ISpiralAbyssRecordService spiralAbyssRecordService,
IMetadataService metadataService,
IUserService userService,
- IAsyncRelayCommandFactory asyncRelayCommandFactory)
+ IAsyncRelayCommandFactory asyncRelayCommandFactory,
+ IMessenger messenger)
{
this.spiralAbyssRecordService = spiralAbyssRecordService;
this.metadataService = metadataService;
@@ -69,6 +58,8 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
RefreshCommand = asyncRelayCommandFactory.Create(RefreshAsync);
UploadSpiralAbyssRecordCommand = asyncRelayCommandFactory.Create(UploadSpiralAbyssRecordAsync);
+
+ messenger.Register(this);
}
///
@@ -116,20 +107,46 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati
///
public ICommand UploadSpiralAbyssRecordCommand { get; }
+ ///
+ public void Receive(UserChangedMessage message)
+ {
+ if (message.NewValue != null)
+ {
+ UserAndRole userAndRole = UserAndRole.FromUser(message.NewValue);
+ if (userAndRole.Role != null)
+ {
+ UpdateSpiralAbyssCollectionAsync(UserAndRole.FromUser(message.NewValue)).SafeForget();
+ return;
+ }
+ }
+
+ SpiralAbyssView = null;
+ }
+
private async Task OpenUIAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
idAvatarMap = AvatarIds.ExtendAvatars(idAvatarMap);
- ObservableCollection temp = await spiralAbyssRecordService.GetSpiralAbyssCollectionAsync().ConfigureAwait(false);
-
- await ThreadHelper.SwitchToMainThreadAsync();
- SpiralAbyssEntries = temp;
- SelectedEntry = SpiralAbyssEntries.FirstOrDefault();
+ if (userService.Current?.SelectedUserGameRole != null)
+ {
+ await UpdateSpiralAbyssCollectionAsync(UserAndRole.FromUser(userService.Current)).ConfigureAwait(false);
+ }
}
}
+ private async Task UpdateSpiralAbyssCollectionAsync(UserAndRole userAndRole)
+ {
+ ObservableCollection temp = await spiralAbyssRecordService
+ .GetSpiralAbyssCollectionAsync(userAndRole)
+ .ConfigureAwait(false);
+
+ await ThreadHelper.SwitchToMainThreadAsync();
+ SpiralAbyssEntries = temp;
+ SelectedEntry = SpiralAbyssEntries.FirstOrDefault();
+ }
+
private async Task RefreshAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
@@ -139,6 +156,9 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati
await spiralAbyssRecordService
.RefreshSpiralAbyssAsync(UserAndRole.FromUser(userService.Current))
.ConfigureAwait(false);
+
+ await ThreadHelper.SwitchToMainThreadAsync();
+ SelectedEntry = SpiralAbyssEntries?.FirstOrDefault();
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs
new file mode 100644
index 00000000..0b1bc9bc
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs
@@ -0,0 +1,125 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using Snap.Hutao.Control;
+using Snap.Hutao.Core.IO;
+using Snap.Hutao.Core.IO.Bits;
+using Snap.Hutao.Extension;
+using Snap.Hutao.Factory.Abstraction;
+using Snap.Hutao.Service.User;
+using Snap.Hutao.View.Dialog;
+using Snap.Hutao.Web.Hoyolab;
+using Snap.Hutao.Web.Hoyolab.Passport;
+using Snap.Hutao.Web.Response;
+
+namespace Snap.Hutao.ViewModel;
+
+///
+/// 测试视图模型
+///
+[Injection(InjectAs.Scoped)]
+internal class TestViewModel : ObservableObject, ISupportCancellation
+{
+ ///
+ /// 构造一个新的测试视图模型
+ ///
+ /// 异步命令工厂
+ public TestViewModel(IAsyncRelayCommandFactory asyncRelayCommandFactory)
+ {
+ ShowCommunityGameRecordDialogCommand = asyncRelayCommandFactory.Create(ShowCommunityGameRecordDialogAsync);
+ ShowAdoptCalculatorDialogCommand = asyncRelayCommandFactory.Create(ShowAdoptCalculatorDialogAsync);
+ DangerousLoginMihoyoBbsCommand = asyncRelayCommandFactory.Create(DangerousLoginMihoyoBbsAsync);
+ DownloadStaticFileCommand = asyncRelayCommandFactory.Create(DownloadStaticFileAsync);
+ }
+
+ ///
+ public CancellationToken CancellationToken { get; set; }
+
+ ///
+ /// 打开游戏社区记录对话框命令
+ ///
+ public ICommand ShowCommunityGameRecordDialogCommand { get; }
+
+ ///
+ /// 打开养成计算对话框命令
+ ///
+ public ICommand ShowAdoptCalculatorDialogCommand { get; }
+
+ ///
+ /// Dangerous 登录米游社命令
+ ///
+ public ICommand DangerousLoginMihoyoBbsCommand { get; }
+
+ ///
+ /// 下载资源文件命令
+ ///
+ public ICommand DownloadStaticFileCommand { get; }
+
+ private async Task ShowCommunityGameRecordDialogAsync()
+ {
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await new CommunityGameRecordDialog().ShowAsync();
+ }
+
+ private async Task ShowAdoptCalculatorDialogAsync()
+ {
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await new AdoptCalculatorDialog().ShowAsync();
+ }
+
+ private async Task DangerousLoginMihoyoBbsAsync()
+ {
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, Dictionary? data) = await new LoginMihoyoBBSDialog().GetInputAccountPasswordAsync().ConfigureAwait(false);
+
+ if (isOk)
+ {
+ (Response? resp, Aigis? aigis) = await Ioc.Default
+ .GetRequiredService()
+ .LoginByPasswordAsync(data, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ if (resp != null)
+ {
+ if (resp.IsOk())
+ {
+ Cookie cookie = Cookie.FromLoginResult(resp.Data);
+
+ await Ioc.Default
+ .GetRequiredService()
+ .ProcessInputCookieAsync(cookie)
+ .ConfigureAwait(false);
+ }
+
+ if (resp.ReturnCode == (int)KnownReturnCode.RET_NEED_AIGIS)
+ {
+ }
+ }
+ }
+ }
+
+ private async Task DownloadStaticFileAsync()
+ {
+ BitsManager bitsManager = Ioc.Default.GetRequiredService();
+ Uri testUri = new(Web.HutaoEndpoints.StaticZip("AvatarIcon"));
+ ILogger logger = Ioc.Default.GetRequiredService>();
+ Progress progress = new(status => logger.LogInformation("{info}", status));
+ (bool isOk, TempFile file) = await bitsManager.DownloadAsync(testUri, progress).ConfigureAwait(false);
+
+ using (file)
+ {
+ if (isOk)
+ {
+ logger.LogInformation("Download completed.");
+ }
+ else
+ {
+ logger.LogInformation("Download failed.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
index 8a19d1a3..680660f4 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Microsoft.Data.Sqlite;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction;
@@ -100,15 +101,25 @@ internal class UserViewModel : ObservableObject
private async Task OpenUIAsync()
{
- Users = await userService.GetUserCollectionAsync().ConfigureAwait(true);
- SelectedUser = userService.Current;
+ try
+ {
+ Users = await userService.GetUserCollectionAsync().ConfigureAwait(true);
+ SelectedUser = userService.Current;
+ }
+ catch (SqliteException ex)
+ {
+ // SQLite Error 11: 'database disk image is malformed'.
+ infoBarService.Error(ex, "用户数据文件已损坏");
+ }
}
private async Task AddUserAsync()
{
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+
// Get cookie from user input
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- ValueResult result = await new UserDialog(mainWindow).GetInputCookieAsync().ConfigureAwait(false);
+ ValueResult result = await new UserDialog().GetInputCookieAsync().ConfigureAwait(false);
// User confirms the input
if (result.IsOk)
@@ -120,6 +131,12 @@ internal class UserViewModel : ObservableObject
switch (optionResult)
{
case UserOptionResult.Added:
+ if (Users!.Count == 1)
+ {
+ await ThreadHelper.SwitchToMainThreadAsync();
+ SelectedUser = Users.Single();
+ }
+
infoBarService.Success($"用户 [{uid}] 添加成功");
break;
case UserOptionResult.Incomplete:
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
new file mode 100644
index 00000000..dfac990a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
@@ -0,0 +1,185 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Common;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Messaging;
+using CommunityToolkit.WinUI.Notifications;
+using Microsoft.Extensions.DependencyInjection;
+using Snap.Hutao.Core.Caching;
+using Snap.Hutao.Core.IO;
+using Snap.Hutao.Core.IO.Bits;
+using Snap.Hutao.Core.Setting;
+using Snap.Hutao.Extension;
+using Snap.Hutao.Factory.Abstraction;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.IO.Compression;
+
+namespace Snap.Hutao.ViewModel;
+
+///
+/// 欢迎视图模型
+///
+[Injection(InjectAs.Scoped)]
+internal class WelcomeViewModel : ObservableObject
+{
+ private readonly IServiceProvider serviceProvider;
+
+ private ObservableCollection? downloadSummaries;
+
+ ///
+ /// 构造一个新的欢迎视图模型
+ ///
+ /// 异步命令工厂
+ /// 服务提供器
+ public WelcomeViewModel(IAsyncRelayCommandFactory asyncRelayCommandFactory, IServiceProvider serviceProvider)
+ {
+ this.serviceProvider = serviceProvider;
+ OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
+ }
+
+ ///
+ /// 下载信息
+ ///
+ public ObservableCollection? DownloadSummaries { get => downloadSummaries; set => SetProperty(ref downloadSummaries, value); }
+
+ ///
+ /// 打开界面命令
+ ///
+ public ICommand OpenUICommand { get; }
+
+ private async Task OpenUIAsync()
+ {
+ List downloadSummaries = new();
+
+ if (!LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false))
+ {
+ downloadSummaries.Add(new(serviceProvider, "基础图标", "Bg"));
+ downloadSummaries.Add(new(serviceProvider, "角色图标", "AvatarIcon"));
+ downloadSummaries.Add(new(serviceProvider, "角色立绘图标", "GachaAvatarIcon"));
+ downloadSummaries.Add(new(serviceProvider, "角色立绘图像", "GachaAvatarImg"));
+ downloadSummaries.Add(new(serviceProvider, "武器图标", "EquipIcon"));
+ downloadSummaries.Add(new(serviceProvider, "武器立绘图标", "GachaEquipIcon"));
+ downloadSummaries.Add(new(serviceProvider, "名片图像", "NameCardPic"));
+ downloadSummaries.Add(new(serviceProvider, "天赋图标", "Skill"));
+ downloadSummaries.Add(new(serviceProvider, "命之座图标", "Talent"));
+ }
+
+ if (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false))
+ {
+ downloadSummaries.Add(new(serviceProvider, "成就图标", "AchievementIcon"));
+ downloadSummaries.Add(new(serviceProvider, "物品图标", "ItemIcon"));
+ downloadSummaries.Add(new(serviceProvider, "元素图标", "IconElement"));
+ downloadSummaries.Add(new(serviceProvider, "圣遗物图标", "RelicIcon"));
+ }
+
+ DownloadSummaries = new(downloadSummaries);
+
+ await Task.WhenAll(downloadSummaries.Select(d => d.DownloadAndExtractAsync())).ConfigureAwait(true);
+
+ serviceProvider.GetRequiredService().Send(new Message.WelcomeStateCompleteMessage());
+
+ // Complete StaticResourceContracts
+ LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true);
+ LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true);
+
+ new ToastContentBuilder()
+ .AddText("下载完成")
+ .AddText("现在可以开始使用胡桃了")
+ .Show();
+ }
+
+ ///
+ /// 下载信息
+ ///
+ public class DownloadSummary : ObservableObject
+ {
+ private readonly IServiceProvider serviceProvider;
+ private readonly BitsManager bitsManager;
+ private readonly string fileName;
+ private readonly Uri fileUri;
+ private readonly Progress progress;
+ private string description = "等待中";
+ private double progressValue;
+
+ ///
+ /// 构造一个新的下载信息
+ ///
+ /// 服务提供器
+ /// 显示名称
+ /// 压缩文件名称
+ public DownloadSummary(IServiceProvider serviceProvider, string displayName, string fileName)
+ {
+ this.serviceProvider = serviceProvider;
+ bitsManager = serviceProvider.GetRequiredService();
+ DisplayName = displayName;
+ this.fileName = fileName;
+ fileUri = new(Web.HutaoEndpoints.StaticZip(fileName));
+
+ progress = new(UpdateProgressStatus);
+ }
+
+ ///
+ /// 显示名称
+ ///
+ public string DisplayName { get; init; }
+
+ ///
+ /// 描述
+ ///
+ public string Description { get => description; private set => SetProperty(ref description, value); }
+
+ ///
+ /// 进度值,最大1
+ ///
+ public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
+
+ ///
+ /// 异步下载并解压
+ ///
+ /// 任务
+ public async Task DownloadAndExtractAsync()
+ {
+ (bool isOk, TempFile file) = await bitsManager.DownloadAsync(fileUri, progress).ConfigureAwait(false);
+
+ using (file)
+ {
+ await ThreadHelper.SwitchToMainThreadAsync();
+ if (isOk && File.Exists(file.Path))
+ {
+ ProgressValue = 1;
+ await ThreadHelper.SwitchToBackgroundAsync();
+ ExtractFiles(file.Path);
+ await ThreadHelper.SwitchToMainThreadAsync();
+ Description = "完成";
+ }
+ else
+ {
+ ProgressValue = 0;
+ Description = "文件下载异常";
+ }
+ }
+ }
+
+ private void UpdateProgressStatus(ProgressUpdateStatus status)
+ {
+ Description = $"{Converters.ToFileSizeString(status.BytesRead)}/{Converters.ToFileSizeString(status.TotalBytes)}";
+ ProgressValue = (double)status.BytesRead / status.TotalBytes;
+ }
+
+ private void ExtractFiles(string file)
+ {
+ IImageCacheFilePathOperation imageCache = serviceProvider.GetRequiredService().ImplictAs()!;
+
+ using (ZipArchive archive = ZipFile.OpenRead(file))
+ {
+ foreach (ZipArchiveEntry entry in archive.Entries)
+ {
+ string destPath = imageCache.GetFilePathFromCategoryAndFileName(fileName, entry.FullName);
+ entry.ExtractToFile(destPath, true);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
index 6b923018..0ce7734e 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
@@ -129,8 +129,9 @@ internal class WikiAvatarViewModel : ObservableObject
if (userService.Current != null)
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(mainWindow, avatar.ToCalculable(), null)
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(avatar.ToCalculable(), null)
.GetPromotionDeltaAsync()
.ConfigureAwait(false);
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs
index c375b829..fcac3e39 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs
@@ -132,8 +132,9 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
if (userService.Current != null)
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(mainWindow, null, weapon.ToCalculable())
+ // ContentDialog must be created by main thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(null, weapon.ToCalculable())
.GetPromotionDeltaAsync()
.ConfigureAwait(false);
@@ -231,4 +232,4 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
return keep;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs
index feaa34ec..fbddb725 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs
@@ -7,7 +7,7 @@ using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Web;
///
-/// API端点
+/// API 端点
///
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1124")]
@@ -311,14 +311,6 @@ internal static class ApiEndpoints
public const string AccountCreateActionTicket = $"{PassportApi}/account/ma-cn-verifier/app/createActionTicketByToken";
#endregion
- #region Patcher
-
- ///
- /// 胡桃检查更新
- ///
- public const string PatcherHutaoStable = $"{PatcherApi}/hutao/stable";
- #endregion
-
#region SdkStaticLauncherApi
///
@@ -368,8 +360,6 @@ internal static class ApiEndpoints
private const string PassportApiAuthApi = $"{PassportApi}/account/auth/api";
private const string PassportApiV4 = "https://passport-api-v4.mihoyo.com";
- private const string PatcherApi = "https://patcher.dgp-studio.cn";
-
private const string SdkStatic = "https://sdk-static.mihoyo.com";
private const string SdkStaticLauncherApi = $"{SdkStatic}/hk4e_cn/mdk/launcher/api";
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
index 968b0253..c9836285 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
@@ -124,7 +124,7 @@ public class MiHoYoJSInterface
[JsMethod("getDS")]
public virtual JsResult> GetDynamicSecrectV1(JsParam param)
{
- string salt = DynamicSecretHandler.DynamicSecrets[nameof(SaltType.LK2)];
+ string salt = Core.CoreEnvironment.DynamicSecrets[nameof(SaltType.LK2)];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string r = GetRandomString();
string check = Md5Convert.ToHexString($"salt={salt}&t={t}&r={r}").ToLowerInvariant();
@@ -155,7 +155,7 @@ public class MiHoYoJSInterface
[JsMethod("getDS2")]
public virtual JsResult> GetDynamicSecrectV2(JsParam param)
{
- string salt = DynamicSecretHandler.DynamicSecrets[nameof(SaltType.X4)];
+ string salt = Core.CoreEnvironment.DynamicSecrets[nameof(SaltType.X4)];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
int r = GetRandom();
string b = param.Payload.Body;
@@ -371,7 +371,7 @@ public class MiHoYoJSInterface
if (result != null && param.Callback != null)
{
- await ExecuteCallbackScriptAsync(param.Callback, result.ToString(options)).ConfigureAwait(false);
+ await ExecuteCallbackScriptAsync(param.Callback, result.ToString()).ConfigureAwait(false);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
index cc633de8..0912faaf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
@@ -11,7 +11,6 @@ public interface IJsResult
///
/// 转换到Json字符串表示
///
- /// 序列化参数
/// JSON字符串
- string ToString(JsonSerializerOptions options);
+ string ToString();
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs
index 44c2d52f..90c93d47 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs
@@ -29,7 +29,7 @@ public class JsResult : IJsResult
public TData Data { get; set; } = default!;
///
- string IJsResult.ToString(JsonSerializerOptions options)
+ string IJsResult.ToString()
{
return JsonSerializer.Serialize(this);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs
index 4e029e91..9b969926 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs
@@ -14,7 +14,7 @@ namespace Snap.Hutao.Web.Enka;
[HttpClient(HttpClientConfigration.Default)]
internal class EnkaClient
{
- private const string EnkaAPI = "https://enka.shinshin.moe/u/{0}/__data.json";
+ private const string EnkaAPI = "https://enka.network/u/{0}/__data.json";
private const string EnkaAPIHutaoForward = "https://enka-api.hut.ao/{0}";
private readonly HttpClient httpClient;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs
index e3eccc7f..a6ae0b79 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs
@@ -3,7 +3,6 @@
using Snap.Hutao.Core.Convert;
using Snap.Hutao.Web.Request;
-using System.Collections.Immutable;
using System.Net.Http;
using System.Text;
@@ -15,19 +14,6 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
[Injection(InjectAs.Transient)]
public class DynamicSecretHandler : DelegatingHandler
{
- ///
- /// 盐
- ///
- // https://github.com/UIGF-org/Hoyolab.Salt
- public static readonly ImmutableDictionary DynamicSecrets = new Dictionary()
- {
- [nameof(SaltType.K2)] = "jrU9ULHGZdM9Os3uGHOpjyRELYxby5cg",
- [nameof(SaltType.LK2)] = "9gaxOdeeY2W9dw5x62pywhik8cxy5TIJ",
- [nameof(SaltType.X4)] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
- [nameof(SaltType.X6)] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
- [nameof(SaltType.PROD)] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
- }.ToImmutableDictionary();
-
private const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890";
///
@@ -40,7 +26,7 @@ public class DynamicSecretHandler : DelegatingHandler
string saltType = definations[1];
bool includeChars = definations[2] == "true";
- string salt = DynamicSecrets[saltType];
+ string salt = Core.CoreEnvironment.DynamicSecrets[saltType];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs
index f50bd00b..891874b1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs
@@ -22,8 +22,6 @@ namespace Snap.Hutao.Web.Hutao;
[HttpClient(HttpClientConfigration.Default)]
internal class HomaClient
{
- private const string HutaoAPI = "https://homa.snapgenshin.com";
-
private readonly HttpClient httpClient;
private readonly GameRecordClient gameRecordClient;
private readonly JsonSerializerOptions options;
@@ -54,7 +52,7 @@ internal class HomaClient
public async Task CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
{
Response? resp = await httpClient
- .GetFromJsonAsync>($"{HutaoAPI}/Record/Check?uid={uid}", token)
+ .GetFromJsonAsync>(HutaoEndpoints.RecordCheck(uid.Value), token)
.ConfigureAwait(false);
return resp?.Data == true;
@@ -70,8 +68,8 @@ internal class HomaClient
public async Task GetRankAsync(PlayerUid uid, CancellationToken token = default)
{
Response? resp = await httpClient
- .GetFromJsonAsync>($"{HutaoAPI}/Record/Rank?uid={uid}", token)
- .ConfigureAwait(false);
+ .GetFromJsonAsync>(HutaoEndpoints.RecordRank(uid.Value), token)
+ .ConfigureAwait(false);
return resp?.Data;
}
@@ -85,7 +83,7 @@ internal class HomaClient
public async Task GetOverviewAsync(CancellationToken token = default)
{
Response? resp = await httpClient
- .GetFromJsonAsync>($"{HutaoAPI}/Statistics/Overview", token)
+ .GetFromJsonAsync>(HutaoEndpoints.StatisticsOverview, token)
.ConfigureAwait(false);
return resp?.Data;
@@ -100,7 +98,7 @@ internal class HomaClient
public async Task> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
{
Response>? resp = await httpClient
- .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", options, logger, token)
+ .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarAttendanceRate, options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -115,7 +113,7 @@ internal class HomaClient
public async Task> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
{
Response>? resp = await httpClient
- .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", options, logger, token)
+ .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarUtilizationRate, options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -130,7 +128,7 @@ internal class HomaClient
public async Task> GetAvatarCollocationsAsync(CancellationToken token = default)
{
Response>? resp = await httpClient
- .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", options, logger, token)
+ .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarAvatarCollocation, options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -145,7 +143,7 @@ internal class HomaClient
public async Task> GetWeaponCollocationsAsync(CancellationToken token = default)
{
Response>? resp = await httpClient
- .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Weapon/WeaponCollocation", options, logger, token)
+ .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsWeaponWeaponCollocation, options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -160,7 +158,7 @@ internal class HomaClient
public async Task> GetAvatarHoldingRatesAsync(CancellationToken token = default)
{
Response>? resp = await httpClient
- .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", options, logger, token)
+ .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarHoldingRate, options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -175,7 +173,7 @@ internal class HomaClient
public async Task> GetTeamCombinationsAsync(CancellationToken token = default)
{
Response>? resp = await httpClient
- .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Team/Combination", options, logger, token)
+ .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsTeamCombination, options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -217,6 +215,6 @@ internal class HomaClient
/// 响应
public Task?> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
{
- return httpClient.TryCatchPostAsJsonAsync>($"{HutaoAPI}/Record/Upload", playerRecord, options, logger, token);
+ return httpClient.TryCatchPostAsJsonAsync>(HutaoEndpoints.RecordUpload, playerRecord, options, logger, token);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs
index 6790ab55..755cfd6b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs
@@ -14,7 +14,6 @@ namespace Snap.Hutao.Web.Hutao;
[HttpClient(HttpClientConfigration.Default)]
internal class HomaClient2
{
- private const string HutaoAPI = "https://homa.snapgenshin.com";
private readonly HttpClient httpClient;
///
@@ -41,7 +40,7 @@ internal class HomaClient2
};
Response? a = await httpClient
- .TryCatchPostAsJsonAsync>($"{HutaoAPI}/HutaoLog/Upload", log)
+ .TryCatchPostAsJsonAsync>(HutaoEndpoints.HutaoLogUpload, log)
.ConfigureAwait(false);
return a?.Data;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs
index 1907d7b1..a4362758 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs
@@ -36,6 +36,6 @@ internal class PatchClient
/// 更新信息
public Task GetPatchInformationAsync(CancellationToken token = default)
{
- return httpClient.TryCatchGetFromJsonAsync(ApiEndpoints.PatcherHutaoStable, options, logger, token);
+ return httpClient.TryCatchGetFromJsonAsync(HutaoEndpoints.PatcherHutaoStable, options, logger, token);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs
new file mode 100644
index 00000000..75618762
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs
@@ -0,0 +1,146 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web;
+
+///
+/// 胡桃 API 端点
+///
+[SuppressMessage("", "SA1201")]
+[SuppressMessage("", "SA1124")]
+internal static class HutaoEndpoints
+{
+ ///
+ /// 胡桃资源主机名
+ ///
+ public const string StaticHutao = "static.hut.ao";
+
+ #region HutaoAPI
+
+ ///
+ /// 上传日志
+ ///
+ public const string HutaoLogUpload = $"{HomaSnapGenshinApi}/HutaoLog/Upload";
+
+ ///
+ /// 检查 uid 是否上传记录
+ ///
+ /// uid
+ /// 路径
+ public static string RecordCheck(string uid)
+ {
+ return $"{HomaSnapGenshinApi}/Record/Check?uid={uid}";
+ }
+
+ ///
+ /// uid 排行
+ ///
+ /// uid
+ /// 路径
+ public static string RecordRank(string uid)
+ {
+ return $"{HomaSnapGenshinApi}/Record/Rank?uid={uid}";
+ }
+
+ ///
+ /// 上传记录
+ ///
+ public const string RecordUpload = $"{HomaSnapGenshinApi}/Record/Upload";
+
+ ///
+ /// 统计信息
+ ///
+ public const string StatisticsOverview = $"{HomaSnapGenshinApi}/Statistics/Overview";
+
+ ///
+ /// 出场率
+ ///
+ public const string StatisticsAvatarAttendanceRate = $"{HomaSnapGenshinApi}/Statistics/Avatar/AttendanceRate";
+
+ ///
+ /// 使用率
+ ///
+ public const string StatisticsAvatarUtilizationRate = $"{HomaSnapGenshinApi}/Statistics/Avatar/UtilizationRate";
+
+ ///
+ /// 角色搭配
+ ///
+ public const string StatisticsAvatarAvatarCollocation = $"{HomaSnapGenshinApi}/Statistics/Avatar/AvatarCollocation";
+
+ ///
+ /// 角色持有率
+ ///
+ public const string StatisticsAvatarHoldingRate = $"{HomaSnapGenshinApi}/Statistics/Avatar/HoldingRate";
+
+ ///
+ /// 武器搭配
+ ///
+ public const string StatisticsWeaponWeaponCollocation = $"{HomaSnapGenshinApi}/Statistics/Weapon/WeaponCollocation";
+
+ ///
+ /// 持有率
+ ///
+ public const string StatisticsTeamCombination = $"{HomaSnapGenshinApi}/Statistics/Team/Combination";
+ #endregion
+
+ #region Metadata
+
+ ///
+ /// 胡桃元数据文件
+ ///
+ /// 文件名称
+ /// 路径
+ public static string HutaoMetadataFile(string fileName)
+ {
+ return $"{HutaoMetadataSnapGenshinApi}/{fileName}";
+ }
+ #endregion
+
+ #region Patcher
+
+ ///
+ /// 胡桃检查更新
+ ///
+ public const string PatcherHutaoStable = $"{PatcherDGPStudioApi}/hutao/stable";
+ #endregion
+
+ #region Static & Zip
+
+ ///
+ /// UI_Icon_None
+ ///
+ public static readonly Uri UIIconNone = new(StaticFile("Bg", "UI_Icon_None.png"));
+
+ ///
+ /// UI_ItemIcon_None
+ ///
+ public static readonly Uri UIItemIconNone = new(StaticFile("Bg", "UI_ItemIcon_None.png"));
+
+ ///
+ /// 压缩包资源
+ ///
+ /// 分类
+ /// 文件名称 包括后缀
+ /// 路径
+ public static string StaticFile(string category, string fileName)
+ {
+ return $"{StaticSnapGenshinApi}/{category}/{fileName}";
+ }
+
+ ///
+ /// 压缩包资源
+ ///
+ /// 文件名称 不包括后缀
+ /// 路径
+ public static string StaticZip(string fileName)
+ {
+ return $"{StaticZipSnapGenshinApi}/{fileName}.zip";
+ }
+ #endregion
+
+ private const string HutaoMetadataSnapGenshinApi = "http://hutao-metadata.snapgenshin.com";
+ private const string HomaSnapGenshinApi = "https://homa.snapgenshin.com";
+ private const string PatcherDGPStudioApi = "https://patcher.dgp-studio.cn";
+ private const string StaticSnapGenshinApi = "https://static.snapgenshin.com";
+ private const string StaticZipSnapGenshinApi = "https://static-zip.snapgenshin.com";
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
index 6f9e7ff2..f6c91c94 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Response;
///
/// 已知的返回代码
///
-public enum KnownReturnCode : int
+public enum KnownReturnCode
{
///
/// 无效请求
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
index 972264c8..b2d1d342 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
@@ -94,7 +94,7 @@ public class Response : Response, IJsResult
}
///
- string IJsResult.ToString(JsonSerializerOptions options)
+ string IJsResult.ToString()
{
return JsonSerializer.Serialize(this);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/MemoryExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Win32/MemoryExtensions.cs
index c0453603..5090961f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Win32/MemoryExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Win32/MemoryExtensions.cs
@@ -12,24 +12,7 @@ namespace Snap.Hutao.Win32;
internal static class MemoryExtensions
{
///
- /// 暂时固定 以读取偏移量位置处的内存内容
- ///
- /// 输出类型
- /// 内存
- /// 偏移量
- /// 内容
- public static unsafe T Fixed(this ref Span span, int offset)
- where T : unmanaged
- {
- fixed (byte* pSpan = span)
- {
- T result = *(T*)(pSpan + offset);
- return result;
- }
- }
-
- ///
- /// 将 __winmdroot_Foundation_CHAR_256 转换到 字符串
+ /// 将 __CHAR_256 转换到 字符串
///
/// 目标字符数组
/// 结果字符串