Compare commits

..

2 Commits

Author SHA1 Message Date
Lightczx
7f998dc87f impl #1430 2024-04-11 15:48:11 +08:00
Lightczx
2367c4759d refactor 2024-04-10 16:53:21 +08:00
26 changed files with 365 additions and 192 deletions

View File

@@ -110,7 +110,6 @@ dotnet_diagnostic.SA1642.severity = none
dotnet_diagnostic.IDE0005.severity = warning dotnet_diagnostic.IDE0005.severity = warning
dotnet_diagnostic.IDE0060.severity = none dotnet_diagnostic.IDE0060.severity = none
dotnet_diagnostic.IDE0290.severity = none
# SA1208: System using directives should be placed before other using directives # SA1208: System using directives should be placed before other using directives
dotnet_diagnostic.SA1208.severity = none dotnet_diagnostic.SA1208.severity = none
@@ -321,7 +320,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals” # CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion dotnet_diagnostic.CA2251.severity = suggestion
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_prefer_primary_constructors = false:none
[*.vb] [*.vb]
#### 命名样式 #### #### 命名样式 ####

View File

@@ -3,6 +3,7 @@
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.Diagnostics;
using System.Buffers;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
@@ -77,34 +78,36 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false)) using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
{ {
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent())
Memory<byte> buffer = new byte[bufferSize];
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
{ {
int totalBytesRead = 0; Memory<byte> buffer = memoryOwner.Memory;
int bytesReadAfterPreviousReport = 0; using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
do
{ {
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false); int totalBytesRead = 0;
if (bytesRead <= 0) int bytesReadAfterPreviousReport = 0;
do
{ {
progress.Report(new(bytesReadAfterPreviousReport)); int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
bytesReadAfterPreviousReport = 0; if (bytesRead <= 0)
break; {
} progress.Report(new(bytesReadAfterPreviousReport));
bytesReadAfterPreviousReport = 0;
break;
}
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false); await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
bytesReadAfterPreviousReport += bytesRead; bytesReadAfterPreviousReport += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500) if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
{ {
progress.Report(new(bytesReadAfterPreviousReport)); progress.Report(new(bytesReadAfterPreviousReport));
bytesReadAfterPreviousReport = 0; bytesReadAfterPreviousReport = 0;
stopwatch = ValueStopwatch.StartNew(); stopwatch = ValueStopwatch.StartNew();
}
} }
while (true);
} }
while (true);
} }
} }
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.Diagnostics;
using System.Buffers;
using System.IO; using System.IO;
namespace Snap.Hutao.Core.IO; namespace Snap.Hutao.Core.IO;
@@ -51,26 +52,30 @@ internal class StreamCopyWorker<TStatus>
long totalBytesRead = 0; long totalBytesRead = 0;
int bytesRead; int bytesRead;
Memory<byte> buffer = new byte[bufferSize];
do using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize))
{ {
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false); Memory<byte> buffer = memoryOwner.Memory;
if (bytesRead == 0)
{
progress.Report(statusFactory(totalBytesRead));
break;
}
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false); do
totalBytesRead += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
{ {
progress.Report(statusFactory(totalBytesRead)); bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
stopwatch = ValueStopwatch.StartNew(); if (bytesRead is 0)
{
progress.Report(statusFactory(totalBytesRead));
break;
}
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
{
progress.Report(statusFactory(totalBytesRead));
stopwatch = ValueStopwatch.StartNew();
}
} }
while (bytesRead > 0);
} }
while (bytesRead > 0);
} }
} }

View File

@@ -116,15 +116,15 @@ internal sealed partial class Activation : IActivation
// If it's the first time launch, we show the guide window anyway. // If it's the first time launch, we show the guide window anyway.
// Otherwise, we check if there's any unfulfilled resource category present. // Otherwise, we check if there's any unfulfilled resource category present.
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin) if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
{ {
if (StaticResource.IsAnyUnfulfilledCategoryPresent()) if (StaticResource.IsAnyUnfulfilledCategoryPresent())
{ {
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin); UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
} }
} }
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) < GuideState.Completed) if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
serviceProvider.GetRequiredService<GuideWindow>(); serviceProvider.GetRequiredService<GuideWindow>();

View File

@@ -20,7 +20,9 @@ internal static class SettingKeys
#region Application #region Application
public const string LaunchTimes = "LaunchTimes"; public const string LaunchTimes = "LaunchTimes";
public const string DataFolderPath = "DataFolderPath"; public const string DataFolderPath = "DataFolderPath";
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState"; public const string Major1Minor10Revision0GuideState = "Major1Minor10Revision0GuideState1";
public const string StaticResourceImageQuality = "StaticResourceImageQuality";
public const string StaticResourceUseTrimmedArchive = "StaticResourceUseTrimmedArchive";
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever"; public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2"; public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
#endregion #endregion
@@ -60,6 +62,10 @@ internal static class SettingKeys
#endregion #endregion
#region Obsolete #region Obsolete
[Obsolete("重置新手引导状态")]
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
[Obsolete("重置调试控制台开关")] [Obsolete("重置调试控制台开关")]
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled"; public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
#endregion #endregion

View File

@@ -8,7 +8,7 @@
xmlns:shvg="using:Snap.Hutao.View.Guide" xmlns:shvg="using:Snap.Hutao.View.Guide"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid x:Name="RootGrid"> <Grid x:Name="RootGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
<RowDefinition/> <RowDefinition/>

View File

@@ -1397,6 +1397,12 @@
<data name="ViewGuideStepAgreementTermOfService" xml:space="preserve"> <data name="ViewGuideStepAgreementTermOfService" xml:space="preserve">
<value>用户使用协议与法律声明</value> <value>用户使用协议与法律声明</value>
</data> </data>
<data name="ViewGuideStepCommonSetting" xml:space="preserve">
<value>基础设置</value>
</data>
<data name="ViewGuideStepCommonSettingHint" xml:space="preserve">
<value>稍后可以在设置中修改</value>
</data>
<data name="ViewGuideStepDocument" xml:space="preserve"> <data name="ViewGuideStepDocument" xml:space="preserve">
<value>文档</value> <value>文档</value>
</data> </data>
@@ -1407,7 +1413,7 @@
<value>安装完成后重启胡桃以查看是否正常生效</value> <value>安装完成后重启胡桃以查看是否正常生效</value>
</data> </data>
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve"> <data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
<value>如果上方的图标中存在乱码,请前往</value> <value>如果上方的图标中存在乱码或方块字,请前往</value>
</data> </data>
<data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve"> <data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve">
<value>下载并自行安装图标字体</value> <value>下载并自行安装图标字体</value>
@@ -1424,6 +1430,24 @@
<data name="ViewGuideStepStaticResource" xml:space="preserve"> <data name="ViewGuideStepStaticResource" xml:space="preserve">
<value>资源</value> <value>资源</value>
</data> </data>
<data name="ViewGuideStepStaticResourceSetting" xml:space="preserve">
<value>图像资源设置</value>
</data>
<data name="ViewGuideStepStaticResourceSettingHint" xml:space="preserve">
<value>* 除非你卸载并重新安装胡桃,否则你将无法更改这些设置</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumHeader" xml:space="preserve">
<value>图片资源包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOff" xml:space="preserve">
<value>全部资源(节省 0% 磁盘空间占用)</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOn" xml:space="preserve">
<value>部分资源(节省 13.5% 磁盘空间占用)</value>
</data>
<data name="ViewGuideStepStaticResourceSettingQualityHeader" xml:space="preserve">
<value>图片资源质量</value>
</data>
<data name="ViewHutaoDatabaseHeader" xml:space="preserve"> <data name="ViewHutaoDatabaseHeader" xml:space="preserve">
<value>深渊统计</value> <value>深渊统计</value>
</data> </data>
@@ -1613,6 +1637,12 @@
<data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve"> <data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve">
<value>下载资源文件中,请稍候</value> <value>下载资源文件中,请稍候</value>
</data> </data>
<data name="ViewModelGuideStaticResourceQualityHigh" xml:space="preserve">
<value>高质量(节省 72.8% 磁盘空间占用)</value>
</data>
<data name="ViewModelGuideStaticResourceQualityRaw" xml:space="preserve">
<value>原图(节省 0% 磁盘空间占用)</value>
</data>
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve"> <data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
<value>请输入正确的邮箱</value> <value>请输入正确的邮箱</value>
</data> </data>

View File

@@ -19,7 +19,7 @@ internal sealed class HutaoStatisticsFactory
private readonly GachaEvent avatarEvent; private readonly GachaEvent avatarEvent;
private readonly GachaEvent avatarEvent2; private readonly GachaEvent avatarEvent2;
private readonly GachaEvent weaponEvent; private readonly GachaEvent weaponEvent;
private readonly GachaEvent? chronicledEvent; private readonly GachaEvent chronicledEvent;
public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context) public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context)
{ {
@@ -32,7 +32,7 @@ internal sealed class HutaoStatisticsFactory
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityAvatar); avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityAvatar);
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.SpecialActivityAvatar); avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.SpecialActivityAvatar);
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityWeapon); weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityWeapon);
chronicledEvent = context.GachaEvents.SingleOrDefault(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity); chronicledEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
} }
public HutaoStatistics Create(GachaEventStatistics raw) public HutaoStatistics Create(GachaEventStatistics raw)
@@ -42,7 +42,7 @@ internal sealed class HutaoStatisticsFactory
AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent), AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent),
AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2), AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2),
WeaponEvent = CreateWishSummary(weaponEvent, raw.WeaponEvent), WeaponEvent = CreateWishSummary(weaponEvent, raw.WeaponEvent),
Chronicled = chronicledEvent is null ? null : CreateWishSummary(chronicledEvent, raw.Chronicled), Chronicled = CreateWishSummary(chronicledEvent, raw.Chronicled),
}; };
} }

View File

@@ -151,7 +151,6 @@
<None Remove="View\Control\DescParamComboBox.xaml" /> <None Remove="View\Control\DescParamComboBox.xaml" />
<None Remove="View\Control\Elevation.xaml" /> <None Remove="View\Control\Elevation.xaml" />
<None Remove="View\Control\HutaoStatisticsCard.xaml" /> <None Remove="View\Control\HutaoStatisticsCard.xaml" />
<None Remove="View\Control\HutaoStatisticsPanel.xaml" />
<None Remove="View\Control\ItemIcon.xaml" /> <None Remove="View\Control\ItemIcon.xaml" />
<None Remove="View\Control\LaunchGameResourceExpander.xaml" /> <None Remove="View\Control\LaunchGameResourceExpander.xaml" />
<None Remove="View\Control\LoadingView.xaml" /> <None Remove="View\Control\LoadingView.xaml" />
@@ -348,14 +347,6 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'"> <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" /> <ProjectCapability Include="Msix" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\HutaoStatisticsPanel.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml"> <Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -1,13 +0,0 @@
<shcp:HorizontalEqualPanel
x:Class="Snap.Hutao.View.Control.HutaoStatisticsPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
xmlns:shvc="using:Snap.Hutao.View.Control"
mc:Ignorable="d">
<shvc:HutaoStatisticsCard DataContext="{x:Bind Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard DataContext="{x:Bind Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard DataContext="{x:Bind Statistics.WeaponEvent}"/>
</shcp:HorizontalEqualPanel>

View File

@@ -1,38 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Panel;
using Snap.Hutao.ViewModel.GachaLog;
namespace Snap.Hutao.View.Control;
[DependencyProperty("Statistics", typeof(HutaoStatistics), default, nameof(OnStatisticsChanged))]
internal sealed partial class HutaoStatisticsPanel : HorizontalEqualPanel
{
public HutaoStatisticsPanel()
{
InitializeComponent();
if (DataContext is HutaoStatistics statistics && statistics.Chronicled is { } chronicled)
{
Children.Add(new HutaoStatisticsCard
{
DataContext = chronicled,
});
}
}
private static void OnStatisticsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
HutaoStatisticsPanel panel = (HutaoStatisticsPanel)obj;
if (args.NewValue is HutaoStatistics { Chronicled: { } chronicled })
{
panel.Children.Add(new HutaoStatisticsCard
{
DataContext = chronicled,
});
}
}
}

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup" xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvg="using:Snap.Hutao.ViewModel.Guide" xmlns:shvg="using:Snap.Hutao.ViewModel.Guide"
@@ -49,7 +50,7 @@
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<cwc:SwitchPresenter Value="{Binding State, Mode=OneWay}"> <cwc:SwitchPresenter ContentTransitions="{ThemeResource EntranceThemeTransitions}" Value="{Binding State, Mode=OneWay}">
<cwc:Case Value="{shcm:UInt32 Value=0}"> <cwc:Case Value="{shcm:UInt32 Value=0}">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<GridView <GridView
@@ -125,10 +126,11 @@
<StackPanel <StackPanel
Margin="16" Margin="16"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center"> VerticalAlignment="Center"
Spacing="{ThemeResource SettingsCardSpacing}">
<TextBlock <TextBlock
Margin="1,0,0,5" Margin="1,0,0,5"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Style="{StaticResource TitleTextBlockStyle}"
Text="Segoe Fluent Icons"/> Text="Segoe Fluent Icons"/>
<StackPanel <StackPanel
Margin="0,8" Margin="0,8"
@@ -155,7 +157,10 @@
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentFontDescription2}"/> <Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentFontDescription2}"/>
</TextBlock> </TextBlock>
<TextBlock Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentAfterInstallDescription}"/> <TextBlock Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentAfterInstallDescription}"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/> <TextBlock
Margin="1,32,0,5"
Style="{StaticResource TitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{Binding RuntimeOptions.WebView2Version}"/> <TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{Binding RuntimeOptions.WebView2Version}"/>
<TextBlock> <TextBlock>
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentWebView2Description1}"/> <Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentWebView2Description1}"/>
@@ -169,6 +174,74 @@
</Grid> </Grid>
</cwc:Case> </cwc:Case>
<cwc:Case Value="{shcm:UInt32 Value=3}"> <cwc:Case Value="{shcm:UInt32 Value=3}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="16"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingHomeAnnouncementRegionHeader}"/>
<ListView
MinWidth="320"
Margin="0,8,0,0"
DisplayMemberPath="Name"
ItemsSource="{Binding AppOptions.LazyRegions.Value}"
SelectedItem="{Binding SelectedRegion, Mode=TwoWay}"/>
</StackPanel>
<TextBlock
Grid.Row="1"
Margin="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalTextAlignment="Center"
Opacity="0.7"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewGuideStepCommonSettingHint}"/>
</Grid>
</cwc:Case>
<cwc:Case Value="{shcm:UInt32 Value=4}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="16"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingQualityHeader}"/>
<ListView
MinWidth="320"
Margin="0,8,0,32"
DisplayMemberPath="Name"
ItemsSource="{Binding StaticResourceOptions.ImageQualities}"
SelectedItem="{Binding StaticResourceOptions.ImageQuality, Mode=TwoWay}"/>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumHeader}"/>
<ToggleSwitch
IsOn="{Binding StaticResourceOptions.UseTrimmedArchive, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumOff}"
OnContent="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumOn}"/>
</StackPanel>
<TextBlock
Grid.Row="1"
Margin="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
HorizontalTextAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingHint}"/>
</Grid>
</cwc:Case>
<cwc:Case Value="{shcm:UInt32 Value=5}">
<StackPanel Margin="32,0" HorizontalAlignment="Left"> <StackPanel Margin="32,0" HorizontalAlignment="Left">
<TextBlock <TextBlock
Margin="1,16,0,5" Margin="1,16,0,5"
@@ -190,11 +263,14 @@
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal"> <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<cwc:Segmented <cwc:Segmented
Margin="16" Margin="16"
HorizontalAlignment="Center"
IsHitTestVisible="False" IsHitTestVisible="False"
SelectedIndex="{Binding State, Mode=TwoWay}"> SelectedIndex="{Binding State, Mode=TwoWay}">
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepLanguage}" Icon="{shcm:FontIcon Glyph=&#xF2B7;}"/> <cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepLanguage}" Icon="{shcm:FontIcon Glyph=&#xF2B7;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepDocument}" Icon="{shcm:FontIcon Glyph=&#xF28B;}"/> <cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepDocument}" Icon="{shcm:FontIcon Glyph=&#xF28B;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepEnvironment}" Icon="{shcm:FontIcon Glyph=&#xE81E;}"/> <cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepEnvironment}" Icon="{shcm:FontIcon Glyph=&#xE81E;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepCommonSetting}" Icon="{shcm:FontIcon Glyph=&#xE713;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResourceSetting}" Icon="{shcm:FontIcon Glyph=&#xE8BA;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResource}" Icon="{shcm:FontIcon Glyph=&#xE8B9;}"/> <cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResource}" Icon="{shcm:FontIcon Glyph=&#xE8B9;}"/>
</cwc:Segmented> </cwc:Segmented>
<Button <Button

View File

@@ -468,15 +468,24 @@
Margin="16" Margin="16"
CornerRadius="{ThemeResource ControlCornerRadius}" CornerRadius="{ThemeResource ControlCornerRadius}"
IsLoading="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolNegationConverter}}"/> IsLoading="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
<shvc:HutaoStatisticsPanel <Grid
Margin="16" Margin="16"
Spacing="16" ColumnSpacing="16"
Statistics="{Binding HutaoCloudStatisticsViewModel.Statistics}"
Visibility="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}"> Visibility="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding HutaoCloudStatisticsViewModel.OpenUICommand}"/> <shcb:InvokeCommandOnLoadedBehavior Command="{Binding HutaoCloudStatisticsViewModel.OpenUICommand}"/>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
</shvc:HutaoStatisticsPanel> <Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="3" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.Chronicled}"/>
</Grid>
</Grid> </Grid>
</PivotItem> </PivotItem>
</Pivot> </Pivot>

View File

@@ -26,5 +26,5 @@ internal sealed class HutaoStatistics
/// <summary> /// <summary>
/// 集录祈愿 /// 集录祈愿
/// </summary> /// </summary>
public HutaoWishSummary? Chronicled { get; set; } public HutaoWishSummary Chronicled { get; set; } = default!;
} }

View File

@@ -7,105 +7,107 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching; using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO;
using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Collections.Frozen;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Net.Http; using System.Net.Http;
namespace Snap.Hutao.ViewModel.Guide; namespace Snap.Hutao.ViewModel.Guide;
/// <summary>
/// 下载信息
/// </summary>
internal sealed class DownloadSummary : ObservableObject internal sealed class DownloadSummary : ObservableObject
{ {
private static readonly FrozenSet<string?> AllowedMediaTypes = FrozenSet.ToFrozenSet(
[
"application/octet-stream",
"application/zip",
]);
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IImageCache imageCache; private readonly IImageCache imageCache;
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
private readonly string fileName; private readonly string fileName;
private readonly string fileUrl; private readonly string fileUrl;
private readonly Progress<StreamCopyStatus> progress; private readonly IProgress<StreamCopyStatus> progress;
private string description = SH.ViewModelWelcomeDownloadSummaryDefault; private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
private double progressValue; private double progressValue;
/// <summary>
/// 构造一个新的下载信息
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="fileName">压缩文件名称</param>
public DownloadSummary(IServiceProvider serviceProvider, string fileName) public DownloadSummary(IServiceProvider serviceProvider, string fileName)
{ {
taskContext = serviceProvider.GetRequiredService<ITaskContext>(); taskContext = serviceProvider.GetRequiredService<ITaskContext>();
httpRequestMessageBuilderFactory = serviceProvider.GetRequiredService<IHttpRequestMessageBuilderFactory>();
httpClient = serviceProvider.GetRequiredService<HttpClient>(); httpClient = serviceProvider.GetRequiredService<HttpClient>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(serviceProvider.GetRequiredService<RuntimeOptions>().UserAgent);
imageCache = serviceProvider.GetRequiredService<IImageCache>(); imageCache = serviceProvider.GetRequiredService<IImageCache>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
DisplayName = fileName;
this.fileName = fileName; this.fileName = fileName;
fileUrl = Web.HutaoEndpoints.StaticZip(fileName); fileUrl = Web.HutaoEndpoints.StaticZip(fileName);
progress = new(UpdateProgressStatus); progress = serviceProvider.GetRequiredService<IProgressFactory>().CreateForMainThread<StreamCopyStatus>(UpdateProgressStatus);
} }
/// <summary> public string DisplayName { get => fileName; }
/// 显示名称
/// </summary>
public string DisplayName { get; init; }
/// <summary>
/// 描述
/// </summary>
public string Description { get => description; private set => SetProperty(ref description, value); } public string Description { get => description; private set => SetProperty(ref description, value); }
/// <summary>
/// 进度值最大1
/// </summary>
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); } public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
/// <summary>
/// 异步下载并解压
/// </summary>
/// <returns>任务</returns>
public async ValueTask<bool> DownloadAndExtractAsync() public async ValueTask<bool> DownloadAndExtractAsync()
{ {
ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>(); ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>();
try try
{ {
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); HttpRequestMessage message = httpRequestMessageBuilderFactory
.Create()
.SetRequestUri(fileUrl)
.SetStaticResourceControlHeaders()
.Get()
.HttpRequestMessage;
if (response.Content.Headers.ContentType?.MediaType is not ("application/octet-stream" or "application/zip")) using (message)
{ {
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType); using (HttpResponseMessage response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
await taskContext.SwitchToMainThreadAsync();
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
return false;
}
long contentLength = response.Content.Headers.ContentLength ?? 0;
logger.LogInformation("Begin download, length: {length}", contentLength);
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
{ {
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false); if (!AllowedMediaTypes.Contains(response.Content.Headers.ContentType?.MediaType))
ExtractFiles(temp); {
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType);
await taskContext.SwitchToMainThreadAsync();
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
return false;
}
await taskContext.SwitchToMainThreadAsync(); long contentLength = response.Content.Headers.ContentLength ?? 0;
ProgressValue = 1; logger.LogInformation("Begin download, size: {length}", Converters.ToFileSizeString(contentLength));
Description = SH.ViewModelWelcomeDownloadSummaryComplete; using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
return true; {
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
ExtractFiles(temp);
await taskContext.SwitchToMainThreadAsync();
ProgressValue = 1;
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
return true;
}
}
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Download Static Zip failed"); logger.LogError(ex, "Download static zip failed");
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
Description = ex is HttpRequestException httpRequestException Description = ex is HttpRequestException httpRequestEx
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestException.StatusCode:D}'] [Error '{httpRequestException.HttpRequestError}']" ? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestEx.StatusCode:D}'] [Error '{httpRequestEx.HttpRequestError}']"
: ex.Message; : ex.Message;
return false; return false;
} }
@@ -114,7 +116,7 @@ internal sealed class DownloadSummary : ObservableObject
private void UpdateProgressStatus(StreamCopyStatus status) private void UpdateProgressStatus(StreamCopyStatus status)
{ {
Description = $"{Converters.ToFileSizeString(status.BytesCopied)}/{Converters.ToFileSizeString(status.TotalBytes)}"; Description = $"{Converters.ToFileSizeString(status.BytesCopied)}/{Converters.ToFileSizeString(status.TotalBytes)}";
ProgressValue = status.TotalBytes == 0 ? 0 : (double)status.BytesCopied / status.TotalBytes; ProgressValue = status.TotalBytes is 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
} }
private void ExtractFiles(Stream stream) private void ExtractFiles(Stream stream)

View File

@@ -24,7 +24,17 @@ internal enum GuideState : uint
Environment, Environment,
/// <summary> /// <summary>
/// 开始下载资源 /// 正在查看常用设置
/// </summary>
CommonSetting,
/// <summary>
/// 正在查看图像资源设置
/// </summary>
StaticResourceSetting,
/// <summary>
/// 开始下载图像资源
/// </summary> /// </summary>
StaticResourceBegin, StaticResourceBegin,

View File

@@ -6,6 +6,7 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model; using Snap.Hutao.Model;
using Snap.Hutao.Service; using Snap.Hutao.Service;
using Snap.Hutao.Web.Hoyolab;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Globalization; using System.Globalization;
@@ -20,12 +21,15 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly StaticResourceOptions staticResourceOptions;
private readonly CultureOptions cultureOptions; private readonly CultureOptions cultureOptions;
private readonly RuntimeOptions runtimeOptions; private readonly RuntimeOptions runtimeOptions;
private readonly AppOptions appOptions;
private string nextOrCompleteButtonText = SH.ViewModelGuideActionNext; private string nextOrCompleteButtonText = SH.ViewModelGuideActionNext;
private bool isNextOrCompleteButtonEnabled = true; private bool isNextOrCompleteButtonEnabled = true;
private NameValue<CultureInfo>? selectedCulture; private NameValue<CultureInfo>? selectedCulture;
private NameValue<Region>? selectedRegion;
private bool isTermOfServiceAgreed; private bool isTermOfServiceAgreed;
private bool isPrivacyPolicyAgreed; private bool isPrivacyPolicyAgreed;
private bool isIssueReportAgreed; private bool isIssueReportAgreed;
@@ -36,8 +40,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
get get
{ {
uint value = LocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, 0U); GuideState state = UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
GuideState state = (GuideState)value;
if (state is GuideState.Document) if (state is GuideState.Document)
{ {
@@ -61,12 +64,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
(NextOrCompleteButtonText, IsNextOrCompleteButtonEnabled) = (SH.ViewModelGuideActionNext, true); (NextOrCompleteButtonText, IsNextOrCompleteButtonEnabled) = (SH.ViewModelGuideActionNext, true);
} }
return value; return (uint)state;
} }
set set
{ {
LocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, value); LocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, value);
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@@ -79,6 +82,10 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
public RuntimeOptions RuntimeOptions { get => runtimeOptions; } public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
public AppOptions AppOptions { get => appOptions; }
public StaticResourceOptions StaticResourceOptions { get => staticResourceOptions; }
public NameValue<CultureInfo>? SelectedCulture public NameValue<CultureInfo>? SelectedCulture
{ {
get => selectedCulture ??= CultureOptions.GetCurrentCultureForSelectionOrDefault(); get => selectedCulture ??= CultureOptions.GetCurrentCultureForSelectionOrDefault();
@@ -93,13 +100,26 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
} }
} }
public NameValue<Region>? SelectedRegion
{
get => selectedRegion ??= AppOptions.GetCurrentRegionForSelectionOrDefault();
set
{
if (SetProperty(ref selectedRegion, value) && value is not null)
{
AppOptions.Region = value.Value;
}
}
}
#region Agreement
public bool IsTermOfServiceAgreed public bool IsTermOfServiceAgreed
{ {
get => isTermOfServiceAgreed; set get => isTermOfServiceAgreed; set
{ {
if (SetProperty(ref isTermOfServiceAgreed, value)) if (SetProperty(ref isTermOfServiceAgreed, value))
{ {
OnAgreeSateChanged(); OnAgreementStateChanged();
} }
} }
} }
@@ -110,7 +130,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
if (SetProperty(ref isPrivacyPolicyAgreed, value)) if (SetProperty(ref isPrivacyPolicyAgreed, value))
{ {
OnAgreeSateChanged(); OnAgreementStateChanged();
} }
} }
} }
@@ -121,7 +141,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
if (SetProperty(ref isIssueReportAgreed, value)) if (SetProperty(ref isIssueReportAgreed, value))
{ {
OnAgreeSateChanged(); OnAgreementStateChanged();
} }
} }
} }
@@ -132,14 +152,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
if (SetProperty(ref isOpenSourceLicenseAgreed, value)) if (SetProperty(ref isOpenSourceLicenseAgreed, value))
{ {
OnAgreeSateChanged(); OnAgreementStateChanged();
} }
} }
} }
#endregion
/// <summary>
/// 下载信息
/// </summary>
public ObservableCollection<DownloadSummary>? DownloadSummaries public ObservableCollection<DownloadSummary>? DownloadSummaries
{ {
get => downloadSummaries; get => downloadSummaries;
@@ -152,7 +170,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
++State; ++State;
} }
private void OnAgreeSateChanged() private void OnAgreementStateChanged()
{ {
IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed; IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed;
} }
@@ -173,7 +191,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
}).ConfigureAwait(false); }).ConfigureAwait(false);
StaticResource.FulfillAll(); StaticResource.FulfillAll();
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Completed); UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Completed);
AppInstance.Restart(string.Empty); AppInstance.Restart(string.Empty);
} }
} }

View File

@@ -16,7 +16,7 @@ internal static class StaticResource
private static readonly ApplicationDataCompositeValue DefaultResourceVersionMap = new() private static readonly ApplicationDataCompositeValue DefaultResourceVersionMap = new()
{ {
// DO NOT MIDIFY THIS MAP // DO NOT MODIFY THIS MAP
{ "AchievementIcon", 0 }, { "AchievementIcon", 0 },
{ "AvatarCard", 0 }, { "AvatarCard", 0 },
{ "AvatarIcon", 0 }, { "AvatarIcon", 0 },

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Net.Http.Headers;
namespace Snap.Hutao.ViewModel.Guide;
internal static class StaticResourceHttpHeaderBuilderExtension
{
public static TBuilder SetStaticResourceControlHeaders<TBuilder>(this TBuilder builder)
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
{
return builder
.SetHeader("x-quality", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw)}")
.SetHeader("x-minimum", $"{LocalSetting.Get(SettingKeys.StaticResourceUseTrimmedArchive, false)}");
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
namespace Snap.Hutao.ViewModel.Guide;
[Injection(InjectAs.Singleton)]
internal sealed class StaticResourceOptions
{
private readonly List<NameValue<StaticResourceQuality>> imageQualities = CollectionsNameValue.FromEnum<StaticResourceQuality>(q => q.GetLocalizedDescription());
private NameValue<StaticResourceQuality>? imageQuality;
public StaticResourceOptions()
{
ImageQuality = ImageQualities.First(q => q.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw));
}
public List<NameValue<StaticResourceQuality>> ImageQualities { get => imageQualities; }
public NameValue<StaticResourceQuality>? ImageQuality
{
get => imageQuality;
set
{
if (value is not null)
{
imageQuality = value;
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageQuality, value.Value);
}
}
}
public bool UseTrimmedArchive
{
get => LocalSetting.Get(SettingKeys.StaticResourceUseTrimmedArchive, false);
set => LocalSetting.Set(SettingKeys.StaticResourceUseTrimmedArchive, value);
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.Guide;
[Localization]
internal enum StaticResourceQuality
{
[LocalizationKey(nameof(SH.ViewModelGuideStaticResourceQualityRaw))]
Raw,
[LocalizationKey(nameof(SH.ViewModelGuideStaticResourceQualityHigh))]
High,
}

View File

@@ -224,7 +224,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private static void ResetStaticResource() private static void ResetStaticResource()
{ {
StaticResource.FailAll(); StaticResource.FailAll();
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin); UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
AppInstance.Restart(string.Empty); AppInstance.Restart(string.Empty);
} }

View File

@@ -84,7 +84,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
[Command("ResetGuideStateCommand")] [Command("ResetGuideStateCommand")]
private static void ResetGuideState() private static void ResetGuideState()
{ {
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language); UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
} }
[Command("ExceptionCommand")] [Command("ExceptionCommand")]

View File

@@ -35,8 +35,7 @@ internal static class HttpContentBuilderExtension
} }
[DebuggerStepThrough] [DebuggerStepThrough]
public static T SetFormUrlEncodedContent<T>( public static T SetFormUrlEncodedContent<T>(this T builder, IEnumerable<KeyValuePair<string, string>> content)
this T builder, IEnumerable<KeyValuePair<string, string>> content)
where T : IHttpContentBuilder where T : IHttpContentBuilder
{ {
ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(builder);

View File

@@ -41,12 +41,6 @@ internal static partial class HttpHeadersBuilderExtension
return builder.ConfigureHeaders<TBuilder, THeaders>(headers => headers.Add(name, value)); return builder.ConfigureHeaders<TBuilder, THeaders>(headers => headers.Add(name, value));
} }
public static T SetReferer<T>(this T builder, string referer)
where T : IHttpHeadersBuilder<HttpHeaders>
{
return builder.SetHeader("Referer", referer);
}
[DebuggerStepThrough] [DebuggerStepThrough]
public static T SetHeader<T>(this T builder, string name) public static T SetHeader<T>(this T builder, string name)
where T : IHttpHeadersBuilder<HttpHeaders> where T : IHttpHeadersBuilder<HttpHeaders>
@@ -80,6 +74,12 @@ internal static partial class HttpHeadersBuilderExtension
.AddHeader<TBuilder, THeaders>(name, value); .AddHeader<TBuilder, THeaders>(name, value);
} }
public static T SetReferer<T>(this T builder, string referer)
where T : IHttpHeadersBuilder<HttpHeaders>
{
return builder.SetHeader("Referer", referer);
}
[DebuggerStepThrough] [DebuggerStepThrough]
public static T RemoveHeader<T>(this T builder, params string?[]? names) public static T RemoveHeader<T>(this T builder, params string?[]? names)
where T : IHttpHeadersBuilder<HttpHeaders> where T : IHttpHeadersBuilder<HttpHeaders>

View File

@@ -12,7 +12,7 @@ internal static class HttpHeadersExtension
ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(name);
// We have to work around the .NET API a little bit. See the comment below for details. // We have to work around the .NET API a little bit. See the comment below for details.
values ??= Enumerable.Empty<string?>(); values ??= [];
values = values.Where(v => v is not null); values = values.Where(v => v is not null);
if (values.Any()) if (values.Any())