Thread pool optimization

This commit is contained in:
DismissedLight
2022-12-10 13:50:42 +08:00
parent 267b34f571
commit 844c8b7810
84 changed files with 1456 additions and 1054 deletions

View File

@@ -4,50 +4,57 @@
xmlns:contract7NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,7)"
xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)">
<Style x:Key="SettingButtonStyle" TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}" >
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Padding" Value="16,5,16,6" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Style
x:Key="SettingButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Padding" Value="16,5,16,6"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style x:Key="HyperlinkButtonStyle" TargetType="HyperlinkButton" >
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Style x:Key="HyperlinkButtonStyle" TargetType="HyperlinkButton">
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
</Style>
<Style x:Key="TextButtonStyle" TargetType="ButtonBase">
<Setter Property="Background" Value="{ThemeResource HyperlinkButtonBackground}" />
<Setter Property="Foreground" Value="{ThemeResource HyperlinkButtonForeground}" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="Background" Value="{ThemeResource HyperlinkButtonBackground}"/>
<Setter Property="Foreground" Value="{ThemeResource HyperlinkButtonForeground}"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ButtonBase">
<Grid Margin="{TemplateBinding Padding}" CornerRadius="4" Background="{TemplateBinding Background}">
<ContentPresenter x:Name="Text"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
FontWeight="SemiBold"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Grid
Margin="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="4">
<ContentPresenter
x:Name="Text"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
FontWeight="SemiBold"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -56,13 +63,13 @@
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -71,13 +78,13 @@
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushDisabled}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -85,7 +92,6 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
@@ -93,192 +99,434 @@
</Setter>
</Style>
<!-- This style overrides the default style so that all ToggleSwitches are right aligned, with the label on the left -->
<!-- This style overrides the default style so that all ToggleSwitches are right aligned, with the label on the left -->
<Style x:Key="ToggleSwitchSettingStyle" TargetType="ToggleSwitch">
<Setter Property="Foreground" Value="{ThemeResource ToggleSwitchContentForeground}" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="System,TranslateX" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="Foreground" Value="{ThemeResource ToggleSwitchContentForeground}"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="ManipulationMode" Value="System,TranslateX"/>
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
<Setter Property="FocusVisualMargin" Value="-7,-3,-7,-3" />
<Setter Property="FocusVisualMargin" Value="-7,-3,-7,-3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleSwitch">
<Grid
contract7Present:CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
contract7Present:CornerRadius="{TemplateBinding CornerRadius}">
BorderThickness="{TemplateBinding BorderThickness}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{ThemeResource ToggleSwitchTopHeaderMargin}"
VerticalAlignment="Top"
x:DeferLoadStrategy="Lazy"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="{ThemeResource ToggleSwitchHeaderForeground}"
IsHitTestVisible="False"
TextWrapping="Wrap"
Visibility="Collapsed"/>
<Grid
Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource ToggleSwitchPreContentMargin}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{ThemeResource ToggleSwitchPostContentMargin}"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12" MaxWidth="12"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid
x:Name="SwitchAreaGrid"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
Margin="0,5"
contract7NotPresent:CornerRadius="{StaticResource ControlCornerRadius}"
contract7Present:CornerRadius="{TemplateBinding CornerRadius}"
Background="{ThemeResource ToggleSwitchContainerBackground}"
Control.IsTemplateFocusTarget="True"/>
<ContentPresenter
x:Name="OffContentPresenter"
Grid.RowSpan="3"
Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding OffContent}"
ContentTemplate="{TemplateBinding OffContentTemplate}"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Opacity="0"/>
<ContentPresenter
x:Name="OnContentPresenter"
Grid.RowSpan="3"
Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding OnContent}"
ContentTemplate="{TemplateBinding OnContentTemplate}"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Opacity="0"/>
<Rectangle
x:Name="OuterBorder"
Grid.Row="1"
Grid.Column="2"
Width="40"
Height="20"
Fill="{ThemeResource ToggleSwitchFillOff}"
RadiusX="10"
RadiusY="10"
Stroke="{ThemeResource ToggleSwitchStrokeOff}"
StrokeThickness="{ThemeResource ToggleSwitchOuterBorderStrokeThickness}"/>
<Rectangle
x:Name="SwitchKnobBounds"
Grid.Row="1"
Grid.Column="2"
Width="40"
Height="20"
Fill="{ThemeResource ToggleSwitchFillOn}"
Opacity="0"
RadiusX="10"
RadiusY="10"
Stroke="{ThemeResource ToggleSwitchStrokeOn}"
StrokeThickness="{ThemeResource ToggleSwitchOnStrokeThickness}"/>
<Grid
x:Name="SwitchKnob"
Grid.Row="1"
Grid.Column="2"
Width="20"
Height="20"
HorizontalAlignment="Left">
<Border
x:Name="SwitchKnobOn"
Grid.Column="2"
Width="12"
Height="12"
Margin="0,0,1,0"
HorizontalAlignment="Center"
contract7Present:BackgroundSizing="OuterBorderEdge"
Background="{ThemeResource ToggleSwitchKnobFillOn}"
BorderBrush="{ThemeResource ToggleSwitchKnobStrokeOn}"
CornerRadius="7"
Opacity="0"
RenderTransformOrigin="0.5, 0.5">
<Border.RenderTransform>
<CompositeTransform/>
</Border.RenderTransform>
</Border>
<Rectangle
x:Name="SwitchKnobOff"
Grid.Column="2"
Width="12"
Height="12"
Margin="-1,0,0,0"
HorizontalAlignment="Center"
Fill="{ThemeResource ToggleSwitchKnobFillOff}"
RadiusX="7"
RadiusY="7"
RenderTransformOrigin="0.5, 0.5">
<Rectangle.RenderTransform>
<CompositeTransform/>
</Rectangle.RenderTransform>
</Rectangle>
<Grid.RenderTransform>
<TranslateTransform x:Name="KnobTranslateTransform"/>
</Grid.RenderTransform>
</Grid>
<Thumb
x:Name="SwitchThumb"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
AutomationProperties.AccessibilityView="Raw">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOff}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOff}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOff}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOff}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOff}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOff}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOn}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOn}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOn}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOn}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOn}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOn}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContainerBackground}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContainerBackground}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Width" EnableDependentAnimation="True" >
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Width" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPointerOver}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPointerOver}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPointerOver}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPointerOver}"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPointerOver}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPointerOver}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPointerOver}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Width" EnableDependentAnimation="True" >
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="14" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="14" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Width" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="14" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="14" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="SwitchKnobOn.HorizontalAlignment" Value="Right" />
<Setter Target="SwitchKnobOn.Margin" Value="0,0,3,0" />
<Setter Target="SwitchKnobOff.HorizontalAlignment" Value="Left" />
<Setter Target="SwitchKnobOff.Margin" Value="3,0,0,0" />
<Setter Target="SwitchKnobOn.HorizontalAlignment" Value="Right"/>
<Setter Target="SwitchKnobOn.Margin" Value="0,0,3,0"/>
<Setter Target="SwitchKnobOff.HorizontalAlignment" Value="Left"/>
<Setter Target="SwitchKnobOff.Margin" Value="3,0,0,0"/>
</VisualState.Setters>
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPressed}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPressed}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPressed}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPressed}"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPressed}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPressed}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPressed}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Width" EnableDependentAnimation="True" >
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="17" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="17"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="14" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Width" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="17" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="17"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="14" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlFasterAnimationDuration}"
Value="14"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchHeaderForegroundDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchHeaderForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffDisabled}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffDisabled}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffDisabled}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffDisabled}"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnDisabled}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnDisabled}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundDisabled}" />
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundDisabled}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Width" EnableDependentAnimation="True" >
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlNormalAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlNormalAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOn"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Width" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlNormalAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Width">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Height" EnableDependentAnimation="True">
<SplineDoubleKeyFrame KeyTime="{StaticResource ControlNormalAnimationDuration}" KeySpline="{StaticResource ControlFastOutSlowInKeySpline}" Value="12" />
<DoubleAnimationUsingKeyFrames
EnableDependentAnimation="True"
Storyboard.TargetName="SwitchKnobOff"
Storyboard.TargetProperty="Height">
<SplineDoubleKeyFrame
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
KeyTime="{StaticResource ControlNormalAnimationDuration}"
Value="12"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -287,111 +535,117 @@
<VisualStateGroup x:Name="ToggleStates">
<VisualStateGroup.Transitions>
<VisualTransition x:Name="DraggingToOnTransition"
<VisualTransition
x:Name="DraggingToOnTransition"
GeneratedDuration="0"
From="Dragging"
To="On"
GeneratedDuration="0">
To="On">
<Storyboard>
<RepositionThemeAnimation TargetName="SwitchKnob" FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOnOffset}" />
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOnOffset}" TargetName="SwitchKnob"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition x:Name="OnToDraggingTransition"
<VisualTransition
x:Name="OnToDraggingTransition"
GeneratedDuration="0"
From="On"
To="Dragging"
GeneratedDuration="0">
To="Dragging">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0" Value="1" />
<LinearDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0" Value="1" />
<LinearDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0" Value="0" />
<LinearDoubleKeyFrame KeyTime="0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition x:Name="DraggingToOffTransition"
<VisualTransition
x:Name="DraggingToOffTransition"
GeneratedDuration="0"
From="Dragging"
To="Off"
GeneratedDuration="0">
To="Off">
<Storyboard>
<RepositionThemeAnimation TargetName="SwitchKnob" FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOffOffset}" />
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOffOffset}" TargetName="SwitchKnob"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition x:Name="OnToOffTransition"
<VisualTransition
x:Name="OnToOffTransition"
GeneratedDuration="0"
From="On"
To="Off"
GeneratedDuration="0">
To="Off">
<Storyboard>
<RepositionThemeAnimation TargetName="SwitchKnob" FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOnToOffOffset}" />
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOnToOffOffset}" TargetName="SwitchKnob"/>
</Storyboard>
</VisualTransition>
<VisualTransition x:Name="OffToOnTransition"
<VisualTransition
x:Name="OffToOnTransition"
GeneratedDuration="0"
From="Off"
To="On"
GeneratedDuration="0">
To="On">
<Storyboard>
<RepositionThemeAnimation TargetName="SwitchKnob" FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOffToOnOffset}"/>
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOffToOnOffset}" TargetName="SwitchKnob"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Dragging" />
<VisualState x:Name="Off" />
<VisualState x:Name="Dragging"/>
<VisualState x:Name="Off"/>
<VisualState x:Name="On">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="KnobTranslateTransform"
<DoubleAnimation
Storyboard.TargetName="KnobTranslateTransform"
Storyboard.TargetProperty="X"
To="20"
Duration="0" />
Duration="0"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -401,11 +655,12 @@
<VisualState x:Name="OffContent">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="OffContentPresenter"
<DoubleAnimation
Storyboard.TargetName="OffContentPresenter"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="OffContentPresenter">
Duration="0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Boolean>True</x:Boolean>
@@ -417,11 +672,12 @@
<VisualState x:Name="OnContent">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="OnContentPresenter"
<DoubleAnimation
Storyboard.TargetName="OnContentPresenter"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="OnContentPresenter">
Duration="0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Boolean>True</x:Boolean>
@@ -434,149 +690,11 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter x:Name="HeaderContentPresenter"
x:DeferLoadStrategy="Lazy"
Grid.Row="0"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="{ThemeResource ToggleSwitchHeaderForeground}"
IsHitTestVisible="False"
Margin="{ThemeResource ToggleSwitchTopHeaderMargin}"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Collapsed"
AutomationProperties.AccessibilityView="Raw" />
<Grid
Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource ToggleSwitchPreContentMargin}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{ThemeResource ToggleSwitchPostContentMargin}" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="12" MaxWidth="12" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid x:Name="SwitchAreaGrid"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
Margin="0,5"
contract7Present:CornerRadius="{TemplateBinding CornerRadius}"
contract7NotPresent:CornerRadius="{StaticResource ControlCornerRadius}"
Control.IsTemplateFocusTarget="True"
Background="{ThemeResource ToggleSwitchContainerBackground}" />
<ContentPresenter x:Name="OffContentPresenter"
Grid.RowSpan="3"
Grid.Column="0"
Opacity="0"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Content="{TemplateBinding OffContent}"
ContentTemplate="{TemplateBinding OffContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw" />
<ContentPresenter x:Name="OnContentPresenter"
Grid.RowSpan="3"
Grid.Column="0"
Opacity="0"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Content="{TemplateBinding OnContent}"
ContentTemplate="{TemplateBinding OnContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw" />
<Rectangle x:Name="OuterBorder"
Grid.Row="1"
Grid.Column="2"
Height="20"
Width="40"
RadiusX="10"
RadiusY="10"
Fill="{ThemeResource ToggleSwitchFillOff}"
Stroke="{ThemeResource ToggleSwitchStrokeOff}"
StrokeThickness="{ThemeResource ToggleSwitchOuterBorderStrokeThickness}" />
<Rectangle x:Name="SwitchKnobBounds"
Grid.Row="1"
Height="20"
Width="40"
RadiusX="10"
RadiusY="10"
Grid.Column="2"
Fill="{ThemeResource ToggleSwitchFillOn}"
Stroke="{ThemeResource ToggleSwitchStrokeOn}"
StrokeThickness="{ThemeResource ToggleSwitchOnStrokeThickness}"
Opacity="0" />
<Grid x:Name="SwitchKnob"
Grid.Row="1"
Grid.Column="2"
HorizontalAlignment="Left"
Width="20"
Height="20">
<Border x:Name="SwitchKnobOn"
Background="{ThemeResource ToggleSwitchKnobFillOn}"
BorderBrush="{ThemeResource ToggleSwitchKnobStrokeOn}"
contract7Present:BackgroundSizing="OuterBorderEdge"
Width="12"
Height="12"
CornerRadius="7"
Grid.Column="2"
Opacity="0"
HorizontalAlignment="Center"
Margin="0,0,1,0"
RenderTransformOrigin="0.5, 0.5">
<Border.RenderTransform>
<CompositeTransform/>
</Border.RenderTransform>
</Border>
<Rectangle x:Name="SwitchKnobOff"
Fill="{ThemeResource ToggleSwitchKnobFillOff}"
Width="12"
Height="12"
RadiusX="7"
Grid.Column="2"
RadiusY="7"
HorizontalAlignment="Center"
Margin="-1,0,0,0"
RenderTransformOrigin="0.5, 0.5">
<Rectangle.RenderTransform>
<CompositeTransform/>
</Rectangle.RenderTransform>
</Rectangle>
<Grid.RenderTransform>
<TranslateTransform x:Name="KnobTranslateTransform" />
</Grid.RenderTransform>
</Grid>
<Thumb x:Name="SwitchThumb"
AutomationProperties.AccessibilityView="Raw"
Grid.RowSpan="3"
Grid.ColumnSpan="3">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Rectangle Fill="Transparent" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,15 +1,13 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:controls="using:SettingsUI.Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:SettingsUI.Controls">
<Style x:Key="ListViewItemSettingStyle" TargetType="ListViewItem">
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Padding" Value="0,0,0,0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<Style
TargetType="controls:CheckBoxWithDescriptionControl"
BasedOn="{StaticResource DefaultCheckBoxStyle}" />
<Style BasedOn="{StaticResource DefaultCheckBoxStyle}" TargetType="controls:CheckBoxWithDescriptionControl"/>
</ResourceDictionary>

View File

@@ -1,17 +1,15 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="OobeSubtitleStyle" TargetType="TextBlock">
<Setter Property="Margin" Value="0,16,0,0" />
<Setter Property="Foreground" Value="{ThemeResource DefaultTextForegroundThemeBrush}" />
<Setter Property="AutomationProperties.HeadingLevel" Value="Level3" />
<Setter Property="FontFamily" Value="XamlAutoFontFamily" />
<Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSize}" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="LineStackingStrategy" Value="MaxHeight" />
<Setter Property="TextLineBounds" Value="Full" />
<Setter Property="Margin" Value="0,16,0,0"/>
<Setter Property="Foreground" Value="{ThemeResource DefaultTextForegroundThemeBrush}"/>
<Setter Property="AutomationProperties.HeadingLevel" Value="Level3"/>
<Setter Property="FontFamily" Value="XamlAutoFontFamily"/>
<Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="LineStackingStrategy" Value="MaxHeight"/>
<Setter Property="TextLineBounds" Value="Full"/>
</Style>
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>

View File

@@ -1,29 +1,27 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="CardBackgroundFillColorDefaultBrush" />
<StaticResource x:Key="CardBorderBrush" ResourceKey="CardStrokeColorDefaultBrush" />
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="CardBackgroundFillColorDefaultBrush"/>
<StaticResource x:Key="CardBorderBrush" ResourceKey="CardStrokeColorDefaultBrush"/>
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="TextFillColorPrimaryBrush"/>
<SolidColorBrush x:Key="InfoBarInformationalSeverityBackgroundBrush" Color="#FF34424d"/>
<Color x:Key="InfoBarInformationalSeverityIconBackground">#FF5fb2f2</Color>
<Thickness x:Key="CardBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="CardBackgroundFillColorDefaultBrush" />
<StaticResource x:Key="CardBorderBrush" ResourceKey="CardStrokeColorDefaultBrush" />
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="CardBackgroundFillColorDefaultBrush"/>
<StaticResource x:Key="CardBorderBrush" ResourceKey="CardStrokeColorDefaultBrush"/>
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="TextFillColorPrimaryBrush"/>
<SolidColorBrush x:Key="InfoBarInformationalSeverityBackgroundBrush" Color="#FFd3e7f7"/>
<Color x:Key="InfoBarInformationalSeverityIconBackground">#FF0063b1</Color>
<Thickness x:Key="CardBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="SystemColorButtonFaceColorBrush" />
<StaticResource x:Key="CardBorderBrush" ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="CardBackgroundBrush" ResourceKey="SystemColorButtonFaceColorBrush"/>
<StaticResource x:Key="CardBorderBrush" ResourceKey="SystemColorButtonTextColorBrush"/>
<StaticResource x:Key="CardPrimaryForegroundBrush" ResourceKey="SystemColorButtonTextColorBrush"/>
<SolidColorBrush x:Key="InfoBarInformationalSeverityBackgroundBrush" Color="#FF34424d"/>
<Color x:Key="InfoBarInformationalSeverityIconBackground">#FF5fb2f2</Color>
<Thickness x:Key="CardBorderThickness">2</Thickness>

View File

@@ -11,42 +11,38 @@
<!-- Styles -->
<!-- Setting used in a Expander header -->
<Style x:Key="ExpanderHeaderSettingStyle"
TargetType="controls:Setting">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Padding" Value="0,14,0,14" />
<Setter Property="Margin" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Style x:Key="ExpanderHeaderSettingStyle" TargetType="controls:Setting">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="0,14,0,14"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<Thickness x:Key="ExpanderChevronMargin">0,0,8,0</Thickness>
<!-- Setting used in a Expander header -->
<Style x:Key="ExpanderContentSettingStyle"
TargetType="controls:Setting">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Padding" Value="{StaticResource ExpanderSettingMargin}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Style x:Key="ExpanderContentSettingStyle" TargetType="controls:Setting">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0,1,0,0"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="Padding" Value="{StaticResource ExpanderSettingMargin}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<!-- Setting expander style -->
<Style x:Key="SettingExpanderStyle"
TargetType="Expander">
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Style x:Key="SettingExpanderStyle" TargetType="Expander">
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<Style x:Key="ExpanderSeparatorStyle"
TargetType="Rectangle">
<Setter Property="Height" Value="1" />
<Setter Property="Stroke" Value="{ThemeResource CardBorderBrush}" />
<Style x:Key="ExpanderSeparatorStyle" TargetType="Rectangle">
<Setter Property="Height" Value="1"/>
<Setter Property="Stroke" Value="{ThemeResource CardBorderBrush}"/>
</Style>
</ResourceDictionary>

View File

@@ -1,7 +1,5 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/Common.xaml"/>
<ResourceDictionary Source="../Styles/TextBlock.xaml"/>
@@ -9,6 +7,6 @@
<ResourceDictionary Source="../Themes/Colors.xaml"/>
<ResourceDictionary Source="../Themes/SettingsExpanderStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<x:Double x:Key="SettingActionControlMinWidth">240</x:Double>
</ResourceDictionary>

View File

@@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
@@ -144,4 +145,34 @@ internal static partial class IocHttpClientConfiguration
sourceCodeBuilder.Append(line);
}
}
private class HttpClientSyntaxContextReceiver : ISyntaxContextReceiver
{
/// <summary>
/// 注入特性的名称
/// </summary>
public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute";
/// <summary>
/// 所有需要注入的类型符号
/// </summary>
public List<INamedTypeSymbol> Classes { get; } = new();
/// <inheritdoc/>
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
// any class with at least one attribute is a candidate for injection generation
if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
{
// get as named type symbol
if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
{
if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
{
Classes.Add(classSymbol);
}
}
}
}
}
}

View File

@@ -1,42 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
/// <summary>
/// Created on demand before each generation pass
/// </summary>
public class HttpClientSyntaxContextReceiver : ISyntaxContextReceiver
{
/// <summary>
/// 注入特性的名称
/// </summary>
public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute";
/// <summary>
/// 所有需要注入的类型符号
/// </summary>
public List<INamedTypeSymbol> Classes { get; } = new();
/// <inheritdoc/>
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
// any class with at least one attribute is a candidate for injection generation
if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
{
// get as named type symbol
if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
{
if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
{
Classes.Add(classSymbol);
}
}
}
}
}

View File

@@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
@@ -122,4 +123,34 @@ internal static partial class ServiceCollectionExtension
sourceCodeBuilder.Append(line);
}
}
private class InjectionSyntaxContextReceiver : ISyntaxContextReceiver
{
/// <summary>
/// 注入特性的名称
/// </summary>
public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
/// <summary>
/// 所有需要注入的类型符号
/// </summary>
public List<INamedTypeSymbol> Classes { get; } = new();
/// <inheritdoc/>
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
// any class with at least one attribute is a candidate for injection generation
if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
{
// get as named type symbol
if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
{
if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
{
Classes.Add(classSymbol);
}
}
}
}
}
}

View File

@@ -1,42 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
/// <summary>
/// Created on demand before each generation pass
/// </summary>
public class InjectionSyntaxContextReceiver : ISyntaxContextReceiver
{
/// <summary>
/// 注入特性的名称
/// </summary>
public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
/// <summary>
/// 所有需要注入的类型符号
/// </summary>
public List<INamedTypeSymbol> Classes { get; } = new();
/// <inheritdoc/>
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
// any class with at least one attribute is a candidate for injection generation
if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
{
// get as named type symbol
if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
{
if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
{
Classes.Add(classSymbol);
}
}
}
}
}

View File

@@ -21,28 +21,28 @@
<Color x:Key="CompatBackgroundColor">#FF272727</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!--Modify Window title bar color-->
<!-- Modify Window title bar color -->
<StaticResource x:Key="WindowCaptionBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!--Page Transparent Background-->
<!-- Page Transparent Background -->
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<!--IconFont-->
<!-- IconFont -->
<FontFamily x:Key="SymbolThemeFontFamily">ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
<!--InfoBar Resource-->
<!-- InfoBar Resource -->
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
<!--Pivot Resource-->
<!-- Pivot Resource -->
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
<Thickness x:Key="PivotItemMargin">0</Thickness>
<!--CornerRadius-->
<!-- CornerRadius -->
<CornerRadius x:Key="CompatCornerRadius">6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusTop">6,6,0,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!--Converters-->
<!-- Converters -->
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
@@ -59,15 +59,15 @@
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
<!--Styles-->
<!-- Styles -->
<Style
x:Key="LargeGridViewItemStyle"
TargetType="GridViewItem"
BasedOn="{StaticResource DefaultGridViewItemStyle}">
BasedOn="{StaticResource DefaultGridViewItemStyle}"
TargetType="GridViewItem">
<Setter Property="Margin" Value="0,0,12,12"/>
</Style>
<!--ItemsPanelTemplate-->
<!-- ItemsPanelTemplate -->
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
<ItemsStackPanel/>
</ItemsPanelTemplate>

View File

@@ -64,6 +64,20 @@ public static class DbSetExtension
return dbSet.Context().SaveChanges();
}
/// <summary>
/// 异步添加并保存
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param>
/// <returns>影响条数</returns>
public static Task<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
where TEntity : class
{
dbSet.Add(entity);
return dbSet.Context().SaveChangesAsync();
}
/// <summary>
/// 添加列表并保存
/// </summary>
@@ -78,6 +92,20 @@ public static class DbSetExtension
return dbSet.Context().SaveChanges();
}
/// <summary>
/// 异步添加列表并保存
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <param name="entities">实体</param>
/// <returns>影响条数</returns>
public static Task<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
where TEntity : class
{
dbSet.AddRange(entities);
return dbSet.Context().SaveChangesAsync();
}
/// <summary>
/// 移除并保存
/// </summary>
@@ -93,17 +121,17 @@ public static class DbSetExtension
}
/// <summary>
/// 移除并保存
/// 异步移除并保存
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <param name="entities">实体列表</param>
/// <param name="entity">实体</param>
/// <returns>影响条数</returns>
public static int RemoveRangeAndSave<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
public static Task<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
where TEntity : class
{
dbSet.RemoveRange(entities);
return dbSet.Context().SaveChanges();
dbSet.Remove(entity);
return dbSet.Context().SaveChangesAsync();
}
/// <summary>
@@ -119,4 +147,18 @@ public static class DbSetExtension
dbSet.Update(entity);
return dbSet.Context().SaveChanges();
}
/// <summary>
/// 异步更新并保存
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param>
/// <returns>影响条数</returns>
public static Task<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
where TEntity : class
{
dbSet.Update(entity);
return dbSet.Context().SaveChangesAsync();
}
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Snap.Hutao.Core.Database;
/// <summary>
/// 范围化的数据库当前项
/// 简化对数据库中选中项的管理
/// </summary>
/// <typeparam name="TEntity">实体的类型</typeparam>
/// <typeparam name="TMessage">消息的类型</typeparam>
internal class ScopedDbCurrent<TEntity, TMessage>
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TEntity>, new()
{
private readonly IServiceScopeFactory scopeFactory;
private readonly Func<IServiceProvider, DbSet<TEntity>> dbSetFunc;
private readonly IMessenger messenger;
private TEntity? current;
/// <summary>
/// 构造一个新的数据库当前项
/// </summary>
/// <param name="scopeFactory">范围工厂</param>
/// <param name="dbSetFunc">数据集</param>
/// <param name="messenger">消息器</param>
public ScopedDbCurrent(IServiceScopeFactory scopeFactory, Func<IServiceProvider, DbSet<TEntity>> dbSetFunc, IMessenger messenger)
{
this.scopeFactory = scopeFactory;
this.dbSetFunc = dbSetFunc;
this.messenger = messenger;
}
/// <summary>
/// 当前选中的项
/// </summary>
public TEntity? Current
{
get => current;
set
{
// prevent useless sets
if (current == value)
{
return;
}
using (IServiceScope scope = scopeFactory.CreateScope())
{
DbSet<TEntity> dbSet = dbSetFunc(scope.ServiceProvider);
// only update when not processing a deletion
if (value != null)
{
if (current != null)
{
current.IsSelected = false;
dbSet.UpdateAndSave(current);
}
}
TMessage message = new() { OldValue = current, NewValue = value };
current = value;
if (current != null)
{
current.IsSelected = true;
dbSet.UpdateAndSave(current);
}
messenger.Send(message);
}
}
}
}

View File

@@ -11,6 +11,16 @@ namespace Snap.Hutao.Core.Database;
/// </summary>
public static class SettingEntryHelper
{
/// <summary>
/// "True"
/// </summary>
public static readonly string TrueString = true.ToString();
/// <summary>
/// "False"
/// </summary>
public static readonly string FalseString = false.ToString();
/// <summary>
/// 获取或添加一个对应的设置
/// </summary>
@@ -37,17 +47,16 @@ public static class SettingEntryHelper
/// </summary>
/// <param name="dbSet">设置集</param>
/// <param name="key">键</param>
/// <param name="valueFactory">值工厂</param>
/// <param name="value">值</param>
/// <returns>设置</returns>
public static SettingEntry SingleOrAdd(this DbSet<SettingEntry> dbSet, string key, Func<string> valueFactory)
public static async Task<SettingEntry> SingleOrAddAsync(this DbSet<SettingEntry> dbSet, string key, string value)
{
SettingEntry? entry = dbSet.SingleOrDefault(entry => key == entry.Key);
SettingEntry? entry = await dbSet.SingleOrDefaultAsync(entry => key == entry.Key).ConfigureAwait(false);
if (entry == null)
{
entry = new(key, valueFactory());
dbSet.Add(entry);
dbSet.Context().SaveChanges();
entry = new(key, value);
await dbSet.AddAndSaveAsync(entry).ConfigureAwait(false);
}
return entry;

View File

@@ -40,7 +40,6 @@ internal readonly struct ValueStopwatch
/// 获取经过的时间
/// </summary>
/// <returns>经过的时间</returns>
/// <exception cref="InvalidOperationException">当前的停表未合理的初始化</exception>
public long GetElapsedTimestamp()
{
// Start timestamp can't be zero in an initialized ValueStopwatch.
@@ -59,7 +58,6 @@ internal readonly struct ValueStopwatch
/// 获取经过的时间
/// </summary>
/// <returns>经过的时间</returns>
/// <exception cref="InvalidOperationException">当前的停表未合理的初始化</exception>
public TimeSpan GetElapsedTime()
{
return new TimeSpan(GetElapsedTimestamp());

View File

@@ -1,20 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Exception;
/// <summary>
/// Error codes used by COM-based APIs.
/// </summary>
public enum COMError : uint
{
/// <summary>
/// could not be found.
/// </summary>
STG_E_FILENOTFOUND = 0x80030002,
/// <summary>
/// The component cannot be found.
/// </summary>
WINCODEC_ERR_COMPONENTNOTFOUND = 0x88982F50,
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Exception;
/// <summary>
/// COM异常扩展
/// </summary>
internal static class COMExceptionExtensions
{
/// <summary>
/// 比较COM异常是否与某个错误代码等价
/// </summary>
/// <param name="exception">异常</param>
/// <param name="code">错误代码</param>
/// <returns>是否为该错误</returns>
public static bool Is(this COMException exception, COMError code)
{
return exception.HResult == unchecked((int)code);
}
}

View File

@@ -28,12 +28,18 @@ internal sealed class TemporaryFile : IDisposable
/// </summary>
/// <param name="file">源文件</param>
/// <returns>临时文件</returns>
public static TemporaryFile CreateFromFileCopy(string file)
public static TemporaryFile? CreateFromFileCopy(string file)
{
TemporaryFile temporaryFile = new();
File.Copy(file, temporaryFile.Path, true);
return temporaryFile;
try
{
File.Copy(file, temporaryFile.Path, true);
return temporaryFile;
}
catch (System.Exception)
{
return null;
}
}
/// <summary>

View File

@@ -1,6 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Converter;
using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Core.Json.Annotation;
/// <summary>
@@ -9,6 +12,18 @@ namespace Snap.Hutao.Core.Json.Annotation;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal class JsonEnumAttribute : Attribute
{
private static readonly Type ConfigurableEnumConverterType = typeof(ConfigurableEnumConverter<>);
/// <summary>
/// 构造一个新的Json枚举声明
/// </summary>
/// <param name="readAndWriteAs">读取与写入</param>
public JsonEnumAttribute(JsonSerializeType readAndWriteAs)
{
ReadAs = readAndWriteAs;
WriteAs = readAndWriteAs;
}
/// <summary>
/// 构造一个新的Json枚举声明
/// </summary>
@@ -29,4 +44,15 @@ internal class JsonEnumAttribute : Attribute
/// 写入形式
/// </summary>
public JsonSerializeType WriteAs { get; init; }
/// <summary>
/// 创建一个新的转换器
/// </summary>
/// <param name="info">属性信息</param>
/// <returns>Json转换器</returns>
internal JsonConverter CreateConverter(JsonPropertyInfo info)
{
Type converterType = ConfigurableEnumConverterType.MakeGenericType(info.PropertyType);
return (JsonConverter)Activator.CreateInstance(converterType, ReadAs, WriteAs)!;
}
}

View File

@@ -4,12 +4,12 @@
namespace Snap.Hutao.Core.Json.Annotation;
/// <summary>
/// Json 文本字符串序列化类型
/// Json 序列化类型
/// </summary>
public enum JsonSerializeType
{
/// <summary>
/// 数字
/// Int32
/// </summary>
Int32,

View File

@@ -1,29 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Json.Converter;
/// <summary>
/// 枚举 - 字符串数字 转换器
/// </summary>
/// <typeparam name="TEnum">枚举的类型</typeparam>
internal class EnumStringValueConverter<TEnum> : JsonConverter<TEnum>
where TEnum : struct, Enum
{
/// <inheritdoc/>
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is string str)
{
return Enum.Parse<TEnum>(str);
}
throw Must.NeverHappen();
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("D"));
}
}

View File

@@ -14,7 +14,6 @@ namespace Snap.Hutao.Core.Json;
internal static class JsonTypeInfoResolvers
{
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
private static readonly Type ConfigurableEnumConverterType = typeof(ConfigurableEnumConverter<>);
/// <summary>
/// 解析枚举类型
@@ -32,10 +31,9 @@ internal static class JsonTypeInfoResolvers
foreach (JsonPropertyInfo enumProperty in enumProperties)
{
JsonEnumAttribute attr = enumProperty.PropertyType.GetCustomAttribute<JsonEnumAttribute>()!;
Type converterType = ConfigurableEnumConverterType.MakeGenericType(enumProperty.PropertyType);
JsonEnumAttribute attr = enumProperty.AttributeProvider!.GetCustomAttributes(false).OfType<JsonEnumAttribute>().Single();
enumProperty.CustomConverter = (JsonConverter)Activator.CreateInstance(converterType, attr.ReadAs, attr.WriteAs)!;
enumProperty.CustomConverter = attr.CreateConverter(enumProperty);
}
}
}

View File

@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 调度器队列切换操作
/// 等待此类型对象后上下文会被切换至主线程
/// </summary>
public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
{
@@ -29,9 +30,9 @@ public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueue
}
/// <inheritdoc/>
public void OnCompleted(Action continuation)
public DispatherQueueSwitchOperation GetAwaiter()
{
dispatherQueue.TryEnqueue(() => { continuation(); });
return this;
}
/// <inheritdoc/>
@@ -40,8 +41,8 @@ public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueue
}
/// <inheritdoc/>
public DispatherQueueSwitchOperation GetAwaiter()
public void OnCompleted(Action continuation)
{
return this;
dispatherQueue.TryEnqueue(() => { continuation(); });
}
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading;
/// <summary>
@@ -8,13 +10,25 @@ namespace Snap.Hutao.Core.Threading;
/// </summary>
internal static class ThreadHelper
{
/// <summary>
/// 使用此静态方法以 异步切换到 后台线程
/// </summary>
/// <remarks>使用 <see cref="SwitchToMainThreadAsync"/> 异步切换到 主线程</remarks>
/// <returns>等待体</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ThreadPoolSwitchOperation SwitchToBackgroundAsync()
{
return default;
}
/// <summary>
/// 使用此静态方法以 异步切换到 主线程
/// </summary>
/// <remarks>使用 <see cref="Task.Yield"/> 异步切换到 后台线程</remarks>
/// <remarks>使用 <see cref="SwitchToBackgroundAsync"/> 异步切换到 后台线程</remarks>
/// <returns>等待体</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
{
return new(Program.DispatcherQueue!);
}
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Threading.Abstraction;
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 线程池切换操作
/// 等待此类型对象后上下文会被切换至线程池线程
/// </summary>
public readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitchOperation>, IAwaiter, ICriticalAwaiter
{
private static readonly WaitCallback WaitCallbackRunAction = RunAction;
/// <inheritdoc/>
public bool IsCompleted { get => false; }
/// <inheritdoc/>
public ThreadPoolSwitchOperation GetAwaiter()
{
return this;
}
/// <inheritdoc/>
public void GetResult()
{
}
/// <inheritdoc/>
public void OnCompleted(Action continuation)
{
QueueContinuation(continuation, flowContext: true);
}
/// <inheritdoc/>
public void UnsafeOnCompleted(Action continuation)
{
QueueContinuation(continuation, flowContext: false);
}
private static void QueueContinuation(Action continuation, bool flowContext)
{
if (flowContext)
{
ThreadPool.QueueUserWorkItem(WaitCallbackRunAction, continuation);
}
else
{
ThreadPool.UnsafeQueueUserWorkItem(WaitCallbackRunAction, continuation);
}
}
private static void RunAction(object? state)
{
((Action)state!)();
}
}

View File

@@ -8,7 +8,6 @@ namespace Snap.Hutao.Message;
/// <summary>
/// 成就存档切换消息
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
{
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Message;
/// <summary>
/// 养成计划切换消息
/// </summary>
internal class CultivateProjectChangedMessage : ValueChangedMessage<CultivateProject>
{
}

View File

@@ -57,7 +57,7 @@ public class Achievement : ObservableObject
// Only update state when checked
if (value)
{
Entity.Status = Intrinsic.AchievementInfoStatus.ACHIEVEMENT_POINT_TAKEN;
Entity.Status = Intrinsic.AchievementInfoStatus.STATUS_REWARD_TAKEN;
Entity.Time = DateTimeOffset.Now;
OnPropertyChanged(nameof(Time));
}

View File

@@ -14,4 +14,9 @@ public class Skill : NameIconDescription
/// 技能属性
/// </summary>
public LevelParam<string, ParameterInfo> Info { get; set; } = default!;
/// <summary>
/// 技能等级,仅用于养成计算
/// </summary>
internal int Level { get; set; }
}

View File

@@ -30,11 +30,6 @@ public class AchievementArchive : ISelectable
/// </summary>
public bool IsSelected { get; set; }
/// <summary>
/// 成就
/// </summary>
public virtual ICollection<Achievement> Achievements { get; set; } = default!;
/// <summary>
/// 创建一个新的存档
/// </summary>

View File

@@ -23,6 +23,12 @@ public class ObjectCacheEntry
/// </summary>
public DateTimeOffset ExpireTime { get; set; }
/// <summary>
/// 获取该对象是否过期
/// </summary>
[NotMapped]
public bool IsExpired { get => ExpireTime > DateTimeOffset.Now; }
/// <summary>
/// 值字符串
/// </summary>

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Converter;
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Model.InterChange.GachaLog;
@@ -15,6 +15,6 @@ public class UIGFItem : GachaLogItem
/// 额外祈愿映射
/// </summary>
[JsonPropertyName("uigf_gacha_type")]
[JsonConverter(typeof(EnumStringValueConverter<GachaConfigType>))]
[JsonEnum(JsonSerializeType.Int32AsString)]
public GachaConfigType UIGFGachaType { get; set; } = default!;
}

View File

@@ -16,20 +16,20 @@ public enum AchievementInfoStatus
/// <summary>
/// 非法值
/// </summary>
ACHIEVEMENT_INVALID = 0,
STATUS_INVALID = 0,
/// <summary>
/// 未完成
/// </summary>
ACHIEVEMENT_UNFINISHED = 1,
STATUS_UNFINISHED = 1,
/// <summary>
/// 已完成
/// </summary>
ACHIEVEMENT_FINISHED = 2,
STATUS_FINISHED = 2,
/// <summary>
/// 奖励已领取
/// </summary>
ACHIEVEMENT_POINT_TAKEN = 3,
STATUS_REWARD_TAKEN = 3,
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 从属地区
/// </summary>
public enum AssociationType
{
/// <summary>
/// 无
/// </summary>
ASSOC_TYPE_NONE,
/// <summary>
/// 蒙德
/// </summary>
ASSOC_TYPE_MONDSTADT,
/// <summary>
/// 璃月
/// </summary>
ASSOC_TYPE_LIYUE,
/// <summary>
/// 主角
/// </summary>
ASSOC_TYPE_MAINACTOR,
/// <summary>
/// 愚人众
/// </summary>
ASSOC_TYPE_FATUI,
/// <summary>
/// 稻妻
/// </summary>
ASSOC_TYPE_INAZUMA,
/// <summary>
/// 游侠
/// </summary>
ASSOC_TYPE_RANGER,
/// <summary>
/// 须弥
/// </summary>
ASSOC_TYPE_SUMERU,
/// <summary>
/// 枫丹
/// </summary>
ASSOC_TYPE_FONTAINE,
/// <summary>
/// 纳塔
/// </summary>
ASSOC_TYPE_NATLAN,
/// <summary>
/// 至冬
/// </summary>
ASSOC_TYPE_SNEZHNAYA,
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 体型
/// </summary>
public enum BodyType
{
/// <summary>
/// 无
/// </summary>
BODY_NONE,
/// <summary>
/// 男孩
/// </summary>
BODY_BOY,
/// <summary>
/// 女孩
/// </summary>
BODY_GIRL,
/// <summary>
/// 成女
/// </summary>
BODY_LADY,
/// <summary>
/// 成男
/// </summary>
BODY_MALE,
/// <summary>
/// 萝莉
/// </summary>
BODY_LOLI,
}

View File

@@ -5,7 +5,6 @@ namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 武器类型
/// https://github.com/Grasscutters/Grasscutter/blob/development/src/main/java/emu/grasscutter/game/props/WeaponType.java
/// </summary>
[SuppressMessage("", "SA1124")]
public enum WeaponType

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
@@ -29,7 +30,8 @@ public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality
/// <summary>
/// 体型
/// </summary>
public string Body { get; set; } = default!;
[JsonEnum(JsonSerializeType.String)]
public BodyType Body { get; set; } = default!;
/// <summary>
/// 正面图标

View File

@@ -1,6 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
@@ -21,7 +24,8 @@ public class FetterInfo
/// <summary>
/// 地区
/// </summary>
public string Association { get; set; } = default!;
[JsonEnum(JsonSerializeType.String)]
public AssociationType Association { get; set; } = default!;
/// <summary>
/// 属于组织

View File

@@ -11,6 +11,7 @@ namespace Snap.Hutao.Model.Metadata;
[SuppressMessage("", "SA1600")]
public static class AvatarIds
{
public static readonly AvatarId Kate = 10000001;
public static readonly AvatarId Ayaka = 10000002;
public static readonly AvatarId Qin = 10000003;

View File

@@ -1,5 +1,6 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": true,
"public": true
"public": true,
"emitSingleFile": true
}

View File

@@ -12,7 +12,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.2.6.0" />
Version="1.2.7.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -10,6 +10,7 @@ namespace Snap.Hutao.Service.Achievement;
/// <summary>
/// 成就数据库操作
/// 双指针操作
/// </summary>
public class AchievementDbOperation
{
@@ -39,7 +40,6 @@ public class AchievementDbOperation
int add = 0;
int update = 0;
int remove = 0;
using (IEnumerator<EntityAchievement> entityEnumerator = oldData.GetEnumerator())
{
@@ -105,7 +105,7 @@ public class AchievementDbOperation
}
}
return new(add, update, remove);
return new(add, update, 0);
}
/// <summary>

View File

@@ -57,30 +57,28 @@ internal class AchievementService : IAchievementService
}
/// <inheritdoc/>
public Task RemoveArchiveAsync(EntityArchive archive)
public async Task RemoveArchiveAsync(EntityArchive archive)
{
Must.NotNull(archiveCollection!);
// Sync cache
archiveCollection.Remove(archive);
// Keep this on main thread.
await ThreadHelper.SwitchToMainThreadAsync();
archiveCollection!.Remove(archive);
// Sync database
appDbContext.AchievementArchives.Remove(archive);
return appDbContext.SaveChangesAsync();
await ThreadHelper.SwitchToBackgroundAsync();
await appDbContext.AchievementArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive)
{
if (string.IsNullOrEmpty(newArchive.Name))
if (string.IsNullOrWhiteSpace(newArchive.Name))
{
return ArchiveAddResult.InvalidName;
}
Must.NotNull(archiveCollection!);
// 查找是否有相同的名称
if (archiveCollection.SingleOrDefault(a => a.Name == newArchive.Name) is EntityArchive userWithSameUid)
if (archiveCollection!.SingleOrDefault(a => a.Name == newArchive.Name) is EntityArchive userWithSameUid)
{
return ArchiveAddResult.AlreadyExists;
}
@@ -88,11 +86,11 @@ internal class AchievementService : IAchievementService
{
// Sync cache
await ThreadHelper.SwitchToMainThreadAsync();
archiveCollection.Add(newArchive);
archiveCollection!.Add(newArchive);
// Sync database
appDbContext.AchievementArchives.Add(newArchive);
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToBackgroundAsync();
await appDbContext.AchievementArchives.AddAndSaveAsync(newArchive).ConfigureAwait(false);
return ArchiveAddResult.Added;
}
@@ -104,15 +102,12 @@ internal class AchievementService : IAchievementService
Guid archiveId = archive.InnerId;
List<EntityAchievement> entities = appDbContext.Achievements
.Where(a => a.ArchiveId == archiveId)
// Important! Prevent multiple sql command for SingleOrDefault below.
.ToList();
List<BindingAchievement> results = new();
foreach (MetadataAchievement meta in metadata)
{
EntityAchievement entity = entities.SingleOrDefault(e => e.Id == meta.Id)
?? EntityAchievement.Create(archiveId, meta.Id);
EntityAchievement entity = entities.SingleOrDefault(e => e.Id == meta.Id) ?? EntityAchievement.Create(archiveId, meta.Id);
results.Add(new(meta, entity));
}
@@ -121,8 +116,9 @@ internal class AchievementService : IAchievementService
}
/// <inheritdoc/>
public Task<UIAF> ExportToUIAFAsync(EntityArchive archive)
public async Task<UIAF> ExportToUIAFAsync(EntityArchive archive)
{
await ThreadHelper.SwitchToBackgroundAsync();
List<UIAFItem> list = appDbContext.Achievements
.Where(i => i.ArchiveId == archive.InnerId)
.AsEnumerable()
@@ -135,12 +131,15 @@ internal class AchievementService : IAchievementService
List = list,
};
return Task.FromResult(uigf);
return uigf;
}
/// <inheritdoc/>
public ImportResult ImportFromUIAF(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy)
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;
switch (strategy)
@@ -170,12 +169,6 @@ internal class AchievementService : IAchievementService
}
}
/// <inheritdoc/>
public Task<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy)
{
return Task.Run(() => ImportFromUIAF(archive, list, strategy));
}
/// <inheritdoc/>
public void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements)
{

View File

@@ -41,15 +41,6 @@ internal interface IAchievementService
/// <returns>成就存档集合</returns>
ObservableCollection<EntityArchive> GetArchiveCollection();
/// <summary>
/// 导入UIAF数据
/// </summary>
/// <param name="archive">用户</param>
/// <param name="list">UIAF数据</param>
/// <param name="strategy">选项</param>
/// <returns>导入结果</returns>
ImportResult ImportFromUIAF(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy);
/// <summary>
/// 异步导入UIAF数据
/// </summary>

View File

@@ -37,6 +37,7 @@ internal partial class AnnouncementService : IAnnouncementService
return Must.NotNull((AnnouncementWrapper)cache!);
}
await ThreadHelper.SwitchToBackgroundAsync();
AnnouncementWrapper? wrapper = await announcementClient
.GetAnnouncementsAsync(cancellationToken)
.ConfigureAwait(false);

View File

@@ -228,6 +228,11 @@ internal class AvatarInfoService : IAvatarInfoService
AvatarDetail? detailAvatar = await calculateClient.GetAvatarDetailAsync(userAndRole.User, userAndRole.Role, avatar).ConfigureAwait(false);
if (detailAvatar == null)
{
continue;
}
ModelAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == avatar.Id);
if (entity == null)

View File

@@ -65,7 +65,7 @@ internal static class SummaryHelper
return new();
}
Dictionary<string, int> skillLevelMapCopy = new(skillLevelMap);
Dictionary<string, int> skillExtraLeveledMap = new(skillLevelMap);
if (proudSkillExtraLevelMap != null)
{
@@ -74,7 +74,7 @@ internal static class SummaryHelper
int skillGroupIdInt32 = int.Parse(skillGroupId);
int skillId = proudSkills.Single(p => p.GroupId == skillGroupIdInt32).Id;
skillLevelMapCopy.Increase($"{skillId}", extraLevel);
skillExtraLeveledMap.Increase(skillId.ToString(), extraLevel);
}
}
@@ -87,7 +87,9 @@ internal static class SummaryHelper
Name = proudableSkill.Name,
Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon),
Description = proudableSkill.Description,
Info = DescParamDescriptor.Convert(proudableSkill.Proud, skillLevelMapCopy[$"{proudableSkill.Id}"]),
Level = skillLevelMap[proudableSkill.Id.ToString()],
Info = DescParamDescriptor.Convert(proudableSkill.Proud, skillExtraLeveledMap[proudableSkill.Id.ToString()]),
};
skills.Add(skill);

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Model.Binding.AvatarProperty;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Service.AvatarInfo;

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Service.Cultivation;
/// <summary>
/// 养成计算服务
/// </summary>
[Injection(InjectAs.Singleton, typeof(ICultivationService))]
internal class CultivationService : ICultivationService
{
private readonly DbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent;
/// <summary>
/// 构造一个新的养成计算服务
/// </summary>
/// <param name="appDbContext">数据库上下文</param>
/// <param name="messenger">消息器</param>
public CultivationService(AppDbContext appDbContext, IMessenger messenger)
{
dbCurrent = new(appDbContext.CultivateProjects, messenger);
}
/// <summary>
/// 当前养成计划
/// </summary>
public CultivateProject? Current
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Cultivation;
/// <summary>
/// 养成计算服务
/// </summary>
internal interface ICultivationService
{
}

View File

@@ -0,0 +1,164 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Extension;
using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
using System.Collections.ObjectModel;
using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote;
namespace Snap.Hutao.Service.DailyNote;
/// <summary>
/// 实时便笺通知器
/// </summary>
internal class DailyNoteNotifier
{
private readonly IServiceScopeFactory scopeFactory;
private readonly BindingClient bindingClient;
private readonly DailyNoteEntry entry;
/// <summary>
/// 构造一个新的实时便笺通知器
/// </summary>
/// <param name="scopeFactory">范围工厂</param>
/// <param name="bindingClient">绑定客户端</param>
/// <param name="entry">实时便笺入口</param>
public DailyNoteNotifier(IServiceScopeFactory scopeFactory, BindingClient bindingClient, DailyNoteEntry entry)
{
this.scopeFactory = scopeFactory;
this.bindingClient = bindingClient;
this.entry = entry;
}
/// <summary>
/// 异步通知
/// </summary>
/// <returns>任务</returns>
public async ValueTask NotifyAsync()
{
if (entry.DailyNote == null)
{
return;
}
List<string> hints = new();
// NotifySuppressed judge
{
if (entry.DailyNote.CurrentResin >= entry.ResinNotifyThreshold)
{
if (!entry.ResinNotifySuppressed)
{
hints.Add($"当前原粹树脂:{entry.DailyNote.CurrentResin}");
entry.ResinNotifySuppressed = true;
}
}
else
{
entry.ResinNotifySuppressed = false;
}
if (entry.DailyNote.CurrentHomeCoin >= entry.HomeCoinNotifyThreshold)
{
if (!entry.HomeCoinNotifySuppressed)
{
hints.Add($"当前洞天宝钱:{entry.DailyNote.CurrentHomeCoin}");
entry.HomeCoinNotifySuppressed = true;
}
}
else
{
entry.HomeCoinNotifySuppressed = false;
}
if (entry.DailyTaskNotify && !entry.DailyNote.IsExtraTaskRewardReceived)
{
if (!entry.DailyTaskNotifySuppressed)
{
hints.Add(entry.DailyNote.ExtraTaskRewardDescription);
entry.DailyTaskNotifySuppressed = true;
}
}
else
{
entry.DailyTaskNotifySuppressed = false;
}
if (entry.TransformerNotify && entry.DailyNote.Transformer.Obtained && entry.DailyNote.Transformer.RecoveryTime.Reached)
{
if (!entry.TransformerNotifySuppressed)
{
hints.Add("参量质变仪已准备完成");
entry.TransformerNotifySuppressed = true;
}
}
else
{
entry.TransformerNotifySuppressed = false;
}
if (entry.ExpeditionNotify && entry.DailyNote.Expeditions.All(e => e.Status == ExpeditionStatus.Finished))
{
if (!entry.ExpeditionNotifySuppressed)
{
hints.Add("探索派遣已完成");
entry.ExpeditionNotifySuppressed = true;
}
}
else
{
entry.ExpeditionNotifySuppressed = false;
}
}
if (hints.Count <= 0)
{
return;
}
List<UserGameRole> roles = await bindingClient.GetUserGameRolesByCookieAsync(entry.User).ConfigureAwait(false);
string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色";
ToastContentBuilder builder = new ToastContentBuilder()
.AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE")
.AddAttributionText(attribution)
.AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid))
.AddButton(new ToastButtonDismiss("我知道了"));
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString).GetBoolean())
{
builder.SetToastScenario(ToastScenario.Reminder);
}
}
if (hints.Count > 2)
{
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
{
builder.AddText(hint);
}
}
await ThreadHelper.SwitchToMainThreadAsync();
builder.Show();
}
}

View File

@@ -82,7 +82,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<DailyNoteEntry> entryList = appDbContext.DailyNotes.ToList();
List<DailyNoteEntry> entryList = appDbContext.DailyNotes.AsNoTracking().ToList();
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
entries = new(appDbContext.DailyNotes);
}
@@ -113,7 +113,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
if (notify)
{
await NotifyDailyNoteAsync(bindingClient, entry).ConfigureAwait(false);
await new DailyNoteNotifier(scopeFactory, bindingClient, entry).NotifyAsync().ConfigureAwait(false);
}
}
@@ -128,124 +128,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.DailyNotes.RemoveAndSave(entry);
scope.ServiceProvider.GetRequiredService<AppDbContext>().DailyNotes.RemoveAndSave(entry);
}
}
private async ValueTask NotifyDailyNoteAsync(BindingClient client, DailyNoteEntry entry)
{
if (entry.DailyNote == null)
{
return;
}
List<string> hints = new();
// NotifySuppressed judge
{
if (entry.DailyNote.CurrentResin >= entry.ResinNotifyThreshold)
{
if (!entry.ResinNotifySuppressed)
{
hints.Add($"当前原粹树脂:{entry.DailyNote.CurrentResin}");
entry.ResinNotifySuppressed = true;
}
}
else
{
entry.ResinNotifySuppressed = false;
}
if (entry.DailyNote.CurrentHomeCoin >= entry.HomeCoinNotifyThreshold)
{
if (!entry.HomeCoinNotifySuppressed)
{
hints.Add($"当前洞天宝钱:{entry.DailyNote.CurrentHomeCoin}");
entry.HomeCoinNotifySuppressed = true;
}
}
else
{
entry.HomeCoinNotifySuppressed = false;
}
if (entry.DailyTaskNotify && !entry.DailyNote.IsExtraTaskRewardReceived)
{
if (!entry.DailyTaskNotifySuppressed)
{
hints.Add(entry.DailyNote.ExtraTaskRewardDescription);
entry.DailyTaskNotifySuppressed = true;
}
}
else
{
entry.DailyTaskNotifySuppressed = false;
}
if (entry.TransformerNotify && entry.DailyNote.Transformer.Obtained && entry.DailyNote.Transformer.RecoveryTime.Reached)
{
if (!entry.TransformerNotifySuppressed)
{
hints.Add("参量质变仪已准备完成");
entry.TransformerNotifySuppressed = true;
}
}
else
{
entry.TransformerNotifySuppressed = false;
}
if (entry.ExpeditionNotify && entry.DailyNote.Expeditions.All(e => e.Status == ExpeditionStatus.Finished))
{
if (!entry.ExpeditionNotifySuppressed)
{
hints.Add("探索派遣已完成");
entry.ExpeditionNotifySuppressed = true;
}
}
else
{
entry.ExpeditionNotifySuppressed = false;
}
}
if (hints.Count <= 0)
{
return;
}
List<UserGameRole> roles = await client.GetUserGameRolesByCookieAsync(entry.User).ConfigureAwait(false);
string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色";
ToastContentBuilder builder = new ToastContentBuilder()
.AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE")
.AddAttributionText(attribution)
.AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid))
.AddButton(new ToastButtonDismiss("我知道了"));
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, false.ToString()).GetBoolean())
{
builder.SetToastScenario(ToastScenario.Reminder);
}
}
if (hints.Count > 2)
{
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
{
builder.AddText(hint);
}
}
await ThreadHelper.SwitchToMainThreadAsync();
builder.Show();
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using System.Collections.Immutable;
namespace Snap.Hutao.Service.GachaLog.Factory;
@@ -10,22 +11,29 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
/// </summary>
public class GachaConfigTypeComparar : IComparer<GachaConfigType>
{
private static readonly GachaConfigTypeComparar InnerShared = new();
private static readonly ImmutableDictionary<GachaConfigType, int> OrderMap = new Dictionary<GachaConfigType, int>()
{
{ GachaConfigType.AvatarEventWish, 0 },
{ GachaConfigType.AvatarEventWish2, 1 },
{ GachaConfigType.WeaponEventWish, 2 },
{ GachaConfigType.PermanentWish, 3 },
{ GachaConfigType.NoviceWish, 4 },
}.ToImmutableDictionary();
/// <summary>
/// 共享的比较器
/// </summary>
public static GachaConfigTypeComparar Shared { get => InnerShared; }
/// <inheritdoc/>
public int Compare(GachaConfigType x, GachaConfigType y)
{
return GetOrder(x) - GetOrder(y);
return OrderOf(x) - OrderOf(y);
}
private static int GetOrder(GachaConfigType type)
private static int OrderOf(GachaConfigType type)
{
return type switch
{
GachaConfigType.AvatarEventWish => 0,
GachaConfigType.AvatarEventWish2 => 1,
GachaConfigType.WeaponEventWish => 2,
GachaConfigType.PermanentWish => 3,
GachaConfigType.NoviceWish => 4,
_ => 0,
};
return OrderMap.GetValueOrDefault(type, 0);
}
}

View File

@@ -3,8 +3,6 @@
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using System.Security.Cryptography;
using System.Text;
using Windows.UI;
@@ -62,35 +60,11 @@ public static class GachaStatisticsExtensions
/// <summary>
/// 将计数器转换为统计物品列表
/// </summary>
/// <typeparam name="TItem">物品类型</typeparam>
/// <param name="dict">计数器</param>
/// <returns>统计物品列表</returns>
public static List<StatisticsItem> ToStatisticsList(this Dictionary<IStatisticsItemSource, int> dict)
{
return dict
.Select(kvp => kvp.Key.ToStatisticsItem(kvp.Value))
.OrderByDescending(item => item.Count)
.ToList();
}
/// <summary>
/// 将计数器转换为统计物品列表
/// </summary>
/// <param name="dict">计数器</param>
/// <returns>统计物品列表</returns>
public static List<StatisticsItem> ToStatisticsList(this Dictionary<Avatar, int> dict)
{
return dict
.Select(kvp => kvp.Key.ToStatisticsItem(kvp.Value))
.OrderByDescending(item => item.Count)
.ToList();
}
/// <summary>
/// 将计数器转换为统计物品列表
/// </summary>
/// <param name="dict">计数器</param>
/// <returns>统计物品列表</returns>
public static List<StatisticsItem> ToStatisticsList(this Dictionary<Weapon, int> dict)
public static List<StatisticsItem> ToStatisticsList<TItem>(this Dictionary<TItem, int> dict)
where TItem : IStatisticsItemSource
{
return dict
.Select(kvp => kvp.Key.ToStatisticsItem(kvp.Value))

View File

@@ -3,6 +3,7 @@
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Entity;
@@ -45,20 +46,13 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
Dictionary<string, Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync().ConfigureAwait(false);
List<GachaEvent> gachaevents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaevents.Select(g => new HistoryWishBuilder(g, nameAvatarMap, nameWeaponMap)).ToList();
SettingEntry? entry = await appDbContext.Settings
.SingleOrDefaultAsync(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible)
SettingEntry entry = await appDbContext.Settings
.SingleOrAddAsync(SettingEntry.IsEmptyHistoryWishVisible, SettingEntryHelper.TrueString)
.ConfigureAwait(false);
if (entry == null)
{
entry = new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString());
appDbContext.Settings.Add(entry);
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
}
bool isEmptyHistoryWishVisible = bool.Parse(entry.Value!);
bool isEmptyHistoryWishVisible = entry.GetBoolean();
IOrderedEnumerable<GachaItem> orderedItems = items.OrderBy(i => i.Id);
return await Task.Run(() => CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap, isEmptyHistoryWishVisible)).ConfigureAwait(false);
@@ -71,9 +65,9 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
Dictionary<WeaponId, Weapon> weaponMap,
bool isEmptyHistoryWishVisible)
{
TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.PermanentWish, 90, 10);
TypedWishSummaryBuilder avatarWishBuilder = new("角色活动", TypedWishSummaryBuilder.AvatarEventWish, 90, 10);
TypedWishSummaryBuilder weaponWishBuilder = new("神铸赋形", TypedWishSummaryBuilder.WeaponEventWish, 80, 10);
TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.IsPermanentWish, 90, 10);
TypedWishSummaryBuilder avatarWishBuilder = new("角色活动", TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10);
TypedWishSummaryBuilder weaponWishBuilder = new("神铸赋形", TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10);
Dictionary<Avatar, int> orangeAvatarCounter = new();
Dictionary<Avatar, int> purpleAvatarCounter = new();
@@ -86,7 +80,6 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
foreach (GachaItem item in items)
{
// Find target history wish to operate.
// TODO: improve performance.
HistoryWishBuilder? targetHistoryWishBuilder = historyWishBuilders
.Where(w => w.ConfigType == item.GachaType)
.SingleOrDefault(w => w.From <= item.Time && w.To >= item.Time);
@@ -101,11 +94,11 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
{
case ItemQuality.QUALITY_ORANGE:
orangeAvatarCounter.Increase(avatar);
isUp = targetHistoryWishBuilder?.IncreaseOrangeAvatar(avatar) ?? false;
isUp = targetHistoryWishBuilder?.IncreaseOrange(avatar) ?? false;
break;
case ItemQuality.QUALITY_PURPLE:
purpleAvatarCounter.Increase(avatar);
targetHistoryWishBuilder?.IncreasePurpleAvatar(avatar);
targetHistoryWishBuilder?.IncreasePurple(avatar);
break;
}
@@ -123,15 +116,15 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
switch (weapon.RankLevel)
{
case ItemQuality.QUALITY_ORANGE:
isUp = targetHistoryWishBuilder?.IncreaseOrangeWeapon(weapon) ?? false;
isUp = targetHistoryWishBuilder?.IncreaseOrange(weapon) ?? false;
orangeWeaponCounter.Increase(weapon);
break;
case ItemQuality.QUALITY_PURPLE:
targetHistoryWishBuilder?.IncreasePurpleWeapon(weapon);
targetHistoryWishBuilder?.IncreasePurple(weapon);
purpleWeaponCounter.Increase(weapon);
break;
case ItemQuality.QUALITY_BLUE:
targetHistoryWishBuilder?.IncreaseBlueWeapon(weapon);
targetHistoryWishBuilder?.IncreaseBlue(weapon);
blueWeaponCounter.Increase(weapon);
break;
}
@@ -153,8 +146,9 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
HistoryWishes = historyWishBuilders
.Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty))
.OrderByDescending(builder => builder.From)
.ThenBy(builder => builder.ConfigType, new GachaConfigTypeComparar())
.Select(builder => builder.ToHistoryWish()).ToList(),
.ThenBy(builder => builder.ConfigType, GachaConfigTypeComparar.Shared)
.Select(builder => builder.ToHistoryWish())
.ToList(),
// avatars
OrangeAvatars = orangeAvatarCounter.ToStatisticsList(),

View File

@@ -56,79 +56,47 @@ internal class HistoryWishBuilder
public GachaConfigType ConfigType { get => configType; }
/// <inheritdoc cref="GachaEvent.From"/>
public DateTimeOffset From
{
get => gachaEvent.From;
}
public DateTimeOffset From { get => gachaEvent.From; }
/// <inheritdoc cref="GachaEvent.To"/>
public DateTimeOffset To
{
get => gachaEvent.To;
}
public DateTimeOffset To { get => gachaEvent.To; }
/// <summary>
/// 卡池是否为空
/// </summary>
public bool IsEmpty
{
get => totalCountTracker <= 0;
}
public bool IsEmpty { get => totalCountTracker <= 0; }
/// <summary>
/// 计数五星角色
/// 计数五星物品
/// </summary>
/// <param name="avatar">角色</param>
/// <returns>是否为Up角色</returns>
public bool IncreaseOrangeAvatar(Avatar avatar)
/// <param name="item">物品</param>
/// <returns>是否为Up物品</returns>
public bool IncreaseOrange(IStatisticsItemSource item)
{
orangeCounter.Increase(avatar);
orangeCounter.Increase(item);
++totalCountTracker;
return orangeUpCounter.TryIncrease(avatar);
return orangeUpCounter.TryIncrease(item);
}
/// <summary>
/// 计数四星角色
/// 计数四星物品
/// </summary>
/// <param name="avatar">角色</param>
public void IncreasePurpleAvatar(Avatar avatar)
/// <param name="item">物品</param>
public void IncreasePurple(IStatisticsItemSource item)
{
purpleUpCounter.TryIncrease(avatar);
purpleCounter.Increase(avatar);
++totalCountTracker;
}
/// <summary>
/// 计数五星武器
/// </summary>
/// <param name="weapon">武器</param>
/// <returns>是否为Up武器</returns>
public bool IncreaseOrangeWeapon(Weapon weapon)
{
orangeCounter.Increase(weapon);
++totalCountTracker;
return orangeUpCounter.TryIncrease(weapon);
}
/// <summary>
/// 计数四星武器
/// </summary>
/// <param name="weapon">武器</param>
public void IncreasePurpleWeapon(Weapon weapon)
{
purpleUpCounter.TryIncrease(weapon);
purpleCounter.Increase(weapon);
purpleUpCounter.TryIncrease(item);
purpleCounter.Increase(item);
++totalCountTracker;
}
/// <summary>
/// 计数三星武器
/// </summary>
/// <param name="weapon">武器</param>
public void IncreaseBlueWeapon(Weapon weapon)
/// <param name="item">武器</param>
public void IncreaseBlue(IStatisticsItemSource item)
{
blueCounter.Increase(weapon);
blueCounter.Increase(item);
++totalCountTracker;
}

View File

@@ -18,17 +18,17 @@ internal class TypedWishSummaryBuilder
/// <summary>
/// 常驻祈愿
/// </summary>
public static readonly Func<GachaConfigType, bool> PermanentWish = type => type is GachaConfigType.PermanentWish;
public static readonly Func<GachaConfigType, bool> IsPermanentWish = type => type is GachaConfigType.PermanentWish;
/// <summary>
/// 角色活动
/// </summary>
public static readonly Func<GachaConfigType, bool> AvatarEventWish = type => type is GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2;
public static readonly Func<GachaConfigType, bool> IsAvatarEventWish = type => type is GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2;
/// <summary>
/// 武器活动
/// </summary>
public static readonly Func<GachaConfigType, bool> WeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
public static readonly Func<GachaConfigType, bool> IsWeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
private readonly string name;
private readonly int guarenteeOrangeThreshold;
@@ -37,7 +37,7 @@ internal class TypedWishSummaryBuilder
private readonly List<int> averageOrangePullTracker = new();
private readonly List<int> averageUpOrangePullTracker = new();
private readonly List<SummaryItem> summaryItemCache = new();
private readonly List<SummaryItem> summaryItems = new();
private int maxOrangePullTracker;
private int minOrangePullTracker;
@@ -98,7 +98,7 @@ internal class TypedWishSummaryBuilder
lastUpOrangePullTracker = 0;
}
summaryItemCache.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
lastOrangePullTracker = 0;
++totalOrangePullTracker;
@@ -127,8 +127,8 @@ internal class TypedWishSummaryBuilder
/// <returns>类型化祈愿统计信息</returns>
public TypedWishSummary ToTypedWishSummary()
{
summaryItemCache.CompleteAdding();
double totalCountDouble = totalCountTracker;
summaryItems.CompleteAdding();
double totalCount = totalCountTracker;
return new()
{
@@ -148,12 +148,12 @@ internal class TypedWishSummaryBuilder
TotalOrangePull = totalOrangePullTracker,
TotalPurplePull = totalPurplePullTracker,
TotalBluePull = totalBluePullTracker,
TotalOrangePercent = totalOrangePullTracker / totalCountDouble,
TotalPurplePercent = totalPurplePullTracker / totalCountDouble,
TotalBluePercent = totalBluePullTracker / totalCountDouble,
TotalOrangePercent = totalOrangePullTracker / totalCount,
TotalPurplePercent = totalPurplePullTracker / totalCount,
TotalBluePercent = totalBluePullTracker / totalCount,
AverageOrangePull = averageOrangePullTracker.AverageNoThrow(),
AverageUpOrangePull = averageUpOrangePullTracker.AverageNoThrow(),
OrangeList = summaryItemCache,
OrangeList = summaryItems,
};
}

View File

@@ -33,13 +33,13 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
/// <summary>
/// 祈愿记录查询的类型
/// </summary>
private static readonly ImmutableList<GachaConfigType> QueryTypes = ImmutableList.Create(new GachaConfigType[]
private static readonly ImmutableList<GachaConfigType> QueryTypes = new List<GachaConfigType>
{
GachaConfigType.NoviceWish,
GachaConfigType.PermanentWish,
GachaConfigType.AvatarEventWish,
GachaConfigType.WeaponEventWish,
});
}.ToImmutableList();
private readonly AppDbContext appDbContext;
private readonly IEnumerable<IGachaLogUrlProvider> urlProviders;
@@ -56,6 +56,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
private Dictionary<AvatarId, Model.Metadata.Avatar.Avatar>? idAvatarMap;
private Dictionary<WeaponId, Model.Metadata.Weapon.Weapon>? idWeaponMap;
private ObservableCollection<GachaArchive>? archiveCollection;
/// <summary>
@@ -100,8 +101,6 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
/// <inheritdoc/>
public Task<UIGF> ExportToUIGFAsync(GachaArchive archive)
{
Verify.Operation(IsInitialized, "祈愿记录服务未能正常初始化");
List<UIGFItem> list = appDbContext.GachaItems
.Where(i => i.ArchiveId == archive.InnerId)
.AsEnumerable()
@@ -179,16 +178,31 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
}
/// <inheritdoc/>
public Task ImportFromUIGFAsync(List<UIGFItem> list, string uid)
public async Task ImportFromUIGFAsync(List<UIGFItem> list, string uid)
{
return Task.Run(() => ImportFromUIGF(list, uid));
await ThreadHelper.SwitchToBackgroundAsync();
GachaArchive? archive = null;
SkipOrInitArchive(ref archive, uid);
Guid archiveId = Must.NotNull(archive!).InnerId;
long trimId = appDbContext.GachaItems
.Where(i => i.ArchiveId == archiveId)
.OrderBy(i => i.Id)
.FirstOrDefault()?.Id ?? long.MaxValue;
IEnumerable<GachaItem> toAdd = list
.OrderByDescending(i => i.Id)
.Where(i => i.Id < trimId)
.Select(i => GachaItem.Create(archiveId, i, GetItemId(i)));
await appDbContext.GachaItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false);
CurrentArchive = archive;
}
/// <inheritdoc/>
public async Task<bool> RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token)
{
Verify.Operation(IsInitialized, "祈愿记录服务未能正常初始化");
bool isLazy = strategy switch
{
RefreshStrategy.AggressiveMerge => false,
@@ -204,17 +218,17 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
/// <inheritdoc/>
public async Task RemoveArchiveAsync(GachaArchive archive)
{
Must.NotNull(archiveCollection!);
// Sync cache
archiveCollection.Remove(archive);
await ThreadHelper.SwitchToMainThreadAsync();
archiveCollection!.Remove(archive);
// Sync database
await ThreadHelper.SwitchToBackgroundAsync();
await appDbContext.GachaItems
.Where(item => item.ArchiveId == archive.InnerId)
.ExecuteDeleteAsync()
.ConfigureAwait(false);
appDbContext.GachaArchives.RemoveAndSave(archive);
await appDbContext.GachaArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false);
}
private static Task RandomDelayAsync(CancellationToken token)
@@ -222,26 +236,6 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
return Task.Delay(TimeSpan.FromSeconds(Random.Shared.NextDouble() + 1), token);
}
private void ImportFromUIGF(List<UIGFItem> list, string uid)
{
GachaArchive? archive = null;
SkipOrInitArchive(ref archive, uid);
Guid archiveId = Must.NotNull(archive!).InnerId;
long trimId = appDbContext.GachaItems
.Where(i => i.ArchiveId == archiveId)
.OrderBy(i => i.Id)
.FirstOrDefault()?.Id ?? long.MaxValue;
IEnumerable<GachaItem> toAdd = list
.OrderByDescending(i => i.Id)
.Where(i => i.Id < trimId)
.Select(i => GachaItem.Create(archiveId, i, GetItemId(i)));
appDbContext.GachaItems.AddRangeAndSave(toAdd);
CurrentArchive = archive;
}
private async Task<ValueResult<bool, GachaArchive?>> FetchGachaLogsAsync(string query, bool isLazy, IProgress<FetchState> progress, CancellationToken token)
{
GachaArchive? archive = null;

View File

@@ -18,7 +18,6 @@ internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider
public async Task<ValueResult<bool, string>> GetQueryAsync()
{
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
await ThreadHelper.SwitchToMainThreadAsync();
ValueResult<bool, string> result = await new GachaLogUrlDialog(mainWindow).GetInputUrlAsync().ConfigureAwait(false);
if (result.IsOk)

View File

@@ -48,18 +48,13 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
{
string cacheFile = GetCacheFile(path);
TemporaryFile tempFile;
try
using (TemporaryFile? tempFile = TemporaryFile.CreateFromFileCopy(cacheFile))
{
tempFile = TemporaryFile.CreateFromFileCopy(cacheFile);
}
catch (DirectoryNotFoundException)
{
return new(false, $"找不到原神内置浏览器缓存路径:\n{cacheFile}");
}
if (tempFile == null)
{
return new(false, $"找不到原神内置浏览器缓存路径:\n{cacheFile}");
}
using (tempFile)
{
using (FileStream fileStream = new(tempFile.Path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (MemoryStream memoryStream = new())
@@ -80,8 +75,8 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
private static string? Match(MemoryStream stream)
{
ReadOnlySpan<byte> span = stream.ToArray();
ReadOnlySpan<byte> match = Encoding.UTF8.GetBytes("https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v2/index.html");
ReadOnlySpan<byte> zero = Encoding.UTF8.GetBytes("\0");
ReadOnlySpan<byte> match = "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v2/index.html"u8;
ReadOnlySpan<byte> zero = "\0"u8;
int index = span.LastIndexOf(match);
if (index >= 0)

View File

@@ -23,8 +23,7 @@ namespace Snap.Hutao.Service.Game;
/// 游戏服务
/// </summary>
[Injection(InjectAs.Singleton, typeof(IGameService))]
[SuppressMessage("", "CA1001")]
internal class GameService : IGameService
internal class GameService : IGameService, IDisposable
{
private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
private const string ConfigFile = "config.ini";
@@ -59,21 +58,21 @@ internal class GameService : IGameService
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, string.Empty), out bool added);
SettingEntry entry = await appDbContext.Settings.SingleOrAddAsync(SettingEntry.GamePath, string.Empty).ConfigureAwait(false);
// Cannot find in setting
if (added)
if (string.IsNullOrEmpty(entry.Value))
{
IEnumerable<IGameLocator> gameLocators = scope.ServiceProvider.GetRequiredService<IEnumerable<IGameLocator>>();
// Try locate by registry
IGameLocator locator = gameLocators.Single(l => l.Name == nameof(RegistryLauncherLocator));
IGameLocator locator = gameLocators.Single(l => l.Name == nameof(UnityLogGameLocator));
ValueResult<bool, string> result = await locator.LocateGamePathAsync().ConfigureAwait(false);
if (!result.IsOk)
{
// Try locate manually
locator = gameLocators.Single(l => l.Name == nameof(ManualGameLocator));
locator = gameLocators.Single(l => l.Name == nameof(RegistryLauncherLocator));
result = await locator.LocateGamePathAsync().ConfigureAwait(false);
}
@@ -85,7 +84,7 @@ internal class GameService : IGameService
}
else
{
return new(false, null!);
return new(false, "请启动游戏后再次尝试");
}
}
@@ -95,7 +94,7 @@ internal class GameService : IGameService
}
// Set cache and return.
string path = memoryCache.Set(GamePathKey, Must.NotNull(entry.Value!));
string path = memoryCache.Set(GamePathKey, entry.Value);
return new(true, path);
}
}
@@ -113,13 +112,10 @@ internal class GameService : IGameService
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added);
entry.Value ??= string.Empty;
appDbContext.Settings.UpdateAndSave(entry);
SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.GamePath, string.Empty);
// Set cache and return.
return memoryCache.Set(GamePathKey, entry.Value);
return memoryCache.Set(GamePathKey, entry.Value!);
}
}
}
@@ -134,7 +130,7 @@ internal class GameService : IGameService
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out _);
SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.GamePath, string.Empty);
entry.Value = path;
appDbContext.Settings.UpdateAndSave(entry);
}
@@ -222,7 +218,7 @@ internal class GameService : IGameService
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
gameAccounts = new(appDbContext.GameAccounts.ToList());
gameAccounts = new(appDbContext.GameAccounts.AsNoTracking().ToList());
}
}
@@ -290,6 +286,7 @@ internal class GameService : IGameService
}
catch (Win32Exception)
{
// 通常是用户取消了UAC
}
}
}
@@ -318,10 +315,14 @@ internal class GameService : IGameService
gameAccounts.Add(GameAccount.Create(name, registrySdk));
// sync database
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.GameAccounts.AddAndSave(account);
await scope.ServiceProvider
.GetRequiredService<AppDbContext>()
.GameAccounts
.AddAndSaveAsync(account)
.ConfigureAwait(false);
}
}
}
@@ -339,9 +340,8 @@ internal class GameService : IGameService
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
gameAccount.UpdateAttachUid(uid);
appDbContext.GameAccounts.UpdateAndSave(gameAccount);
scope.ServiceProvider.GetRequiredService<AppDbContext>().GameAccounts.UpdateAndSave(gameAccount);
}
}
@@ -349,18 +349,18 @@ internal class GameService : IGameService
public async ValueTask ModifyGameAccountAsync(GameAccount gameAccount)
{
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
(bool isOk, string name) = await new GameAccountNameDialog(mainWindow).GetInputNameAsync().ConfigureAwait(false);
(bool isOk, string name) = await new GameAccountNameDialog(mainWindow).GetInputNameAsync().ConfigureAwait(true);
if (isOk)
{
await ThreadHelper.SwitchToMainThreadAsync();
gameAccount.UpdateName(name);
// sync database
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.GameAccounts.UpdateAndSave(gameAccount);
await appDbContext.GameAccounts.UpdateAndSaveAsync(gameAccount).ConfigureAwait(false);
}
}
}
@@ -368,20 +368,19 @@ internal class GameService : IGameService
/// <inheritdoc/>
public async ValueTask RemoveGameAccountAsync(GameAccount gameAccount)
{
Must.NotNull(gameAccounts!).Remove(gameAccount);
await ThreadHelper.SwitchToMainThreadAsync();
gameAccounts!.Remove(gameAccount);
await Task.Yield();
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
try
{
appDbContext.GameAccounts.RemoveAndSave(gameAccount);
}
catch (DbUpdateConcurrencyException)
{
// This gameAccount has already been deleted.
}
await scope.ServiceProvider.GetRequiredService<AppDbContext>().GameAccounts.RemoveAndSaveAsync(gameAccount).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public void Dispose()
{
gameSemaphore?.Dispose();
}
}

View File

@@ -16,12 +16,4 @@ internal interface IGameLocator : INamed
/// </summary>
/// <returns>游戏位置</returns>
Task<ValueResult<bool, string>> LocateGamePathAsync();
/// <summary>
/// 异步获取游戏启动器位置
/// 路径应当包含启动器文件名称
/// </summary>
/// <returns>游戏启动器位置</returns>
[Obsolete("不应定位启动器位置")]
Task<ValueResult<bool, string>> LocateLauncherPathAsync();
}

View File

@@ -33,12 +33,6 @@ internal class ManualGameLocator : IGameLocator
return LocateInternalAsync("YuanShen.exe");
}
/// <inheritdoc/>
public Task<ValueResult<bool, string>> LocateLauncherPathAsync()
{
return LocateInternalAsync("launcher.exe");
}
private async Task<ValueResult<bool, string>> LocateInternalAsync(string fileName)
{
FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "选择游戏本体", ".exe");

View File

@@ -47,35 +47,31 @@ internal partial class RegistryLauncherLocator : IGameLocator
return Task.FromResult<ValueResult<bool, string>>(new(false, null!));
}
/// <inheritdoc/>
public Task<ValueResult<bool, string>> LocateLauncherPathAsync()
{
return Task.FromResult(LocateInternal("DisplayIcon"));
}
private static ValueResult<bool, string> LocateInternal(string key)
{
RegistryKey? uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神");
if (uninstallKey != null)
using (RegistryKey? uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神"))
{
if (uninstallKey.GetValue(key) is string path)
if (uninstallKey != null)
{
return new(true, path);
if (uninstallKey.GetValue(key) is string path)
{
return new(true, path);
}
else
{
return new(false, null!);
}
}
else
{
return new(false, null!);
}
}
else
{
return new(false, null!);
}
}
private static string Unescape(string str)
{
string? hex4Result = Utf16Regex().Replace(str, @"\u$1");
string? hex4Result = UTF16Regex().Replace(str, @"\u$1");
// 不包含中文
if (!hex4Result.Contains(@"\u"))
@@ -88,5 +84,5 @@ internal partial class RegistryLauncherLocator : IGameLocator
}
[GeneratedRegex("\\\\x([0-9a-f]{4})")]
private static partial Regex Utf16Regex();
private static partial Regex UTF16Regex();
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.IO;
using System.IO;
using System.Text.RegularExpressions;
namespace Snap.Hutao.Service.Game.Locator;
/// <summary>
/// Unity日志游戏定位器
/// </summary>
[Injection(InjectAs.Transient, typeof(IGameLocator))]
internal partial class UnityLogGameLocator : IGameLocator
{
/// <inheritdoc/>
public string Name { get => nameof(UnityLogGameLocator); }
/// <inheritdoc/>
public async Task<ValueResult<bool, string>> LocateGamePathAsync()
{
await ThreadHelper.SwitchToBackgroundAsync();
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string logFilePath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
using (var tempFile = TemporaryFile.CreateFromFileCopy(logFilePath))
{
if (tempFile == null)
{
return new(false, $"找不到 Unity 日志文件:\n{logFilePath}");
}
string content = File.ReadAllText(tempFile.Path);
var matchResult = WarmupFileLine().Match(content);
if (!matchResult.Success)
{
return new(false, $"在 Unity 日志文件中找不到游戏路径");
}
string entryName = matchResult.Groups[0].Value.Replace("_Data", ".exe");
string fullPath = Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
return new(true, fullPath);
}
}
[GeneratedRegex(@"(?m).:/.+YuanShen_Data")]
private static partial Regex WarmupFileLine();
}

View File

@@ -70,23 +70,18 @@ internal class HutaoCache : IHutaoCache
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
List<Task> tasks = new(5)
{
AvatarAppearanceRankAsync(idAvatarMap),
AvatarUsageRanksAsync(idAvatarMap),
AvatarConstellationInfosAsync(idAvatarMap),
TeamAppearancesAsync(idAvatarMap),
OverviewAsync(),
};
Task avatarAppearanceRankTask = AvatarAppearanceRankAsync(idAvatarMap);
Task avatarUsageRank = AvatarUsageRanksAsync(idAvatarMap);
Task avatarConstellationInfoTask = AvatarConstellationInfosAsync(idAvatarMap);
Task teamAppearanceTask = TeamAppearancesAsync(idAvatarMap);
Task ovewviewTask = OverviewAsync();
await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.WhenAll(
avatarAppearanceRankTask,
avatarUsageRank,
avatarConstellationInfoTask,
teamAppearanceTask,
ovewviewTask)
.ConfigureAwait(false);
isDatabaseViewModelInitialized = true;
return true;
return isDatabaseViewModelInitialized = true;
}
return false;

View File

@@ -88,14 +88,14 @@ internal class HutaoService : IHutaoService
if (appDbContext.ObjectCache.SingleOrDefault(e => e.Key == key) is ObjectCacheEntry entry)
{
if (entry.ExpireTime > DateTimeOffset.Now)
if (entry.IsExpired)
{
T value = JsonSerializer.Deserialize<T>(entry.Value!, options)!;
return memoryCache.Set(key, value, TimeSpan.FromMinutes(30));
await appDbContext.ObjectCache.RemoveAndSaveAsync(entry).ConfigureAwait(false);
}
else
{
appDbContext.ObjectCache.RemoveAndSave(entry);
T value = JsonSerializer.Deserialize<T>(entry.Value!, options)!;
return memoryCache.Set(key, value, TimeSpan.FromMinutes(30));
}
}

View File

@@ -87,19 +87,17 @@ internal class UserService : IUserService
/// <inheritdoc/>
public async Task RemoveUserAsync(BindingUser user)
{
await Task.Yield();
// Sync cache
await ThreadHelper.SwitchToMainThreadAsync();
userCollection!.Remove(user);
roleCollection?.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId);
// Sync database
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// Note: cascade deleted dailynotes
appDbContext.Users.RemoveAndSave(user.Entity);
await scope.ServiceProvider.GetRequiredService<AppDbContext>().Users.RemoveAndSaveAsync(user.Entity).ConfigureAwait(false);
}
messenger.Send(new UserRemovedMessage(user.Entity));
@@ -108,6 +106,7 @@ internal class UserService : IUserService
/// <inheritdoc/>
public async Task<ObservableCollection<BindingUser>> GetUserCollectionAsync()
{
await ThreadHelper.SwitchToBackgroundAsync();
if (userCollection == null)
{
List<BindingUser> users = new();
@@ -118,9 +117,7 @@ internal class UserService : IUserService
foreach (Model.Entity.User entity in appDbContext.Users)
{
BindingUser? initialized = await BindingUser
.ResumeAsync(entity)
.ConfigureAwait(false);
BindingUser? initialized = await BindingUser.ResumeAsync(entity).ConfigureAwait(false);
if (initialized != null)
{
@@ -129,7 +126,7 @@ internal class UserService : IUserService
else
{
// User is unable to be initialized, remove it.
appDbContext.Users.RemoveAndSave(entity);
await appDbContext.Users.RemoveAndSaveAsync(entity).ConfigureAwait(false);
}
}
}
@@ -144,6 +141,7 @@ internal class UserService : IUserService
/// <inheritdoc/>
public async Task<ObservableCollection<Model.Binding.User.UserAndRole>> GetRoleCollectionAsync()
{
await ThreadHelper.SwitchToBackgroundAsync();
if (roleCollection == null)
{
List<Model.Binding.User.UserAndRole> userAndRoles = new();
@@ -178,8 +176,7 @@ internal class UserService : IUserService
/// <inheritdoc/>
public async Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie)
{
Must.NotNull(userCollection!);
await ThreadHelper.SwitchToBackgroundAsync();
string? mid = cookie.GetValueOrDefault(Cookie.MID);
if (mid == null)
@@ -188,7 +185,7 @@ internal class UserService : IUserService
}
// 检查 mid 对应用户是否存在
if (UserHelper.TryGetUser(userCollection, mid, out BindingUser? user))
if (UserHelper.TryGetUser(userCollection!, mid, out BindingUser? user))
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
@@ -200,7 +197,7 @@ internal class UserService : IUserService
user.Ltoken = cookie.TryGetAsLtoken(out Cookie? ltoken) ? ltoken : user.Ltoken;
user.CookieToken = cookie.TryGetAsCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
appDbContext.Users.UpdateAndSave(user.Entity);
await appDbContext.Users.UpdateAndSaveAsync(user.Entity).ConfigureAwait(false);
return new(UserOptionResult.Updated, mid);
}
else
@@ -217,6 +214,7 @@ internal class UserService : IUserService
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie)
{
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
@@ -228,19 +226,22 @@ internal class UserService : IUserService
if (userCollection != null)
{
await ThreadHelper.SwitchToMainThreadAsync();
userCollection!.Add(newUser);
if (roleCollection != null)
{
foreach (UserGameRole role in newUser.UserGameRoles)
userCollection!.Add(newUser);
if (roleCollection != null)
{
roleCollection.Add(new(newUser.Entity, role));
foreach (UserGameRole role in newUser.UserGameRoles)
{
roleCollection.Add(new(newUser.Entity, role));
}
}
}
}
// Sync database
appDbContext.Users.AddAndSave(newUser.Entity);
await ThreadHelper.SwitchToBackgroundAsync();
await appDbContext.Users.AddAndSaveAsync(newUser.Entity).ConfigureAwait(false);
return new(UserOptionResult.Added, newUser.UserInfo!.Uid);
}
else

View File

@@ -4,22 +4,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Bridge;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace Snap.Hutao.View.Dialog;

View File

@@ -27,6 +27,7 @@ public sealed partial class GachaLogUrlDialog : ContentDialog
/// <returns>输入的结果</returns>
public async Task<ValueResult<bool, string>> GetInputUrlAsync()
{
await ThreadHelper.SwitchToMainThreadAsync();
ContentDialogResult result = await ShowAsync();
string url = InputText.Text;

View File

@@ -27,6 +27,7 @@ public sealed partial class GameAccountNameDialog : ContentDialog
/// <returns>输入的结果</returns>
public async Task<ValueResult<bool, string>> GetInputNameAsync()
{
await ThreadHelper.SwitchToMainThreadAsync();
ContentDialogResult result = await ShowAsync();
string text = InputText.Text;
return new(result == ContentDialogResult.Primary && (!string.IsNullOrEmpty(text)), text);

View File

@@ -24,7 +24,10 @@
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Background="{StaticResource CardBackgroundFillColorDefaultBrush}">
<Grid
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="252"/>
<ColumnDefinition/>

View File

@@ -13,7 +13,6 @@ using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.AvatarInfo;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.WinRT;
using Windows.Foundation;

View File

@@ -9,7 +9,6 @@ using Snap.Hutao.Context.FileSystem.Location;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.Model.Post;
using Windows.Storage;

View File

@@ -25,10 +25,10 @@ internal class WikiAvatarViewModel : ObservableObject
// filters
private readonly List<Selectable<string>> filterElementInfos;
private readonly List<Selectable<Pair<string, string>>> filterAssociationInfos;
private readonly List<Selectable<Pair<string, AssociationType>>> filterAssociationInfos;
private readonly List<Selectable<Pair<string, WeaponType>>> filterWeaponTypeInfos;
private readonly List<Selectable<Pair<string, ItemQuality>>> filterQualityInfos;
private readonly List<Selectable<Pair<string, string>>> filterBodyInfos;
private readonly List<Selectable<Pair<string, BodyType>>> filterBodyInfos;
private AdvancedCollectionView? avatars;
private Avatar? selected;
@@ -58,12 +58,12 @@ internal class WikiAvatarViewModel : ObservableObject
filterAssociationInfos = new()
{
new(new("蒙德", "ASSOC_TYPE_MONDSTADT"), OnFilterChanged),
new(new("璃月", "ASSOC_TYPE_LIYUE"), OnFilterChanged),
new(new("稻妻", "ASSOC_TYPE_INAZUMA"), OnFilterChanged),
new(new("须弥", "ASSOC_TYPE_SUMERU"), OnFilterChanged),
new(new("愚人众", "ASSOC_TYPE_FATUI"), OnFilterChanged),
new(new("游侠", "ASSOC_TYPE_RANGER"), OnFilterChanged),
new(new("蒙德", AssociationType.ASSOC_TYPE_MONDSTADT), OnFilterChanged),
new(new("璃月", AssociationType.ASSOC_TYPE_LIYUE), OnFilterChanged),
new(new("稻妻", AssociationType.ASSOC_TYPE_INAZUMA), OnFilterChanged),
new(new("须弥", AssociationType.ASSOC_TYPE_SUMERU), OnFilterChanged),
new(new("愚人众", AssociationType.ASSOC_TYPE_FATUI), OnFilterChanged),
new(new("游侠", AssociationType.ASSOC_TYPE_RANGER), OnFilterChanged),
};
filterWeaponTypeInfos = new()
@@ -84,11 +84,11 @@ internal class WikiAvatarViewModel : ObservableObject
filterBodyInfos = new()
{
new(new("成女", "BODY_LADY"), OnFilterChanged),
new(new("少女", "BODY_GIRL"), OnFilterChanged),
new(new("幼女", "BODY_LOLI"), OnFilterChanged),
new(new("成男", "BODY_MALE"), OnFilterChanged),
new(new("少男", "BODY_BOY"), OnFilterChanged),
new(new("成女", BodyType.BODY_LADY), OnFilterChanged),
new(new("少女", BodyType.BODY_GIRL), OnFilterChanged),
new(new("幼女", BodyType.BODY_LOLI), OnFilterChanged),
new(new("成男", BodyType.BODY_MALE), OnFilterChanged),
new(new("少男", BodyType.BODY_BOY), OnFilterChanged),
};
}
@@ -113,7 +113,7 @@ internal class WikiAvatarViewModel : ObservableObject
/// <summary>
/// 筛选用所属国家集合
/// </summary>
public IList<Selectable<Pair<string, string>>> FilterAssociationInfos
public IList<Selectable<Pair<string, AssociationType>>> FilterAssociationInfos
{
get => filterAssociationInfos;
}
@@ -137,7 +137,7 @@ internal class WikiAvatarViewModel : ObservableObject
/// <summary>
/// 筛选用体型信息集合
/// </summary>
public IList<Selectable<Pair<string, string>>> FilterBodyInfos
public IList<Selectable<Pair<string, BodyType>>> FilterBodyInfos
{
get => filterBodyInfos;
}
@@ -189,7 +189,7 @@ internal class WikiAvatarViewModel : ObservableObject
.Select(e => e.Value)
.ToList();
List<string> targetAssociations = filterAssociationInfos
List<AssociationType> targetAssociations = filterAssociationInfos
.Where(e => e.IsSelected)
.Select(e => e.Value.Value)
.ToList();
@@ -204,7 +204,7 @@ internal class WikiAvatarViewModel : ObservableObject
.Select(e => e.Value.Value)
.ToList();
List<string> targetBodies = filterBodyInfos
List<BodyType> targetBodies = filterBodyInfos
.Where(e => e.IsSelected)
.Select(e => e.Value.Value)
.ToList();

View File

@@ -165,7 +165,6 @@ internal static class ApiEndpoints
/// <summary>
/// 计算器角色技能列表
/// </summary>
/// <param name="avatarId">角色Id</param>
/// <param name="avatar">元素类型</param>
/// <returns>技能列表</returns>
public static string CalculateAvatarSkillList(Hoyolab.Takumi.Event.Calculate.Avatar avatar)

View File

@@ -22,6 +22,7 @@ namespace Snap.Hutao.Web.Bridge;
/// <summary>
/// 调用桥
/// </summary>
[SuppressMessage("", "SA1600")]
public class MiHoYoJSInterface
{
private const string InitializeJsInterfaceScript2 = """

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Converter;
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
@@ -21,13 +21,13 @@ public class GachaLogItem
/// 祈愿类型
/// </summary>
[JsonPropertyName("gacha_type")]
[JsonConverter(typeof(EnumStringValueConverter<GachaConfigType>))]
[JsonEnum(JsonSerializeType.Int32AsString)]
public GachaConfigType GachaType { get; set; } = default!;
/// <summary>
/// 总为 <see cref="string.Empty"/>
/// </summary>
[Obsolete("API clear this property")]
[Obsolete("API set this property empty")]
[JsonPropertyName("item_id")]
public string ItemId { get; set; } = string.Empty;
@@ -67,7 +67,7 @@ public class GachaLogItem
/// 物品稀有等级
/// </summary>
[JsonPropertyName("rank_type")]
[JsonConverter(typeof(EnumStringValueConverter<ItemQuality>))]
[JsonEnum(JsonSerializeType.Int32AsString)]
public ItemQuality Rank { get; set; } = default!;
/// <summary>

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>

View File

@@ -39,10 +39,10 @@ internal class CalculateClient
/// <param name="delta">差异</param>
/// <param name="token">取消令牌</param>
/// <returns>消耗结果</returns>
public async Task<Consumption> ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token)
public async Task<Consumption?> ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token)
{
Response<Consumption>? resp = await httpClient
.SetUser(user,CookieType.CookieToken)
.SetUser(user, CookieType.CookieToken)
.TryCatchPostAsJsonAsync<AvatarPromotionDelta, Response<Consumption>>(ApiEndpoints.CalculateCompute, delta, options, logger, token)
.ConfigureAwait(false);
return resp?.Data;
@@ -53,7 +53,6 @@ internal class CalculateClient
/// </summary>
/// <param name="user">用户</param>
/// <param name="uid">Uid</param>
/// <param name="sync">同步</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public async Task<List<Avatar>> GetAvatarsAsync(User user, PlayerUid uid, CancellationToken token = default)

View File

@@ -1,11 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Response;
using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;

View File

@@ -4,7 +4,6 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Annotation;

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.WinRT;
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
public unsafe interface IMemoryBufferByteAccess
{
/// <summary>
/// Gets an IMemoryBuffer as an array of bytes.