mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c83cd3da5 | ||
|
|
e60a04a2bc | ||
|
|
aec483510f | ||
|
|
c245fe654e | ||
|
|
898d95bb1d | ||
|
|
1df22e5b75 | ||
|
|
332e09fef0 | ||
|
|
2a77daf2ca | ||
|
|
8a47ea8727 | ||
|
|
b3937ac810 | ||
|
|
ed5c52dc63 | ||
|
|
461d139602 | ||
|
|
164ec2af33 | ||
|
|
e30523c621 | ||
|
|
11d0405102 | ||
|
|
a1c0b4f830 | ||
|
|
e476ed5960 | ||
|
|
ffc999360d | ||
|
|
84058011c7 | ||
|
|
c18e0c40c5 | ||
|
|
ad78515094 | ||
|
|
38367a090d | ||
|
|
ce30f609fb | ||
|
|
f4b9cc7c48 | ||
|
|
7c2212f44c | ||
|
|
95eddef457 | ||
|
|
02447bc966 | ||
|
|
fb88e33d16 | ||
|
|
5fa36416ef | ||
|
|
7076caaa5d | ||
|
|
b7b1155cfc | ||
|
|
6351f2b460 | ||
|
|
35ac2f33ba | ||
|
|
f8ff1988bb | ||
|
|
907d70ba71 | ||
|
|
a5bdc17712 | ||
|
|
f078d92f33 | ||
|
|
f2d4f0f1d3 | ||
|
|
fcde9b21ae | ||
|
|
24f09861fd | ||
|
|
47708adc83 | ||
|
|
79a254235a | ||
|
|
d9bcb3b16b | ||
|
|
cf7dd548a2 | ||
|
|
04deeb7086 | ||
|
|
9fb2da41cd | ||
|
|
bb01f3a3cb | ||
|
|
f7f2d9c867 | ||
|
|
01b7e58b3e |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: https://afdian.net/a/DismissedLight
|
||||
2
.github/ISSUE_TEMPLATE/network-issue.yml
vendored
2
.github/ISSUE_TEMPLATE/network-issue.yml
vendored
@@ -21,7 +21,7 @@ body:
|
||||
**在填写下面的问题之前请先使用我们的网络诊断工具**
|
||||
**这个工具将会生成一份报告,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
|
||||
- 你可以点击下面的链接以下载网络诊断工具:
|
||||
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-tool.exe)
|
||||
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-hutao.exe)
|
||||
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/network-diagnosis-hutao.exe)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
24
README.md
24
README.md
@@ -1,12 +1,15 @@
|
||||
# [Snap.Hutao](https://hut.ao)
|
||||
|
||||

|
||||
|
||||
> 唷,找本堂主有何贵干呀?
|
||||
## 下载使用
|
||||
|
||||

|
||||
[](https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52)
|
||||
|
||||
# 特别感谢
|
||||
## 贡献
|
||||
|
||||
* [向我们提交 PR](https://github.com/DGP-Studio/Snap.Hutao/pulls)
|
||||
* [在 Crowdin 上进行本地化](https://crowdin.com/project/snap-hutao)
|
||||
|
||||
## 特别感谢
|
||||
|
||||
* [HolographicHat](https://github.com/HolographicHat)
|
||||
* [UIGF organization](https://uigf.org)
|
||||
@@ -30,4 +33,13 @@
|
||||
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
|
||||
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
* [WinUICommunity/SettingsUI](https://github.com/WinUICommunity/SettingsUI)
|
||||
* [WinUICommunity/SettingsUI](https://github.com/WinUICommunity/SettingsUI)
|
||||
|
||||
### 支撑项目
|
||||
|
||||
* [Snap.Hutao.Server](https://github.com/DGP-Studio/Snap.Hutao.Server)
|
||||
* [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)
|
||||
* [Snap.Data.Mapper](https://github.com/DGP-Studio/Snap.Data.Mapper)
|
||||
|
||||
## 近期活跃数据
|
||||

|
||||
@@ -17,6 +17,7 @@ trigger:
|
||||
- azure-pipelines.yml
|
||||
- .github/ISSUE_TEMPLATE/*.yml
|
||||
- .github/workflows/*.yml
|
||||
- src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
@@ -27,6 +28,7 @@ pr:
|
||||
- azure-pipelines.yml
|
||||
- .github/ISSUE_TEMPLATE/*.yml
|
||||
- .github/workflows/*.yml
|
||||
- src/Snap.Hutao/Snap.Hutao/Resource/Localization/*.resx
|
||||
|
||||
|
||||
pool:
|
||||
@@ -125,18 +127,21 @@ steps:
|
||||
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
|
||||
|
||||
- task: MsixSigning@1
|
||||
name: signMsix
|
||||
displayName: Sign MSIX package
|
||||
inputs:
|
||||
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
|
||||
certificate: 'DGP_Studio_CI.pfx'
|
||||
passwordVariable: 'pw'
|
||||
condition: succeeded()
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Upload Output'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/'
|
||||
artifact: 'Output'
|
||||
publishLocation: 'pipeline'
|
||||
|
||||
#- task: PublishPipelineArtifact@1
|
||||
# displayName: 'Upload Output'
|
||||
# inputs:
|
||||
# targetPath: '$(Build.ArtifactStagingDirectory)/'
|
||||
# artifact: 'Output'
|
||||
# publishLocation: 'pipeline'
|
||||
|
||||
- task: DownloadSecureFile@1
|
||||
name: cerFile
|
||||
@@ -145,7 +150,6 @@ steps:
|
||||
secureFile: 'Snap.Hutao.CI.cer'
|
||||
|
||||
- task: GitHubRelease@1
|
||||
condition: or(eq(variables['Build.Reason'], 'Manual'), eq(variables['Build.Reason'], 'IndividualCI'), eq(variables['Build.Reason'], 'BatchedCI'))
|
||||
inputs:
|
||||
gitHubConnection: 'github.com_Masterain'
|
||||
repositoryName: 'DGP-Studio/Snap.Hutao'
|
||||
@@ -178,4 +182,4 @@ steps:
|
||||
displayName: Upload CI via Rclone
|
||||
inputs:
|
||||
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix downloadDGPCN:/releases/Alpha/'
|
||||
configPath: '$(RcloneConfigFile.secureFilePath)'
|
||||
configPath: '$(RcloneConfigFile.secureFilePath)'
|
||||
|
||||
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
files:
|
||||
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
|
||||
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.%locale%.resx
|
||||
@@ -25,4 +25,4 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -50,8 +50,8 @@ Global
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.ActiveCfg = Release|x86
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Build.0 = Release|x86
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Deploy.0 = Release|x86
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -66,8 +66,8 @@ Global
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -78,8 +78,8 @@ Global
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.ActiveCfg = Release|x64
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.Build.0 = Release|x64
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"pathSegment": {
|
||||
"add": {
|
||||
".*": [ ".cs" ]
|
||||
".*": [ ".cs", ".resx" ]
|
||||
}
|
||||
},
|
||||
"fileSuffixToExtension": {
|
||||
@@ -19,11 +19,12 @@
|
||||
},
|
||||
"fileToFile": {
|
||||
"add": {
|
||||
".filenesting.json": [ "App.xaml.cs" ],
|
||||
"app.manifest": [ "App.xaml.cs" ],
|
||||
"Package.appxmanifest": [ "App.xaml.cs" ],
|
||||
"GlobalUsing.cs": [ "Program.cs" ],
|
||||
".filenesting.json": [ "Program.cs" ],
|
||||
".editorconfig": [ "Program.cs" ]
|
||||
"Package.appxmanifest": [ "App.xaml" ],
|
||||
"Package.StoreAssociation.xml": [ "App.xaml" ],
|
||||
".editorconfig": [ "Program.cs" ],
|
||||
"GlobalUsing.cs": [ "Program.cs" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,328 @@
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
|
||||
</Style>
|
||||
<Style x:Key="WebView2ContentDialogStyle" TargetType="ContentDialog">
|
||||
<Setter Property="Foreground" Value="{ThemeResource ContentDialogForeground}"/>
|
||||
<Setter Property="Background" Value="{ThemeResource ContentDialogBackground}"/>
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ContentDialogBorderWidth}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ContentDialogBorderBrush}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContentDialog">
|
||||
<Border x:Name="Container">
|
||||
<Grid x:Name="LayoutRoot" Visibility="Collapsed">
|
||||
<Rectangle x:Name="SmokeLayerBackground" Fill="{ThemeResource ContentDialogSmokeFill}"/>
|
||||
<Border
|
||||
x:Name="BackgroundElement"
|
||||
MinWidth="{ThemeResource ContentDialogMinWidth}"
|
||||
MinHeight="{ThemeResource ContentDialogMinHeight}"
|
||||
MaxWidth="{ThemeResource ContentDialogMaxWidth}"
|
||||
MaxHeight="{ThemeResource ContentDialogMaxHeight}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="InnerBorderEdge"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
FlowDirection="{TemplateBinding FlowDirection}"
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<Border.RenderTransform>
|
||||
<ScaleTransform x:Name="ScaleTransform"/>
|
||||
</Border.RenderTransform>
|
||||
<Grid x:Name="DialogSpace" CornerRadius="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer
|
||||
x:Name="ContentScrollViewer"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
IsTabStop="False"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
ZoomMode="Disabled">
|
||||
<Grid
|
||||
Padding="0"
|
||||
BorderBrush="{ThemeResource ContentDialogSeparatorBorderBrush}"
|
||||
BorderThickness="{ThemeResource ContentDialogSeparatorThickness}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
x:Name="Title"
|
||||
Margin="{ThemeResource ContentDialogTitleMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Content="{TemplateBinding Title}"
|
||||
ContentTemplate="{TemplateBinding TitleTemplate}"
|
||||
FontFamily="{StaticResource ContentControlThemeFontFamily}"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
IsTabStop="False">
|
||||
<ContentControl.Template>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
MaxLines="2"
|
||||
TextWrapping="Wrap"/>
|
||||
</ControlTemplate>
|
||||
</ContentControl.Template>
|
||||
</ContentControl>
|
||||
<ContentPresenter
|
||||
x:Name="Content"
|
||||
Grid.Row="1"
|
||||
Margin="0,0,0,8"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
FontFamily="{StaticResource ContentControlThemeFontFamily}"
|
||||
FontSize="{StaticResource ControlContentThemeFontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<Grid
|
||||
x:Name="CommandSpace"
|
||||
Grid.Row="1"
|
||||
Padding="8,0,8,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
XYFocusKeyboardNavigation="Enabled">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="PrimaryColumn" Width="*"/>
|
||||
<ColumnDefinition x:Name="FirstSpacer" Width="0"/>
|
||||
<ColumnDefinition x:Name="SecondaryColumn" Width="0"/>
|
||||
<ColumnDefinition x:Name="SecondSpacer" Width="{ThemeResource ContentDialogButtonSpacing}"/>
|
||||
<ColumnDefinition x:Name="CloseColumn" Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
x:Name="PrimaryButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{TemplateBinding PrimaryButtonText}"
|
||||
ElementSoundMode="FocusOnly"
|
||||
IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}"
|
||||
IsTabStop="False"
|
||||
Style="{TemplateBinding PrimaryButtonStyle}"/>
|
||||
<Button
|
||||
x:Name="SecondaryButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{TemplateBinding SecondaryButtonText}"
|
||||
ElementSoundMode="FocusOnly"
|
||||
IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}"
|
||||
IsTabStop="False"
|
||||
Style="{TemplateBinding SecondaryButtonStyle}"/>
|
||||
<Button
|
||||
x:Name="CloseButton"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{TemplateBinding CloseButtonText}"
|
||||
ElementSoundMode="FocusOnly"
|
||||
IsTabStop="False"
|
||||
Style="{TemplateBinding CloseButtonStyle}"/>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="DialogShowingStates">
|
||||
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition To="DialogHidden">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="IsHitTestVisible">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="False"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFastAnimationDuration}"
|
||||
Value="1.05"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFastAnimationDuration}"
|
||||
Value="1.05"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0"/>
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0.0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
<VisualTransition To="DialogShowing">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05"/>
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||
Value="1.0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05"/>
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||
Value="1.0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.0"/>
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1.0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
</VisualStateGroup.Transitions>
|
||||
<VisualState x:Name="DialogHidden"/>
|
||||
<VisualState x:Name="DialogShowing">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PrimaryButton.IsTabStop" Value="True"/>
|
||||
<Setter Target="SecondaryButton.IsTabStop" Value="True"/>
|
||||
<Setter Target="CloseButton.IsTabStop" Value="True"/>
|
||||
<Setter Target="LayoutRoot.Visibility" Value="Visible"/>
|
||||
<Setter Target="BackgroundElement.TabFocusNavigation" Value="Cycle"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="DialogShowingWithoutSmokeLayer">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PrimaryButton.IsTabStop" Value="True"/>
|
||||
<Setter Target="SecondaryButton.IsTabStop" Value="True"/>
|
||||
<Setter Target="CloseButton.IsTabStop" Value="True"/>
|
||||
<Setter Target="LayoutRoot.Visibility" Value="Visible"/>
|
||||
<Setter Target="LayoutRoot.Background" Value="{x:Null}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="DialogSizingStates">
|
||||
<VisualState x:Name="DefaultDialogSizing"/>
|
||||
<VisualState x:Name="FullDialogSizing">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="BackgroundElement.VerticalAlignment" Value="Stretch"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="ButtonsVisibilityStates">
|
||||
<VisualState x:Name="AllVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="FirstSpacer.Width" Value="{ThemeResource ContentDialogButtonSpacing}"/>
|
||||
<Setter Target="SecondaryColumn.Width" Value="*"/>
|
||||
<Setter Target="SecondaryButton.(Grid.Column)" Value="2"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoneVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CommandSpace.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PrimaryVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PrimaryButton.(Grid.Column)" Value="4"/>
|
||||
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SecondaryVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SecondaryButton.(Grid.Column)" Value="4"/>
|
||||
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CloseVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
|
||||
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PrimaryAndSecondaryVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SecondaryButton.(Grid.Column)" Value="4"/>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PrimaryAndCloseVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SecondaryButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SecondaryAndCloseVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PrimaryButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="DefaultButtonStates">
|
||||
<VisualState x:Name="NoDefaultButton"/>
|
||||
<VisualState x:Name="PrimaryAsDefaultButton">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PrimaryButton.Style" Value="{StaticResource AccentButtonStyle}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SecondaryAsDefaultButton">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SecondaryButton.Style" Value="{StaticResource AccentButtonStyle}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CloseAsDefaultButton">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Style" Value="{StaticResource AccentButtonStyle}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="DialogBorderStates">
|
||||
<VisualState x:Name="NoBorder"/>
|
||||
<VisualState x:Name="AccentColorBorder">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="BackgroundElement.BorderBrush" Value="{ThemeResource SystemControlForegroundAccentBrush}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!-- ItemsPanelTemplate -->
|
||||
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
|
||||
<ItemsStackPanel/>
|
||||
@@ -120,4 +442,4 @@
|
||||
</ItemsPanelTemplate>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
</Application>
|
||||
@@ -49,8 +49,8 @@ public partial class App : Application
|
||||
firstInstance.Activated += Activation.Activate;
|
||||
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.LocalCacheFolder.Path);
|
||||
logger.LogInformation("Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
|
||||
logger.LogInformation("Cache folder : {folder}", ApplicationData.Current.LocalCacheFolder.Path);
|
||||
|
||||
JumpListHelper.ConfigureAsync().SafeForget(logger);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Snap.Hutao.Control;
|
||||
/// <summary>
|
||||
/// 绑定探针
|
||||
/// 用于处理特定情况下需要穿透数据上下文的工作
|
||||
/// DependencyObject will dispose inner ReferenceTracker in any time
|
||||
/// when object is not used anymore.
|
||||
/// </summary>
|
||||
public class BindingProxy : DependencyObject
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Control.Extension;
|
||||
/// <summary>
|
||||
/// 对话框扩展
|
||||
/// </summary>
|
||||
internal static class ContentDialogExtensions
|
||||
internal static class ContentDialogExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 阻止用户交互
|
||||
@@ -32,7 +32,7 @@ public class CachedImage : ImageEx
|
||||
|
||||
try
|
||||
{
|
||||
Verify.Operation(imageUri.Host != string.Empty, "无效的Uri");
|
||||
Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri);
|
||||
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true);
|
||||
|
||||
// check token state to determine whether the operation should be canceled.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
@@ -11,6 +12,7 @@ using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
@@ -24,7 +26,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
|
||||
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
|
||||
|
||||
private readonly IImageCache imageCache;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private SpriteVisual? spriteVisual;
|
||||
private bool isShow = true;
|
||||
@@ -34,8 +36,15 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
/// </summary>
|
||||
public CompositionImage()
|
||||
{
|
||||
imageCache = Ioc.Default.GetRequiredService<IImageCache>();
|
||||
serviceProvider = Ioc.Default.GetRequiredService<IServiceProvider>();
|
||||
|
||||
AllowFocusOnInteraction = false;
|
||||
IsDoubleTapEnabled = false;
|
||||
IsHitTestVisible = false;
|
||||
IsHoldingEnabled = false;
|
||||
IsRightTapEnabled = false;
|
||||
IsTabStop = false;
|
||||
|
||||
SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
@@ -86,11 +95,11 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
|
||||
if (exception is HttpRequestException httpRequestException)
|
||||
{
|
||||
infoBarService.Error(httpRequestException, $"GET {uri}");
|
||||
infoBarService.Error(httpRequestException, string.Format(SH.ControlImageCompositionImageHttpRequest, uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Error(exception, $"应用 {nameof(CompositionImage)} 的源时发生异常");
|
||||
infoBarService.Error(exception.GetBaseException(), SH.ControlImageCompositionImageSystemException);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,26 +133,20 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
|
||||
if (uri != null)
|
||||
{
|
||||
if (uri.Scheme == "ms-appx")
|
||||
{
|
||||
imageSurface = LoadedImageSurface.StartLoadFromUri(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
string storageFile = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true);
|
||||
IImageCache imageCache = serviceProvider.GetRequiredService<IImageCache>();
|
||||
string file = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true);
|
||||
|
||||
try
|
||||
{
|
||||
imageSurface = await LoadImageSurfaceAsync(storageFile, token).ConfigureAwait(true);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
imageCache.Remove(uri.Enumerate());
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
imageCache.Remove(uri.Enumerate());
|
||||
}
|
||||
try
|
||||
{
|
||||
imageSurface = await LoadImageSurfaceAsync(file, token).ConfigureAwait(true);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
imageCache.Remove(uri.Enumerate());
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
imageCache.Remove(uri.Enumerate());
|
||||
}
|
||||
|
||||
if (imageSurface != null)
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Control.Theme;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
@@ -49,12 +50,7 @@ public class MonoChrome : CompositionImage
|
||||
|
||||
private void SetBackgroundColor(CompositionColorBrush backgroundBrush)
|
||||
{
|
||||
ApplicationTheme theme = ActualTheme switch
|
||||
{
|
||||
ElementTheme.Light => ApplicationTheme.Light,
|
||||
ElementTheme.Dark => ApplicationTheme.Dark,
|
||||
_ => Ioc.Default.GetRequiredService<App>().RequestedTheme,
|
||||
};
|
||||
ApplicationTheme theme = ThemeHelper.ElementToApplication(ActualTheme);
|
||||
|
||||
backgroundBrush.Color = theme switch
|
||||
{
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
|
||||
namespace Snap.Hutao.Control.Markup;
|
||||
|
||||
/// <summary>
|
||||
/// Xaml extension to return a <see cref="string"/> value from resource file associated with a resource key
|
||||
/// </summary>
|
||||
[MarkupExtensionReturnType(ReturnType = typeof(string))]
|
||||
public sealed class ResourceStringExtension : MarkupExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets associated ID from resource strings.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string value from resource file associated with a resource key.
|
||||
/// </summary>
|
||||
/// <param name="name">Resource key name.</param>
|
||||
/// <returns>A string value from resource file associated with a resource key.</returns>
|
||||
public static string GetValue(string name)
|
||||
{
|
||||
// This function is needed to accomodate compiled function usage without second paramater,
|
||||
// which doesn't work with optional values.
|
||||
return SH.ResourceManager.GetString(name)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
return GetValue(Name ?? string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,10 @@ namespace Snap.Hutao.Control.Panel;
|
||||
/// <summary>
|
||||
/// 纵横比控件
|
||||
/// </summary>
|
||||
internal class AspectRatio : Microsoft.UI.Xaml.Controls.ContentControl
|
||||
internal class AspectRatio : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
private const double Epsilon = 2.2204460492503131e-016;
|
||||
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AspectRatio>.Depend(nameof(TargetWidth), 1D);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AspectRatio>.Depend(nameof(TargetHeight), 1D);
|
||||
|
||||
@@ -38,6 +40,11 @@ internal class AspectRatio : Microsoft.UI.Xaml.Controls.ContentControl
|
||||
double ratio = TargetWidth / TargetHeight;
|
||||
double ratioAvailable = availableSize.Width / availableSize.Height;
|
||||
|
||||
if (Math.Abs(ratioAvailable - ratio) < Epsilon)
|
||||
{
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
// 更宽
|
||||
if (ratioAvailable > ratio)
|
||||
{
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="List"
|
||||
Text="列表"/>
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
|
||||
<RadioMenuFlyoutItem
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Tag="Grid"
|
||||
Text="网格"/>
|
||||
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
|
||||
@@ -16,16 +16,34 @@ namespace Snap.Hutao.Control;
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public class ScopedPage : Page
|
||||
{
|
||||
// Allow GC to Collect the IServiceScope
|
||||
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(null!);
|
||||
|
||||
private readonly CancellationTokenSource viewCancellationTokenSource = new();
|
||||
private readonly IServiceScope serviceScope;
|
||||
private readonly IServiceScope currentScope;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的页面
|
||||
/// </summary>
|
||||
public ScopedPage()
|
||||
{
|
||||
serviceScope = Ioc.Default.CreateScope();
|
||||
serviceScope.Track();
|
||||
Unloaded += OnScopedPageUnloaded;
|
||||
currentScope = Ioc.Default.CreateScope();
|
||||
DisposePreviousScope();
|
||||
|
||||
// track current
|
||||
PreviousScopeReference.SetTarget(currentScope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放上个范围
|
||||
/// </summary>
|
||||
public static void DisposePreviousScope()
|
||||
{
|
||||
if (PreviousScopeReference.TryGetTarget(out IServiceScope? scope))
|
||||
{
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +54,7 @@ public class ScopedPage : Page
|
||||
public void InitializeWith<TViewModel>()
|
||||
where TViewModel : class, IViewModel
|
||||
{
|
||||
IViewModel viewModel = serviceScope.ServiceProvider.GetRequiredService<TViewModel>();
|
||||
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
|
||||
viewModel.CancellationToken = viewCancellationTokenSource.Token;
|
||||
DataContext = viewModel;
|
||||
}
|
||||
@@ -59,7 +77,6 @@ public class ScopedPage : Page
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
base.OnNavigatingFrom(e);
|
||||
using (viewCancellationTokenSource)
|
||||
{
|
||||
// Cancel all tasks executed by the view model
|
||||
@@ -73,7 +90,7 @@ public class ScopedPage : Page
|
||||
viewModel.IsViewDisposed = true;
|
||||
|
||||
// Dispose the scope
|
||||
serviceScope.Dispose();
|
||||
currentScope.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,11 +98,15 @@ public class ScopedPage : Page
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
NotifyRecipentAsync(extra).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScopedPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
DataContext = null;
|
||||
Unloaded -= OnScopedPageUnloaded;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Control.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Control.Theme;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Text;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
namespace Snap.Hutao.Control.Theme;
|
||||
|
||||
/// <summary>
|
||||
/// 主题帮助工具类
|
||||
@@ -42,6 +42,21 @@ public static class ThemeHelper
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 <see cref="ElementTheme"/> 转换到 <see cref="ApplicationTheme"/>
|
||||
/// </summary>
|
||||
/// <param name="applicationTheme">元素主题</param>
|
||||
/// <returns>应用主题</returns>
|
||||
public static ApplicationTheme ElementToApplication(ElementTheme applicationTheme)
|
||||
{
|
||||
return applicationTheme switch
|
||||
{
|
||||
ElementTheme.Light => ApplicationTheme.Light,
|
||||
ElementTheme.Dark => ApplicationTheme.Dark,
|
||||
_ => Ioc.Default.GetRequiredService<App>().RequestedTheme,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 <see cref="ElementTheme"/> 转换到 <see cref="SystemBackdropTheme"/>
|
||||
/// </summary>
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 本地化键
|
||||
/// </summary>
|
||||
internal class LocalizationKeyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定本地化键
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
public LocalizationKeyAttribute(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Json;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using System.Collections.Immutable;
|
||||
@@ -36,13 +37,13 @@ internal static class CoreEnvironment
|
||||
/// 盐
|
||||
/// </summary>
|
||||
// https://github.com/UIGF-org/Hoyolab.Salt
|
||||
public static readonly ImmutableDictionary<string, string> DynamicSecrets = new Dictionary<string, string>()
|
||||
public static readonly ImmutableDictionary<SaltType, string> DynamicSecrets = new Dictionary<SaltType, string>()
|
||||
{
|
||||
[nameof(SaltType.K2)] = "dZAwGk4e9aC0MXXItkwnHamjA1x30IYw",
|
||||
[nameof(SaltType.LK2)] = "IEIZiKYaput2OCKQprNuGsog1NZc1FkS",
|
||||
[nameof(SaltType.X4)] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
|
||||
[nameof(SaltType.X6)] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
|
||||
[nameof(SaltType.PROD)] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
|
||||
[SaltType.K2] = "dZAwGk4e9aC0MXXItkwnHamjA1x30IYw",
|
||||
[SaltType.LK2] = "IEIZiKYaput2OCKQprNuGsog1NZc1FkS",
|
||||
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
|
||||
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
|
||||
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
/// <summary>
|
||||
@@ -70,6 +71,11 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public static readonly string FamilyName;
|
||||
|
||||
/// <summary>
|
||||
/// 安装位置
|
||||
/// </summary>
|
||||
public static readonly string InstalledLocation;
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹
|
||||
/// </summary>
|
||||
@@ -98,9 +104,10 @@ internal static class CoreEnvironment
|
||||
|
||||
static CoreEnvironment()
|
||||
{
|
||||
DataFolder = GetDocumentsHutaoPath();
|
||||
DataFolder = GetDatafolderPath();
|
||||
Version = Package.Current.Id.Version.ToVersion();
|
||||
FamilyName = Package.Current.Id.FamilyName;
|
||||
InstalledLocation = Package.Current.InstalledLocation.Path;
|
||||
CommonUA = $"Snap Hutao/{Version}";
|
||||
|
||||
// simply assign a random guid
|
||||
@@ -115,18 +122,27 @@ internal static class CoreEnvironment
|
||||
return Convert.ToMd5HexString($"{userName}{machineGuid}");
|
||||
}
|
||||
|
||||
private static string GetDocumentsHutaoPath()
|
||||
private static string GetDatafolderPath()
|
||||
{
|
||||
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
|
||||
|
||||
if (string.IsNullOrEmpty(preferredPath))
|
||||
{
|
||||
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
#if RELEASE
|
||||
// 将测试版与正式版的文件目录分离
|
||||
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
|
||||
// 将测试版与正式版的文件目录分离
|
||||
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
|
||||
#else
|
||||
// 使得迁移能正常生成
|
||||
string folderName = "Hutao";
|
||||
// 使得迁移能正常生成
|
||||
string folderName = "Hutao";
|
||||
#endif
|
||||
string path = Path.GetFullPath(Path.Combine(myDocument, folderName));
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
string path = Path.GetFullPath(Path.Combine(myDocument, folderName));
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
else
|
||||
{
|
||||
return preferredPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库集合上下文
|
||||
/// 数据库集合扩展
|
||||
/// </summary>
|
||||
public static class DbSetExtension
|
||||
{
|
||||
@@ -134,4 +134,4 @@ public static class DbSetExtension
|
||||
dbSet.Update(entity);
|
||||
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可枚举扩展
|
||||
/// </summary>
|
||||
public static class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取选中的值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>选中的值或默认值</returns>
|
||||
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
|
||||
where TSource : ISelectable
|
||||
{
|
||||
return source.SingleOrDefault(i => i.IsSelected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可查询扩展
|
||||
/// </summary>
|
||||
public static class QueryableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// source.Where(predicate).ExecuteDeleteAsync(token)
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <param name="predicate">条件</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>SQL返回个数</returns>
|
||||
public static Task<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
|
||||
{
|
||||
return source.Where(predicate).ExecuteDeleteAsync(token);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ internal static class IocConfiguration
|
||||
/// </summary>
|
||||
/// <param name="services">集合</param>
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
public static IServiceCollection AddJsonSerializerOptions(this IServiceCollection services)
|
||||
public static IServiceCollection AddJsonOptions(this IServiceCollection services)
|
||||
{
|
||||
return services.AddSingleton(CoreEnvironment.JsonOptions);
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 服务范围扩展
|
||||
/// </summary>
|
||||
public static class ServiceScopeExtension
|
||||
{
|
||||
// Allow GC to Collect the IServiceScope
|
||||
private static readonly WeakReference<IServiceScope> ScopeReference = new(null!);
|
||||
|
||||
/// <summary>
|
||||
/// 追踪服务范围
|
||||
/// </summary>
|
||||
/// <param name="scope">范围</param>
|
||||
public static void Track(this IServiceScope scope)
|
||||
{
|
||||
DisposeLast();
|
||||
ScopeReference.SetTarget(scope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放上个范围
|
||||
/// </summary>
|
||||
public static void DisposeLast()
|
||||
{
|
||||
if (ScopeReference.TryGetTarget(out IServiceScope? scope))
|
||||
{
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
@@ -33,12 +35,16 @@ internal class ExceptionRecorder
|
||||
Ioc.Default.GetRequiredService<Web.Hutao.HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
#pragma warning restore VSTHRD002
|
||||
#endif
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
|
||||
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())
|
||||
StringBuilder dataDetailBuilder = new();
|
||||
foreach (DictionaryEntry entry in e.Exception.Data)
|
||||
{
|
||||
provider.Dispose();
|
||||
string key = $"{entry.Key}";
|
||||
string value = $"{entry.Value}";
|
||||
|
||||
dataDetailBuilder.Append(key).Append(':').Append(value).Append("\r\n");
|
||||
}
|
||||
|
||||
logger.LogError(e.Exception, "未经处理的异常\r\n{detail}", dataDetailBuilder.ToString());
|
||||
}
|
||||
|
||||
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 帮助更好的抛出异常
|
||||
/// </summary>
|
||||
[System.Diagnostics.StackTraceHidden]
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作取消
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <exception cref="OperationCanceledException">操作取消异常</exception>
|
||||
/// <returns>nothing</returns>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner)
|
||||
{
|
||||
throw new OperationCanceledException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 包转换错误
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="PackageConvertException">包转换错误异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static PackageConvertException PackageConvert(string message, Exception inner)
|
||||
{
|
||||
throw new PackageConvertException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据损坏
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <exception cref="UserdataCorruptedException">数据损坏</exception>
|
||||
/// <returns>nothing</returns>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner)
|
||||
{
|
||||
throw new UserdataCorruptedException(message, inner);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ internal class UserdataCorruptedException : Exception
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public UserdataCorruptedException(string message, Exception innerException)
|
||||
: base($"用户数据已损坏: {message}", innerException)
|
||||
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, message), innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
/// </summary>
|
||||
public const string JobNamePrefix = "SnapHutaoBitsJob";
|
||||
|
||||
private const uint BitsEngineNoProgressTimeout = 120;
|
||||
private const uint BitsEngineNoProgressTimeout = 30;
|
||||
private const int MaxResumeAttempts = 10;
|
||||
|
||||
private readonly string displayName;
|
||||
@@ -30,7 +30,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
private readonly object lockObj = new();
|
||||
|
||||
private IBackgroundCopyJob? nativeJob;
|
||||
private System.Exception? jobException;
|
||||
private Exception? jobException;
|
||||
private BG_JOB_PROGRESS progress;
|
||||
private BG_JOB_STATE state;
|
||||
private bool isJobComplete;
|
||||
@@ -79,7 +79,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
UpdateJobState();
|
||||
CompleteOrCancel();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogInformation("Failed to job transfer: {message}", ex.Message);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
CompleteOrCancel();
|
||||
log.LogInformation(jobException, "Job Exception:");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.LogInformation("Failed to handle job error: {message}", ex.Message);
|
||||
}
|
||||
@@ -141,7 +141,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
CompleteOrCancel();
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogInformation(ex, "message");
|
||||
}
|
||||
@@ -283,7 +283,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogInformation("{name} failed. {exception}", displayName, ex);
|
||||
if (throwOnFailure)
|
||||
|
||||
@@ -39,7 +39,8 @@ internal class BitsManager
|
||||
public async Task<ValueResult<bool, TempFile>> DownloadAsync(Uri uri, IProgress<ProgressUpdateStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
TempFile tempFile = new(true);
|
||||
bool result = await Task.Run(() => DownloadCore(uri, tempFile.Path, progress.Report, token), token).ConfigureAwait(false);
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
bool result = DownloadCore(uri, tempFile.Path, progress.Report, token);
|
||||
return new(result, tempFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Bits;
|
||||
|
||||
/// <summary>
|
||||
/// 进度更新状态
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{BytesRead}/{TotalBytes}")]
|
||||
public class ProgressUpdateStatus
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -20,7 +20,6 @@ internal static class Clipboard
|
||||
public static async Task<T?> DeserializeTextAsync<T>(JsonSerializerOptions options)
|
||||
where T : class
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
|
||||
string json = await view.GetTextAsync();
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
|
||||
42
src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs
Normal file
42
src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 摘要
|
||||
/// </summary>
|
||||
internal static class Digest
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取文件 Md5 摘要
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>文件 Md5 摘要</returns>
|
||||
public static async Task<string> GetFileMd5Async(string filePath, CancellationToken token = default)
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(filePath))
|
||||
{
|
||||
return await GetStreamMd5Async(stream, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取流的 Md5 摘要
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>流 Md5 摘要</returns>
|
||||
public static async Task<string> GetStreamMd5Async(Stream stream, CancellationToken token = default)
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
|
||||
return System.Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 文件摘要
|
||||
/// </summary>
|
||||
internal static class FileDigest
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取文件 Md5 摘要
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>文件 Md5 摘要</returns>
|
||||
public static async Task<string> GetMd5Async(string filePath, CancellationToken token)
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(filePath))
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
|
||||
return System.Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 文件操作
|
||||
/// </summary>
|
||||
internal static class FileOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// 将指定文件移动到新位置,提供指定新文件名和覆盖目标文件(如果它已存在)的选项。
|
||||
/// </summary>
|
||||
/// <param name="sourceFileName">要移动的文件的名称。 可以包括相对或绝对路径。</param>
|
||||
/// <param name="destFileName">文件的新路径和名称。</param>
|
||||
/// <param name="overwrite">如果要覆盖目标文件</param>
|
||||
/// <returns>是否发生了移动操作</returns>
|
||||
public static bool Move(string sourceFileName, string destFileName, bool overwrite)
|
||||
{
|
||||
if (File.Exists(sourceFileName))
|
||||
{
|
||||
if (overwrite)
|
||||
{
|
||||
File.Move(sourceFileName, destFileName, overwrite);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(destFileName))
|
||||
{
|
||||
File.Move(sourceFileName, destFileName, overwrite);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,7 @@ internal static class PickerExtension
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<Service.Abstraction.IInfoBarService>()
|
||||
.Warning("无法打开文件选择器", $"请勿在管理员模式下使用此功能 {exception.Message}");
|
||||
}
|
||||
|
||||
InfoBarWaringPickerException(exception);
|
||||
return new(false, null!);
|
||||
}
|
||||
}
|
||||
@@ -64,14 +58,46 @@ internal static class PickerExtension
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<Service.Abstraction.IInfoBarService>()
|
||||
.Warning("无法打开文件选择器", $"请勿在管理员模式下使用此功能 {exception.Message}");
|
||||
}
|
||||
|
||||
InfoBarWaringPickerException(exception);
|
||||
return new(false, null!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="FolderPicker.PickSingleFolderAsync"/>
|
||||
public static async Task<ValueResult<bool, string>> TryPickSingleFolderAsync(this FolderPicker picker)
|
||||
{
|
||||
StorageFolder? folder;
|
||||
Exception? exception = null;
|
||||
try
|
||||
{
|
||||
folder = await picker.PickSingleFolderAsync().AsTask().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
folder = null;
|
||||
}
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
return new(true, folder.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
InfoBarWaringPickerException(exception);
|
||||
return new(false, null!);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InfoBarWaringPickerException(Exception? exception)
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<Service.Abstraction.IInfoBarService>()
|
||||
.Warning(
|
||||
SH.CoreIOPickerExtensionPickerExceptionInfoBarTitle,
|
||||
string.Format(SH.CoreIOPickerExtensionPickerExceptionInfoBarMessage, exception.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,12 @@ internal sealed class TempFile : IDisposable
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
File.Delete(Path);
|
||||
try
|
||||
{
|
||||
File.Delete(Path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ public static class JumpListHelper
|
||||
|
||||
list.Items.Clear();
|
||||
|
||||
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, "启动游戏");
|
||||
launchGameItem.GroupName = "快捷操作";
|
||||
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName);
|
||||
launchGameItem.GroupName = SH.CoreJumpListHelperLaunchGameItemGroupName;
|
||||
launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png");
|
||||
|
||||
list.Items.Add(launchGameItem);
|
||||
|
||||
@@ -10,7 +10,9 @@ using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
#if RELEASE
|
||||
using System.Security.Principal;
|
||||
#endif
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
@@ -37,11 +39,15 @@ internal static class Activation
|
||||
/// <returns>是否提升了权限</returns>
|
||||
public static bool GetElevated()
|
||||
{
|
||||
#if RELEASE
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="ILoggerFactory"/> class.
|
||||
/// </summary>
|
||||
public static class DatabaseLoggerFactoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a debug logger named 'Debug' to the factory.
|
||||
/// </summary>
|
||||
/// <param name="builder">The extension method argument.</param>
|
||||
/// <returns>日志构造器</returns>
|
||||
public static ILoggingBuilder AddDatabase(this ILoggingBuilder builder)
|
||||
{
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DatebaseLoggerProvider>());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// A logger that writes messages in the database table
|
||||
/// </summary>
|
||||
internal sealed partial class DatebaseLogger : ILogger
|
||||
{
|
||||
private readonly string name;
|
||||
private readonly LogEntryQueue logEntryQueue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DatebaseLogger"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the logger.</param>
|
||||
/// <param name="logEntryQueue">日志队列</param>
|
||||
public DatebaseLogger(string name, LogEntryQueue logEntryQueue)
|
||||
{
|
||||
this.name = name;
|
||||
this.logEntryQueue = logEntryQueue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
where TState : notnull
|
||||
{
|
||||
return new NullScope();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return logLevel != LogLevel.None;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, System.Exception? exception, Func<TState, System.Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string message = formatter(state, exception);
|
||||
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogEntry entry = new()
|
||||
{
|
||||
Time = DateTimeOffset.Now,
|
||||
Category = name,
|
||||
LogLevel = logLevel,
|
||||
EventId = eventId.Id,
|
||||
Message = message,
|
||||
Exception = exception?.ToString(),
|
||||
};
|
||||
|
||||
logEntryQueue.Enqueue(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An empty scope without any logic
|
||||
/// </summary>
|
||||
private struct NullScope : IDisposable
|
||||
{
|
||||
public NullScope()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The provider for the <see cref="DatebaseLogger"/>.
|
||||
/// </summary>
|
||||
[ProviderAlias("Database")]
|
||||
public sealed class DatebaseLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly LogEntryQueue logEntryQueue = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new DatebaseLogger(name, logEntryQueue);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
logEntryQueue.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库日志入口点
|
||||
/// </summary>
|
||||
[Table("logs")]
|
||||
public class LogEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志时间
|
||||
/// </summary>
|
||||
public DateTimeOffset Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类别
|
||||
/// </summary>
|
||||
public string Category { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 日志等级
|
||||
/// </summary>
|
||||
public LogLevel LogLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件Id
|
||||
/// </summary>
|
||||
public int EventId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 可能的异常
|
||||
/// </summary>
|
||||
public string? Exception { get; set; }
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 日志队列
|
||||
/// </summary>
|
||||
public sealed class LogEntryQueue : IDisposable
|
||||
{
|
||||
private readonly ConcurrentQueue<LogEntry> entryQueue = new();
|
||||
private readonly CancellationTokenSource disposeTokenSource = new();
|
||||
private readonly TaskCompletionSource writeDbCompletionSource = new();
|
||||
private readonly LogDbContext logDbContext;
|
||||
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的日志队列
|
||||
/// </summary>
|
||||
public LogEntryQueue()
|
||||
{
|
||||
logDbContext = InitializeDbContext();
|
||||
|
||||
Task.Run(() => WritePendingLogsAsync(disposeTokenSource.Token)).SafeForget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将日志消息存入队列
|
||||
/// </summary>
|
||||
/// <param name="logEntry">日志</param>
|
||||
public void Enqueue(LogEntry logEntry)
|
||||
{
|
||||
entryQueue.Enqueue(logEntry);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// notify the write task to complete.
|
||||
disposeTokenSource.Cancel();
|
||||
|
||||
// Wait the db operation complete.
|
||||
writeDbCompletionSource.Task.GetAwaiter().GetResult();
|
||||
|
||||
logDbContext.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private static LogDbContext InitializeDbContext()
|
||||
{
|
||||
string logDbName = System.IO.Path.Combine(CoreEnvironment.DataFolder, "Log.db");
|
||||
LogDbContext logDbContext = LogDbContext.Create($"Data Source={logDbName}");
|
||||
if (logDbContext.Database.GetPendingMigrations().Any())
|
||||
{
|
||||
Debug.WriteLine("[Debug] Performing LogDbContext Migrations");
|
||||
logDbContext.Database.Migrate();
|
||||
}
|
||||
|
||||
// only raw sql can pass
|
||||
logDbContext.Logs.Where(log => log.Exception == null).ExecuteDelete();
|
||||
return logDbContext;
|
||||
}
|
||||
|
||||
private async Task WritePendingLogsAsync(CancellationToken token)
|
||||
{
|
||||
bool hasAdded = false;
|
||||
while (true)
|
||||
{
|
||||
if (entryQueue.TryDequeue(out LogEntry? logEntry))
|
||||
{
|
||||
logDbContext.Logs.Add(logEntry);
|
||||
hasAdded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasAdded)
|
||||
{
|
||||
logDbContext.SaveChanges();
|
||||
hasAdded = false;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
writeDbCompletionSource.TrySetResult();
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(5000, token).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ internal static class ScheduleTaskHelper
|
||||
}
|
||||
|
||||
TaskDefinition task = TaskService.Instance.NewTask();
|
||||
task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。";
|
||||
task.RegistrationInfo.Description = SH.CoreScheduleTaskHelperDailyNoteRefreshTaskDescription;
|
||||
task.Triggers.Add(new TimeTrigger() { Repetition = new(TimeSpan.FromSeconds(interval), TimeSpan.Zero), });
|
||||
task.Actions.Add("explorer", "hutao://DailyNote/Refresh");
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
|
||||
@@ -77,6 +77,12 @@ internal static class LocalSetting
|
||||
return Get<char>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static string Get(string key, string defaultValue)
|
||||
{
|
||||
return Get<string>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static DateTimeOffset Get(string key, DateTimeOffset defaultValue)
|
||||
{
|
||||
@@ -173,6 +179,12 @@ internal static class LocalSetting
|
||||
Set<char>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, string value)
|
||||
{
|
||||
Set<string>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, DateTimeOffset value)
|
||||
{
|
||||
@@ -216,8 +228,7 @@ internal static class LocalSetting
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>获取的值</returns>
|
||||
private static T Get<T>(string key, T defaultValue = default)
|
||||
where T : struct
|
||||
private static T Get<T>(string key, T defaultValue = default!)
|
||||
{
|
||||
if (Container.Values.TryGetValue(key, out object? value))
|
||||
{
|
||||
@@ -238,7 +249,6 @@ internal static class LocalSetting
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
private static void Set<T>(string key, T value)
|
||||
where T : struct
|
||||
{
|
||||
Container.Values[key] = value;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ internal static class SettingKeys
|
||||
/// </summary>
|
||||
public const string LaunchTimes = "LaunchTimes";
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹
|
||||
/// </summary>
|
||||
public const string DataFolderPath = "DataFolderPath";
|
||||
|
||||
/// <summary>
|
||||
/// 静态资源合约
|
||||
/// 新增合约时 请注意
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// 信号量扩展
|
||||
/// </summary>
|
||||
public static class SemaphoreSlimExtensions
|
||||
public static class SemaphoreSlimExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步进入信号量
|
||||
@@ -22,7 +24,7 @@ public static class SemaphoreSlimExtensions
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
throw new OperationCanceledException("信号量已经被释放,操作取消", ex);
|
||||
ThrowHelper.OperationCanceled(SH.CoreThreadingSemaphoreSlimDisposed, ex);
|
||||
}
|
||||
|
||||
return new SemaphoreSlimReleaser(semaphoreSlim);
|
||||
@@ -22,7 +22,7 @@ public static class TaskExtensions
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
@@ -39,11 +39,11 @@ public static class TaskExtensions
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
catch (System.Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
}
|
||||
@@ -55,17 +55,17 @@ public static class TaskExtensions
|
||||
/// <param name="task">任务</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="onException">发生异常时调用</param>
|
||||
public static async void SafeForget(this Task task, ILogger? logger = null, Action<System.Exception>? onException = null)
|
||||
public static async void SafeForget(this Task task, ILogger? logger = null, Action<Exception>? onException = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
catch (System.Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
onException?.Invoke(e);
|
||||
@@ -79,17 +79,17 @@ public static class TaskExtensions
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="onCanceled">任务取消时调用</param>
|
||||
/// <param name="onException">发生异常时调用</param>
|
||||
public static async void SafeForget(this Task task, ILogger? logger = null, Action? onCanceled = null, Action<System.Exception>? onException = null)
|
||||
public static async void SafeForget(this Task task, ILogger? logger = null, Action? onCanceled = null, Action<Exception>? onException = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
onCanceled?.Invoke();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException());
|
||||
onException?.Invoke(e);
|
||||
|
||||
@@ -30,13 +30,13 @@ public readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitchOp
|
||||
/// <inheritdoc/>
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
QueueContinuation(continuation, flowContext: true);
|
||||
QueueContinuation(continuation, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
QueueContinuation(continuation, flowContext: false);
|
||||
QueueContinuation(continuation, false);
|
||||
}
|
||||
|
||||
private static void QueueContinuation(Action continuation, bool flowContext)
|
||||
|
||||
@@ -44,7 +44,7 @@ public static class Must
|
||||
/// <param name="context">上下文</param>
|
||||
/// <returns>Nothing. This method always throws.</returns>
|
||||
[DoesNotReturn]
|
||||
public static System.Exception NeverHappen(string? context = null)
|
||||
public static Exception NeverHappen(string? context = null)
|
||||
{
|
||||
throw new NotSupportedException(context);
|
||||
}
|
||||
@@ -62,21 +62,4 @@ public static class Must
|
||||
{
|
||||
return value ?? throw new ArgumentNullException(parameterName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentNullException"/> if the specified parameter's value is IntPtr.Zero.
|
||||
/// </summary>
|
||||
/// <param name="value">The value of the argument.</param>
|
||||
/// <param name="parameterName">The name of the parameter to include in any thrown exception.</param>
|
||||
/// <returns>The value of the parameter.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is <see cref="IntPtr.Zero"/>.</exception>
|
||||
public static Windows.Win32.Foundation.HWND NotNull(Windows.Win32.Foundation.HWND value, [CallerArgumentExpression("value")] string? parameterName = null)
|
||||
{
|
||||
if (value == default)
|
||||
{
|
||||
throw new ArgumentNullException(parameterName);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ internal abstract class WebView2Helper
|
||||
{
|
||||
private static bool hasEverDetected;
|
||||
private static bool isSupported;
|
||||
private static string version = "未检测到 WebView2 运行时";
|
||||
private static string version = SH.CoreWebView2HelperVersionUndetected;
|
||||
|
||||
/// <summary>
|
||||
/// 检测 WebView2 是否存在
|
||||
@@ -36,7 +36,7 @@ internal abstract class WebView2Helper
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
ILogger<WebView2Helper> logger = Ioc.Default.GetRequiredService<ILogger<WebView2Helper>>();
|
||||
logger.LogError(EventIds.WebView2EnvironmentException, ex, "WebView2 运行时未安装");
|
||||
logger.LogError(EventIds.WebView2EnvironmentException, ex, "WebView2 Runtime not installed.");
|
||||
isSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +124,8 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMe
|
||||
|
||||
private void InitializeWindow()
|
||||
{
|
||||
appWindow.Title = "胡桃";
|
||||
appWindow.SetIcon(Path.Combine(Package.Current.InstalledLocation.Path, "Assets/Logos/Logo.ico"));
|
||||
appWindow.Title = string.Format(SH.AppNameAndVersion, CoreEnvironment.Version);
|
||||
appWindow.SetIcon(Path.Combine(CoreEnvironment.InstalledLocation, "Assets/Logo.ico"));
|
||||
ExtendsContentIntoTitleBar();
|
||||
|
||||
Persistence.RecoverOrInit(appWindow, window.PersistSize, window.InitSize);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control.Theme;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
@@ -140,7 +141,7 @@ public class SystemBackdrop
|
||||
/// <summary>
|
||||
/// 确保系统调度队列控制器存在
|
||||
/// </summary>
|
||||
public void Ensure()
|
||||
public unsafe void Ensure()
|
||||
{
|
||||
if (DispatcherQueue.GetForCurrentThread() != null)
|
||||
{
|
||||
@@ -152,7 +153,7 @@ public class SystemBackdrop
|
||||
{
|
||||
DispatcherQueueOptions options = new()
|
||||
{
|
||||
DwSize = Marshal.SizeOf<DispatcherQueueOptions>(),
|
||||
DwSize = sizeof(DispatcherQueueOptions),
|
||||
ThreadType = 2, // DQTYPE_THREAD_CURRENT
|
||||
ApartmentType = 2, // DQTAT_COM_STA
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
public WindowSubclassManager(TWindow window, HWND hwnd, bool isLegacyDragBar)
|
||||
{
|
||||
this.window = window;
|
||||
this.hwnd = Must.NotNull(hwnd);
|
||||
this.hwnd = hwnd;
|
||||
this.isLegacyDragBar = isLegacyDragBar;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
/// 尝试设置窗体子类
|
||||
/// </summary>
|
||||
/// <returns>是否设置成功</returns>
|
||||
public bool TrySetWindowSubclass()
|
||||
public unsafe bool TrySetWindowSubclass()
|
||||
{
|
||||
windowProc = new(OnSubclassProcedure);
|
||||
bool windowHooked = SetWindowSubclass(hwnd, windowProc, WindowSubclassId, 0);
|
||||
@@ -115,4 +115,4 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
|
||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,22 +8,6 @@ namespace Snap.Hutao.Extension;
|
||||
/// </summary>
|
||||
public static class DateTimeOffsetExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the current <see cref="DateTimeOffset"/> to a <see cref="DateTimeOffset"/> that represents the local time.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeOffset">时间偏移</param>
|
||||
/// <param name="keepTicks">保留主时间部分</param>
|
||||
/// <returns>A <see cref="DateTimeOffset"/> that represents the local time.</returns>
|
||||
public static DateTimeOffset ToLocalTime(this DateTimeOffset dateTimeOffset, bool keepTicks)
|
||||
{
|
||||
if (keepTicks)
|
||||
{
|
||||
dateTimeOffset -= TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now);
|
||||
}
|
||||
|
||||
return dateTimeOffset.ToLocalTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Unix时间戳转换
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Annotation;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
@@ -39,4 +40,25 @@ public static class EnumExtension
|
||||
DescriptionAttribute? attr = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||
return attr?.Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化的描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
/// <param name="enum">枚举值</param>
|
||||
/// <returns>本地化的描述</returns>
|
||||
public static string GetLocalizedDescription<TEnum>(this TEnum @enum)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
string enumName = Enum.GetName(@enum)!;
|
||||
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||
LocalizationKeyAttribute? attr = field?.GetCustomAttribute<LocalizationKeyAttribute>();
|
||||
string? result = null;
|
||||
if (attr != null)
|
||||
{
|
||||
result = SH.ResourceManager.GetString(attr.Key);
|
||||
}
|
||||
|
||||
return result ?? enumName;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// 日志器扩展
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA2254")]
|
||||
public static class LoggerExtension
|
||||
{
|
||||
/// <inheritdoc cref="LoggerExtensions.LogWarning(ILogger, string?, object?[])"/>
|
||||
public static T LogWarning<T>(this ILogger logger, string message, params object?[] param)
|
||||
{
|
||||
logger.LogWarning(message, param);
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
@@ -33,17 +33,4 @@ public static class StringBuilderExtensions
|
||||
{
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串建造器</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="trueValue">条件符合时附加的字符串</param>
|
||||
/// <param name="falseValue">条件不符合时附加的字符串</param>
|
||||
/// <returns>同一个字符串建造器</returns>
|
||||
public static StringBuilder AppendIfElse(this StringBuilder sb, bool condition, string? trueValue, string? falseValue)
|
||||
{
|
||||
return condition ? sb.Append(trueValue) : sb.Append(falseValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ internal class ContentDialogFactory : IContentDialogFactory
|
||||
Title = title,
|
||||
Content = content,
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
PrimaryButtonText = "确认",
|
||||
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
|
||||
};
|
||||
|
||||
return dialog;
|
||||
@@ -75,8 +75,8 @@ internal class ContentDialogFactory : IContentDialogFactory
|
||||
Title = title,
|
||||
Content = content,
|
||||
DefaultButton = defaultButton,
|
||||
PrimaryButtonText = "确认",
|
||||
CloseButtonText = "取消",
|
||||
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
|
||||
CloseButtonText = SH.ContentDialogCancelCloseButtonText,
|
||||
};
|
||||
|
||||
return dialog;
|
||||
|
||||
@@ -13,6 +13,7 @@ global using Snap.Hutao.Core.DependencyInjection;
|
||||
global using Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
global using Snap.Hutao.Core.Threading;
|
||||
global using Snap.Hutao.Core.Validation;
|
||||
global using Snap.Hutao.Resource.Localization;
|
||||
|
||||
// Runtime
|
||||
global using System;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// <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.LogDb
|
||||
{
|
||||
[DbContext(typeof(LogDbContext))]
|
||||
[Migration("20220720121521_Logs")]
|
||||
partial class Logs
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Core.Logging.LogEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EventId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Exception")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LogLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("logs");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations.LogDb
|
||||
{
|
||||
public partial class Logs : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "logs",
|
||||
columns: table => new
|
||||
{
|
||||
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Category = table.Column<string>(type: "TEXT", nullable: false),
|
||||
LogLevel = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
EventId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Message = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Exception = table.Column<string>(type: "TEXT", nullable: true),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_logs", x => x.InnerId);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// <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.LogDb
|
||||
{
|
||||
[DbContext(typeof(LogDbContext))]
|
||||
[Migration("20220903071033_LogTime")]
|
||||
partial class LogTime
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Core.Logging.LogEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EventId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Exception")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LogLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("logs");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations.LogDb
|
||||
{
|
||||
public partial class LogTime : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "Time",
|
||||
table: "logs",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Time",
|
||||
table: "logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations.LogDb
|
||||
{
|
||||
[DbContext(typeof(LogDbContext))]
|
||||
partial class LogDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Core.Logging.LogEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EventId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Exception")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LogLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("logs");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,6 @@ public abstract class WishBase
|
||||
/// </summary>
|
||||
public string TotalCountFormatted
|
||||
{
|
||||
get => $"{TotalCount} 抽";
|
||||
get => string.Format(SH.ModelBingGachaWishBaseTotalCountFormat, TotalCount);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class GachaStatistics
|
||||
/// <summary>
|
||||
/// 奔行世间
|
||||
/// </summary>
|
||||
public TypedWishSummary PermanentWish { get; set; } = default!;
|
||||
public TypedWishSummary StandardWish { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 历史卡池
|
||||
|
||||
@@ -20,7 +20,7 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public string MaxOrangePullFormatted
|
||||
{
|
||||
get => $"最非 {MaxOrangePull} 抽";
|
||||
get => string.Format(SH.ModelBingGachaTypedWishSummaryMaxOrangePullFormat, MaxOrangePull);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +33,7 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public string MinOrangePullFormatted
|
||||
{
|
||||
get => $"最欧 {MinOrangePull} 抽";
|
||||
get => string.Format(SH.ModelBingGachaTypedWishSummaryMinOrangePullFormat, MinOrangePull);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,7 +46,7 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public string LastOrangePullFormatted
|
||||
{
|
||||
get => $"已垫 {LastOrangePull} 抽";
|
||||
get => string.Format(SH.ModelBingGachaTypedWishSummaryLastPullFormat, LastOrangePull);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +64,7 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public string LastPurplePullFormatted
|
||||
{
|
||||
get => $"已垫 {LastPurplePull} 抽";
|
||||
get => string.Format(SH.ModelBingGachaTypedWishSummaryLastPullFormat, LastPurplePull);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,7 +136,7 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public string AverageOrangePullFormatted
|
||||
{
|
||||
get => $"{AverageOrangePull:f2} 抽";
|
||||
get => string.Format(SH.ModelBingGachaTypedWishSummaryAveragePullFormat, AverageOrangePull);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -149,7 +149,7 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public string AverageUpOrangePullFormatted
|
||||
{
|
||||
get => $"{AverageUpOrangePull:f2} 抽";
|
||||
get => string.Format(SH.ModelBingGachaTypedWishSummaryAveragePullFormat, AverageUpOrangePull);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,9 +15,9 @@ public class LaunchScheme
|
||||
/// </summary>
|
||||
public static readonly ImmutableList<LaunchScheme> KnownSchemes = new List<LaunchScheme>()
|
||||
{
|
||||
new LaunchScheme("官方服 | 天空岛", "eYd89JmJ", "18", "1", "1"),
|
||||
new LaunchScheme("渠道服 | 世界树", "KAtdSsoQ", "17", "14", "0"),
|
||||
new LaunchScheme("国际服 | 部分支持", "gcStgarh", "10", "1", "0"),
|
||||
new LaunchScheme(SH.ModelBindingLaunchGameLaunchSchemeChinese, "eYd89JmJ", "18", "1", "1"),
|
||||
new LaunchScheme(SH.ModelBindingLaunchGameLaunchSchemeBilibili, "KAtdSsoQ", "17", "14", "0"),
|
||||
new LaunchScheme(SH.ModelBindingLaunchGameLaunchSchemeOversea, "gcStgarh", "10", "1", "0"),
|
||||
}.ToImmutableList();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 日志数据库上下文
|
||||
/// 由于写入日志的行为需要锁定数据库上下文
|
||||
/// 所以将日志单独分离出来进行读写
|
||||
/// </summary>
|
||||
public class LogDbContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建一个新的
|
||||
/// </summary>
|
||||
/// <param name="options">选项</param>
|
||||
private LogDbContext(DbContextOptions<LogDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志记录
|
||||
/// </summary>
|
||||
public DbSet<LogEntry> Logs { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的日志数据库上下文
|
||||
/// </summary>
|
||||
/// <param name="sqlConnectionString">连接字符串</param>
|
||||
/// <returns>日志数据库上下文</returns>
|
||||
public static LogDbContext Create(string sqlConnectionString)
|
||||
{
|
||||
return new(new DbContextOptionsBuilder<LogDbContext>().UseSqlite(sqlConnectionString).Options);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
namespace Snap.Hutao.Context.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 此类只用于在生成迁移时提供数据库上下文
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class LogDbContextDesignTimeFactory : IDesignTimeDbContextFactory<LogDbContext>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public LogDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
string logDbName = System.IO.Path.Combine(Core.CoreEnvironment.DataFolder, "Log.db");
|
||||
return LogDbContext.Create($"Data Source={logDbName}");
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ public class GameAccount : INotifyPropertyChanged
|
||||
|
||||
/// <summary>
|
||||
/// [MIHOYOSDK_ADL_PROD_CN_h3123967166]
|
||||
/// see <see cref="Service.Game.GameAccountRegistryInterop.SdkKey"/>
|
||||
/// </summary>
|
||||
public string MihoyoSDK { get; set; } = default!;
|
||||
|
||||
|
||||
@@ -22,12 +22,14 @@ public class UIAF
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("info")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public UIAFInfo Info { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("list")]
|
||||
public List<UIAFItem> List { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,21 +13,25 @@ public class UIAFItem
|
||||
/// <summary>
|
||||
/// 成就Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 完成时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前值
|
||||
/// 对于progress为1的项,该属性始终为0
|
||||
/// </summary>
|
||||
[JsonPropertyName("current")]
|
||||
public int Current { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 完成状态
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public AchievementInfoStatus Status { get; set; }
|
||||
}
|
||||
@@ -41,4 +41,21 @@ public class UIGF
|
||||
{
|
||||
return SupportedVersion.Contains(Info?.UIGFVersion ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列表物品是否正常
|
||||
/// </summary>
|
||||
/// <returns>是否正常</returns>
|
||||
public bool IsValidList()
|
||||
{
|
||||
foreach (UIGFItem item in List)
|
||||
{
|
||||
if (item.ItemType != "角色" || item.ItemType != "武器")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ WM_GETMINMAXINFO
|
||||
WM_NCRBUTTONDOWN
|
||||
WM_NCRBUTTONUP
|
||||
|
||||
// Type definition
|
||||
// Type & Enum definition
|
||||
CWMO_FLAGS
|
||||
MINMAXINFO
|
||||
|
||||
|
||||
22
src/Snap.Hutao/Snap.Hutao/Package.StoreAssociation.xml
Normal file
22
src/Snap.Hutao/Snap.Hutao/Package.StoreAssociation.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<StoreAssociation xmlns="http://schemas.microsoft.com/appx/2010/storeassociation">
|
||||
<Publisher>CN=35C8E923-85DF-49A7-9172-B39DC6312C52</Publisher>
|
||||
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
||||
<DeveloperAccountType>MSA</DeveloperAccountType>
|
||||
<GeneratePackageHash>http://www.w3.org/2001/04/xmlenc#sha256</GeneratePackageHash>
|
||||
<SupportedLocales>
|
||||
<Language Code="en-us" InMinimumRequirementSet="true" />
|
||||
<Language Code="zh-cn" InMinimumRequirementSet="true" />
|
||||
</SupportedLocales>
|
||||
<ProductReservedInfo>
|
||||
<MainPackageIdentityName>60568DGPStudio.SnapHutao</MainPackageIdentityName>
|
||||
<ReservedNames>
|
||||
<ReservedName>Snap Hutao</ReservedName>
|
||||
<ReservedName>胡桃工具箱</ReservedName>
|
||||
</ReservedNames>
|
||||
</ProductReservedInfo>
|
||||
<AccountPackageIdentityNames>
|
||||
<MainPackageIdentityName>60568DGPStudio.SnapGenshinResin</MainPackageIdentityName>
|
||||
</AccountPackageIdentityNames>
|
||||
<PackageInfoList LandingUrl="https://developer.microsoft.com/dashboard/Application?appId=9PH4NXJ2JN52" />
|
||||
</StoreAssociation>
|
||||
@@ -4,31 +4,29 @@
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
IgnorableNamespaces="com uap desktop desktop6 rescap">
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
IgnorableNamespaces="com uap desktop rescap mp">
|
||||
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio&comma DissmissedLight"
|
||||
Version="1.3.13.0" />
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.4.11.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
|
||||
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<!--<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />-->
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22621.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="zh-CN"/>
|
||||
<Resource Language="en-us"/>
|
||||
<Resource Language="zh-cn"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
@@ -36,7 +34,7 @@
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="胡桃"
|
||||
DisplayName="Snap Hutao"
|
||||
Description="A Snap Hutao Software"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
@@ -66,6 +64,5 @@
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust"/>
|
||||
<rescap:Capability Name="unvirtualizedResources"/>
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
||||
@@ -5,7 +5,6 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Runtime.InteropServices;
|
||||
using WinRT;
|
||||
|
||||
@@ -33,7 +32,7 @@ public static partial class Program
|
||||
// In a Desktop app this runs a message pump internally,
|
||||
// and does not return until the application shuts down.
|
||||
Application.Start(InitializeApp);
|
||||
ServiceScopeExtension.DisposeLast();
|
||||
Control.ScopedPage.DisposePreviousScope();
|
||||
}
|
||||
|
||||
AppInstance.GetCurrent().UnregisterKey();
|
||||
@@ -54,11 +53,11 @@ public static partial class Program
|
||||
ServiceProvider services = new ServiceCollection()
|
||||
|
||||
// Microsoft extension
|
||||
.AddLogging(builder => builder.AddDebug().AddDatabase())
|
||||
.AddLogging(builder => builder.AddDebug())
|
||||
.AddMemoryCache()
|
||||
|
||||
// Hutao extensions
|
||||
.AddJsonSerializerOptions()
|
||||
.AddJsonOptions()
|
||||
.AddDatebase()
|
||||
.AddInjections()
|
||||
.AddHttpClients()
|
||||
@@ -66,7 +65,7 @@ public static partial class Program
|
||||
// Discrete services
|
||||
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
|
||||
|
||||
.BuildServiceProvider();
|
||||
.BuildServiceProvider(true);
|
||||
|
||||
Ioc.Default.ConfigureServices(services);
|
||||
return services;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"Snap.Hutao (Package)": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false,
|
||||
"doNotLaunchApp": false
|
||||
"doNotLaunchApp": false,
|
||||
"allowLocalNetworkLoopbackProperty": true
|
||||
},
|
||||
"Snap.Hutao (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
|
||||
3798
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
generated
Normal file
3798
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en-US.resx
Normal file
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en-US.resx
Normal file
File diff suppressed because it is too large
Load Diff
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja-JP.resx
Normal file
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja-JP.resx
Normal file
File diff suppressed because it is too large
Load Diff
1365
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
Normal file
1365
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
Normal file
File diff suppressed because it is too large
Load Diff
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ru-RU.resx
Normal file
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ru-RU.resx
Normal file
File diff suppressed because it is too large
Load Diff
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.zh-TW.resx
Normal file
1296
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.zh-TW.resx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -72,8 +72,7 @@ internal class AchievementService : IAchievementService
|
||||
|
||||
// Cascade deleted the achievements.
|
||||
await appDbContext.AchievementArchives
|
||||
.Where(a => a.InnerId == archive.InnerId)
|
||||
.ExecuteDeleteAsync()
|
||||
.ExecuteDeleteWhereAsync(a => a.InnerId == archive.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -115,14 +114,14 @@ internal class AchievementService : IAchievementService
|
||||
List<BindingAchievement> results = new();
|
||||
foreach (MetadataAchievement meta in metadata)
|
||||
{
|
||||
EntityAchievement? entity;
|
||||
EntityAchievement? entity = null;
|
||||
try
|
||||
{
|
||||
entity = entities.SingleOrDefault(e => e.Id == meta.Id);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
throw new UserdataCorruptedException("单个成就存档内发现多个相同的成就 Id", ex);
|
||||
ThrowHelper.UserdataCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
|
||||
}
|
||||
|
||||
entity ??= EntityAchievement.Create(archiveId, meta.Id);
|
||||
@@ -155,7 +154,6 @@ internal class AchievementService : IAchievementService
|
||||
/// <inheritdoc/>
|
||||
public async Task<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy)
|
||||
{
|
||||
// return Task.Run(() => ImportFromUIAF(archive, list, strategy));
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
|
||||
Guid archiveId = archive.InnerId;
|
||||
@@ -191,7 +189,7 @@ internal class AchievementService : IAchievementService
|
||||
public void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements)
|
||||
{
|
||||
string name = archive.Name;
|
||||
logger.LogInformation(EventIds.Achievement, "Begin saving achievements for [{name}]", name);
|
||||
logger.LogInformation("Begin saving achievements for [{name}]", name);
|
||||
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||
|
||||
IEnumerable<EntityAchievement> newData = achievements
|
||||
@@ -201,8 +199,8 @@ internal class AchievementService : IAchievementService
|
||||
ImportResult result = achievementDbOperation.Overwrite(archive.InnerId, newData);
|
||||
|
||||
double time = stopwatch.GetElapsedTime().TotalMilliseconds;
|
||||
logger.LogInformation(EventIds.Achievement, "{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove);
|
||||
logger.LogInformation(EventIds.Achievement, "Save achievements for [{name}] completed in {time}ms", name, time);
|
||||
logger.LogInformation("{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove);
|
||||
logger.LogInformation("Save achievements for [{name}] completed in {time}ms", name, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -57,7 +57,7 @@ internal partial class AnnouncementService : IAnnouncementService
|
||||
Dictionary<int, string> contentMap = contents
|
||||
.ToDictionary(id => id.AnnId, content => content.Content);
|
||||
|
||||
// 将活动公告置于上方
|
||||
// 将活动公告置于前方
|
||||
wrapper.List.Reverse();
|
||||
|
||||
// 将公告内容联入公告列表
|
||||
|
||||
@@ -60,7 +60,7 @@ internal class SummaryAvatarFactory
|
||||
FetterLevel = avatarInfo.FetterInfo?.ExpLevel ?? 0,
|
||||
Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap),
|
||||
CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}",
|
||||
LevelNumber = int.Parse(avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value ?? string.Empty),
|
||||
LevelNumber = avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].ValueInt32 ?? 0,
|
||||
|
||||
// processed webinfo part
|
||||
Weapon = reliquaryAndWeapon.Weapon,
|
||||
|
||||
@@ -35,11 +35,10 @@ internal class SummaryFactoryImplementation
|
||||
return new()
|
||||
{
|
||||
Player = SummaryHelper.CreatePlayer(playerInfo),
|
||||
Avatars = avatarInfos.Where(a => !AvatarIds.IsPlayer(a.AvatarId)).Select(a =>
|
||||
{
|
||||
SummaryAvatarFactory summaryAvatarFactory = new(metadataContext, a);
|
||||
return summaryAvatarFactory.CreateAvatar();
|
||||
}).ToList(),
|
||||
Avatars = avatarInfos
|
||||
.Where(a => !AvatarIds.IsPlayer(a.AvatarId))
|
||||
.Select(a => new SummaryAvatarFactory(metadataContext, a).CreateAvatar())
|
||||
.ToList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,10 @@ internal class CultivationService : ICultivationService
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
await scope.ServiceProvider.GetRequiredService<AppDbContext>().CultivateProjects.RemoveAndSaveAsync(project).ConfigureAwait(false);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.CultivateProjects
|
||||
.ExecuteDeleteWhereAsync(p => p.InnerId == project.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ internal class DailyNoteNotifier
|
||||
if (!entry.ResinNotifySuppressed)
|
||||
{
|
||||
notifyInfos.Add(new(
|
||||
"原粹树脂",
|
||||
SH.ServiceDailyNoteNotifierResin,
|
||||
"ms-appx:///Resource/Icon/UI_ItemIcon_210_256.png",
|
||||
$"{entry.DailyNote.CurrentResin}",
|
||||
$"当前原粹树脂:{entry.DailyNote.CurrentResin}"));
|
||||
string.Format(SH.ServiceDailyNoteNotifierResinCurrent, entry.DailyNote.CurrentResin)));
|
||||
entry.ResinNotifySuppressed = true;
|
||||
}
|
||||
}
|
||||
@@ -71,10 +71,10 @@ internal class DailyNoteNotifier
|
||||
if (!entry.HomeCoinNotifySuppressed)
|
||||
{
|
||||
notifyInfos.Add(new(
|
||||
"洞天宝钱",
|
||||
SH.ServiceDailyNoteNotifierHomeCoin,
|
||||
"ms-appx:///Resource/Icon/UI_ItemIcon_204.png",
|
||||
$"{entry.DailyNote.CurrentHomeCoin}",
|
||||
$"当前洞天宝钱:{entry.DailyNote.CurrentHomeCoin}"));
|
||||
string.Format(SH.ServiceDailyNoteNotifierHomeCoinCurrent, entry.DailyNote.CurrentHomeCoin)));
|
||||
entry.HomeCoinNotifySuppressed = true;
|
||||
}
|
||||
}
|
||||
@@ -88,9 +88,9 @@ internal class DailyNoteNotifier
|
||||
if (!entry.DailyTaskNotifySuppressed)
|
||||
{
|
||||
notifyInfos.Add(new(
|
||||
"每日委托",
|
||||
SH.ServiceDailyNoteNotifierDailyTask,
|
||||
"ms-appx:///Resource/Icon/UI_MarkQuest_Events_Proce.png",
|
||||
$"奖励待领取",
|
||||
SH.ServiceDailyNoteNotifierDailyTaskHint,
|
||||
entry.DailyNote.ExtraTaskRewardDescription));
|
||||
entry.DailyTaskNotifySuppressed = true;
|
||||
}
|
||||
@@ -105,10 +105,10 @@ internal class DailyNoteNotifier
|
||||
if (!entry.TransformerNotifySuppressed)
|
||||
{
|
||||
notifyInfos.Add(new(
|
||||
"参量质变仪",
|
||||
SH.ServiceDailyNoteNotifierTransformer,
|
||||
"ms-appx:///Resource/Icon/UI_ItemIcon_220021.png",
|
||||
$"准备完成",
|
||||
"参量质变仪已准备完成"));
|
||||
SH.ServiceDailyNoteNotifierTransformerAdaptiveHint,
|
||||
SH.ServiceDailyNoteNotifierTransformerHint));
|
||||
entry.TransformerNotifySuppressed = true;
|
||||
}
|
||||
}
|
||||
@@ -122,10 +122,10 @@ internal class DailyNoteNotifier
|
||||
if (!entry.ExpeditionNotifySuppressed)
|
||||
{
|
||||
notifyInfos.Add(new(
|
||||
"探索派遣",
|
||||
AvatarIconConverter.IconNameToUri("UI_AvatarIcon_Side_None.png").ToString(),
|
||||
$"已完成",
|
||||
"探索派遣已完成"));
|
||||
SH.ServiceDailyNoteNotifierExpedition,
|
||||
Web.HutaoEndpoints.UIAvatarIconSideNone.ToString(), // TODO: embed this
|
||||
SH.ServiceDailyNoteNotifierExpeditionAdaptiveHint,
|
||||
SH.ServiceDailyNoteNotifierExpeditionHint));
|
||||
entry.ExpeditionNotifySuppressed = true;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ internal class DailyNoteNotifier
|
||||
.GetActionTicketByStokenAsync("game_role", entry.User)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
string? attribution = "请求异常";
|
||||
string? attribution = SH.ServiceDailyNoteNotifierAttribution;
|
||||
if (actionTicketResponse.IsOk())
|
||||
{
|
||||
Response<ListWrapper<UserGameRole>> rolesResponse = await scope.ServiceProvider
|
||||
@@ -166,10 +166,13 @@ internal class DailyNoteNotifier
|
||||
}
|
||||
|
||||
ToastContentBuilder builder = new ToastContentBuilder()
|
||||
.AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE")
|
||||
.AddHeader("DAILYNOTE", SH.ServiceDailyNoteNotifierTitle, "DAILYNOTE")
|
||||
.AddAttributionText(attribution)
|
||||
.AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid))
|
||||
.AddButton(new ToastButtonDismiss("我知道了"));
|
||||
.AddButton(new ToastButton()
|
||||
.SetContent(SH.ServiceDailyNoteNotifierActionLaunchGameButton)
|
||||
.AddArgument("Action", Core.LifeCycle.Activation.LaunchGame)
|
||||
.AddArgument("Uid", entry.Uid))
|
||||
.AddButton(new ToastButtonDismiss(SH.ServiceDailyNoteNotifierActionLaunchGameDismiss));
|
||||
|
||||
if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString).GetBoolean())
|
||||
{
|
||||
@@ -178,7 +181,7 @@ internal class DailyNoteNotifier
|
||||
|
||||
if (notifyInfos.Count > 2)
|
||||
{
|
||||
builder.AddText("多个提醒项达到设定值");
|
||||
builder.AddText(SH.ServiceDailyNoteNotifierMultiValueReached);
|
||||
|
||||
// Desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update)
|
||||
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3))
|
||||
|
||||
@@ -157,14 +157,14 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveDailyNote(DailyNoteEntry entry)
|
||||
public async Task RemoveDailyNoteAsync(DailyNoteEntry entry)
|
||||
{
|
||||
entries!.Remove(entry);
|
||||
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
// DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s)
|
||||
scope.ServiceProvider.GetRequiredService<AppDbContext>().DailyNotes.RemoveAndSave(entry);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.DailyNotes.ExecuteDeleteWhereAsync(d => d.InnerId == entry.InnerId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,5 +36,6 @@ public interface IDailyNoteService
|
||||
/// 移除指定的实时便笺
|
||||
/// </summary>
|
||||
/// <param name="entry">指定的实时便笺</param>
|
||||
void RemoveDailyNote(DailyNoteEntry entry);
|
||||
/// <returns>任务</returns>
|
||||
Task RemoveDailyNoteAsync(DailyNoteEntry entry);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class GachaConfigTypeComparar : IComparer<GachaConfigType>
|
||||
{ GachaConfigType.AvatarEventWish, 0 },
|
||||
{ GachaConfigType.AvatarEventWish2, 1 },
|
||||
{ GachaConfigType.WeaponEventWish, 2 },
|
||||
{ GachaConfigType.PermanentWish, 3 },
|
||||
{ GachaConfigType.StandardWish, 3 },
|
||||
{ GachaConfigType.NoviceWish, 4 },
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding.Gacha;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
@@ -65,9 +66,9 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
Dictionary<WeaponId, Weapon> weaponMap,
|
||||
bool isEmptyHistoryWishVisible)
|
||||
{
|
||||
TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.IsPermanentWish, 90, 10);
|
||||
TypedWishSummaryBuilder avatarWishBuilder = new("角色活动", TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10);
|
||||
TypedWishSummaryBuilder weaponWishBuilder = new("神铸赋形", TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10);
|
||||
TypedWishSummaryBuilder standardWishBuilder = new(SH.ServiceGachaLogFactoryPermanentWishName, TypedWishSummaryBuilder.IsPermanentWish, 90, 10);
|
||||
TypedWishSummaryBuilder avatarWishBuilder = new(SH.ServiceGachaLogFactoryAvatarWishName, TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10);
|
||||
TypedWishSummaryBuilder weaponWishBuilder = new(SH.ServiceGachaLogFactoryWeaponWishName, TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10);
|
||||
|
||||
Dictionary<Avatar, int> orangeAvatarCounter = new();
|
||||
Dictionary<Avatar, int> purpleAvatarCounter = new();
|
||||
@@ -102,7 +103,7 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
break;
|
||||
}
|
||||
|
||||
permanentWishBuilder.Track(item, avatar, isUp);
|
||||
standardWishBuilder.Track(item, avatar, isUp);
|
||||
avatarWishBuilder.Track(item, avatar, isUp);
|
||||
weaponWishBuilder.Track(item, avatar, isUp);
|
||||
}
|
||||
@@ -129,14 +130,15 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
break;
|
||||
}
|
||||
|
||||
permanentWishBuilder.Track(item, weapon, isUp);
|
||||
standardWishBuilder.Track(item, weapon, isUp);
|
||||
avatarWishBuilder.Track(item, weapon, isUp);
|
||||
weaponWishBuilder.Track(item, weapon, isUp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ItemId place not correct.
|
||||
Must.NeverHappen();
|
||||
// TODO: check items id when importing
|
||||
ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.ItemId), null!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +162,7 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
BlueWeapons = blueWeaponCounter.ToStatisticsList(),
|
||||
|
||||
// typed wish summary
|
||||
PermanentWish = permanentWishBuilder.ToTypedWishSummary(),
|
||||
StandardWish = standardWishBuilder.ToTypedWishSummary(),
|
||||
AvatarWish = avatarWishBuilder.ToTypedWishSummary(),
|
||||
WeaponWish = weaponWishBuilder.ToTypedWishSummary(),
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user