mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
13 Commits
feat/IFile
...
feat/versi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f998dc87f | ||
|
|
2367c4759d | ||
|
|
043e3f07d8 | ||
|
|
cd075c4dab | ||
|
|
65252f1f69 | ||
|
|
f5dd5f4c1d | ||
|
|
27ce55f3f7 | ||
|
|
311941bb89 | ||
|
|
07e2489cab | ||
|
|
699ac60aaf | ||
|
|
dc7bc7e35d | ||
|
|
bf67fcf3a2 | ||
|
|
5093246571 |
@@ -110,7 +110,6 @@ dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
dotnet_diagnostic.IDE0290.severity = none
|
||||
|
||||
# SA1208: System using directives should be placed before other using directives
|
||||
dotnet_diagnostic.SA1208.severity = none
|
||||
@@ -321,7 +320,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
|
||||
# CA2251: 使用 “string.Equals”
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
|
||||
csharp_style_prefer_primary_constructors = false:none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace Snap.Hutao.Test.PlatformExtensions;
|
||||
@@ -11,6 +12,7 @@ public sealed class DependencyInjectionTest
|
||||
.AddSingleton<IService, ServiceB>()
|
||||
.AddScoped<IScopedService, ServiceA>()
|
||||
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
|
||||
.AddLogging(builder => builder.AddConsole())
|
||||
.BuildServiceProvider();
|
||||
|
||||
[TestMethod]
|
||||
@@ -41,6 +43,13 @@ public sealed class DependencyInjectionTest
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoggerWithInterfaceTypeCanBeResolved()
|
||||
{
|
||||
Assert.IsNotNull(services.GetService<ILogger<IScopedService>>());
|
||||
Assert.IsNotNull(services.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(IScopedService)));
|
||||
}
|
||||
|
||||
private interface IService
|
||||
{
|
||||
Guid Id { get; }
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,22 @@ internal static class ServiceProviderExtension
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDisposedSlow(this IServiceProvider? serviceProvider)
|
||||
public static bool IsDisposed(this IServiceProvider? serviceProvider)
|
||||
{
|
||||
if (serviceProvider is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (serviceProvider is ServiceProvider serviceProviderImpl)
|
||||
{
|
||||
return GetPrivateDisposed(serviceProviderImpl);
|
||||
}
|
||||
|
||||
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true;
|
||||
}
|
||||
|
||||
// private bool _disposed;
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")]
|
||||
private static extern ref bool GetPrivateDisposed(ServiceProvider serviceProvider);
|
||||
}
|
||||
@@ -17,4 +17,5 @@ internal enum HutaoExceptionKind
|
||||
// Service
|
||||
GachaStatisticsInvalidItemId,
|
||||
GameFpsUnlockingFailed,
|
||||
GameConfigInvalidChannelOptions,
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
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))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent())
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
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;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
@@ -51,26 +52,30 @@ internal class StreamCopyWorker<TStatus>
|
||||
|
||||
long totalBytesRead = 0;
|
||||
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);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
do
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -116,15 +116,15 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
// If it's the first time launch, we show the guide window anyway.
|
||||
// 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())
|
||||
{
|
||||
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();
|
||||
serviceProvider.GetRequiredService<GuideWindow>();
|
||||
|
||||
@@ -20,7 +20,9 @@ internal static class SettingKeys
|
||||
#region Application
|
||||
public const string LaunchTimes = "LaunchTimes";
|
||||
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 IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
|
||||
#endregion
|
||||
@@ -60,6 +62,10 @@ internal static class SettingKeys
|
||||
#endregion
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("重置新手引导状态")]
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
|
||||
[Obsolete("重置调试控制台开关")]
|
||||
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
|
||||
#endregion
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:shvg="using:Snap.Hutao.View.Guide"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid x:Name="RootGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
|
||||
@@ -27,4 +27,27 @@ internal sealed partial class IdentifyMonitorWindow : Window
|
||||
}
|
||||
|
||||
public string Monitor { get; private set; }
|
||||
|
||||
public static async ValueTask IdentifyAllMonitorsAsync(int secondsDelay)
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(secondsDelay).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1334,6 +1334,12 @@
|
||||
<data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve">
|
||||
<value>为账号命名</value>
|
||||
</data>
|
||||
<data name="ViewDialogLaunchGameConfigurationFixDialogHint" xml:space="preserve">
|
||||
<value>请选择当前游戏路径对应的游戏服务器</value>
|
||||
</data>
|
||||
<data name="ViewDialogLaunchGameConfigurationFixDialogTitle" xml:space="preserve">
|
||||
<value>正在修复配置文件</value>
|
||||
</data>
|
||||
<data name="ViewDialogLaunchGamePackageConvertHint" xml:space="preserve">
|
||||
<value>转换可能需要花费一段时间,请勿关闭胡桃</value>
|
||||
</data>
|
||||
@@ -1391,6 +1397,12 @@
|
||||
<data name="ViewGuideStepAgreementTermOfService" xml:space="preserve">
|
||||
<value>用户使用协议与法律声明</value>
|
||||
</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">
|
||||
<value>文档</value>
|
||||
</data>
|
||||
@@ -1401,7 +1413,7 @@
|
||||
<value>安装完成后重启胡桃以查看是否正常生效</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
|
||||
<value>如果上方的图标中存在乱码,请前往</value>
|
||||
<value>如果上方的图标中存在乱码或方块字,请前往</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve">
|
||||
<value>下载并自行安装图标字体</value>
|
||||
@@ -1418,6 +1430,24 @@
|
||||
<data name="ViewGuideStepStaticResource" xml:space="preserve">
|
||||
<value>资源</value>
|
||||
</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">
|
||||
<value>深渊统计</value>
|
||||
</data>
|
||||
@@ -1607,6 +1637,12 @@
|
||||
<data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve">
|
||||
<value>下载资源文件中,请稍候</value>
|
||||
</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">
|
||||
<value>请输入正确的邮箱</value>
|
||||
</data>
|
||||
@@ -1625,6 +1661,12 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>切换服务器失败</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameFixConfigurationFileButtonText" xml:space="preserve">
|
||||
<value>修复配置文件</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameFixConfigurationFileSuccess" xml:space="preserve">
|
||||
<value>修复完成</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>识别显示器</value>
|
||||
</data>
|
||||
@@ -1637,6 +1679,9 @@
|
||||
<data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve">
|
||||
<value>尚未选择任何服务器</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameSetGamePathButtonText" xml:space="preserve">
|
||||
<value>设置游戏目录</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>切换账号失败</value>
|
||||
</data>
|
||||
|
||||
@@ -36,4 +36,6 @@ internal sealed class GameFileSystem
|
||||
public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); }
|
||||
|
||||
public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); }
|
||||
|
||||
public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); }
|
||||
}
|
||||
@@ -47,6 +47,14 @@ internal static class InfoBarOptionsBuilderExtension
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetActionButton<TBuilder>(this TBuilder builder, Action<ButtonBuilder> configureButton)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
ButtonBuilder buttonBaseBuilder = new ButtonBuilder().Configure(configureButton);
|
||||
builder.Configure(builder => builder.Options.ActionButton = buttonBaseBuilder.Button);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetDelay<TBuilder>(this TBuilder builder, int milliSeconds)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Builder.ButtonBase;
|
||||
using Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
namespace Snap.Hutao.Service.Notification;
|
||||
@@ -18,6 +19,11 @@ internal static class InfoBarServiceExtension
|
||||
infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 5000)
|
||||
{
|
||||
infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Information(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Informational).Configure(configure));
|
||||
@@ -48,6 +54,11 @@ internal static class InfoBarServiceExtension
|
||||
infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 30000)
|
||||
{
|
||||
infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Warning(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Warning).Configure(configure));
|
||||
@@ -63,6 +74,11 @@ internal static class InfoBarServiceExtension
|
||||
infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage(ex.Message).SetDelay(milliSeconds));
|
||||
@@ -73,8 +89,13 @@ internal static class InfoBarServiceExtension
|
||||
infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, string buttonContent, ICommand buttonCommand, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Error).Configure(configure));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
<None Remove="View\Dialog\HutaoPassportResetPasswordDialog.xaml" />
|
||||
<None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGameConfigurationFixDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
|
||||
<None Remove="View\Dialog\ReconfirmDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||
@@ -347,7 +348,9 @@
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Control\Theme\SegmentedOverride.xaml">
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.LaunchGameConfigurationFixDialog"
|
||||
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:shccs="using:Snap.Hutao.Control.Collection.Selector"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shvd="using:Snap.Hutao.View.Dialog"
|
||||
Title="{shcm:ResourceString Name=ViewDialogLaunchGameConfigurationFixDialogTitle}"
|
||||
d:DataContext="{d:DesignInstance shvd:LaunchGameConfigurationFixDialog}"
|
||||
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
|
||||
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<shccs:ComboBox2
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DisplayMemberPath="DisplayName"
|
||||
EnableMemberPath="IsNotCompatOnly"
|
||||
Header="{shcm:ResourceString Name=ViewDialogLaunchGameConfigurationFixDialogHint}"
|
||||
ItemsSource="{x:Bind KnownSchemes}"
|
||||
SelectedItem="{x:Bind SelectedScheme, Mode=TwoWay}"
|
||||
Style="{StaticResource DefaultComboBoxStyle}"/>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
[DependencyProperty("KnownSchemes", typeof(IEnumerable<LaunchScheme>))]
|
||||
[DependencyProperty("SelectedScheme", typeof(LaunchScheme))]
|
||||
internal sealed partial class LaunchGameConfigurationFixDialog : ContentDialog
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public LaunchGameConfigurationFixDialog(IServiceProvider serviceProvider)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
}
|
||||
|
||||
public async ValueTask<ValueResult<bool, LaunchScheme>> GetLaunchSchemeAsync()
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
return new(result is ContentDialogResult.Primary, SelectedScheme);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shvg="using:Snap.Hutao.ViewModel.Guide"
|
||||
@@ -49,7 +50,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</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}">
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<GridView
|
||||
@@ -125,10 +126,11 @@
|
||||
<StackPanel
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
VerticalAlignment="Center"
|
||||
Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock
|
||||
Margin="1,0,0,5"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="Segoe Fluent Icons"/>
|
||||
<StackPanel
|
||||
Margin="0,8"
|
||||
@@ -155,7 +157,10 @@
|
||||
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentFontDescription2}"/>
|
||||
</TextBlock>
|
||||
<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>
|
||||
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentWebView2Description1}"/>
|
||||
@@ -169,6 +174,74 @@
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<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">
|
||||
<TextBlock
|
||||
Margin="1,16,0,5"
|
||||
@@ -190,11 +263,14 @@
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<cwc:Segmented
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
SelectedIndex="{Binding State, Mode=TwoWay}">
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepLanguage}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepDocument}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepEnvironment}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepCommonSetting}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResourceSetting}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResource}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
</cwc:Segmented>
|
||||
<Button
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal interface IViewModelSupportLaunchExecution
|
||||
{
|
||||
void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry);
|
||||
LaunchGameShared Shared { get; }
|
||||
|
||||
GameAccount? SelectedGameAccount { get; }
|
||||
|
||||
void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Game.Launching;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal static class LaunchGameLaunchExecution
|
||||
{
|
||||
public static async ValueTask LaunchExecutionAsync(this IViewModelSupportLaunchExecution launchExecution)
|
||||
{
|
||||
IServiceProvider root = Ioc.Default;
|
||||
IInfoBarService infoBarService = root.GetRequiredService<IInfoBarService>();
|
||||
ILogger<IViewModelSupportLaunchExecution> logger = root.GetRequiredService<ILogger<IViewModelSupportLaunchExecution>>();
|
||||
|
||||
LaunchScheme? scheme = launchExecution.Shared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
try
|
||||
{
|
||||
// Root service provider is required.
|
||||
LaunchExecutionContext context = new(root, launchExecution, scheme, launchExecution.SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,39 +2,104 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Configuration;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.View.Page;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal static class LaunchGameShared
|
||||
[Injection(InjectAs.Transient)]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class LaunchGameShared
|
||||
{
|
||||
public static LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService)
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly IGameServiceFacade gameService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public LaunchScheme? GetCurrentLaunchSchemeFromConfigFile()
|
||||
{
|
||||
ChannelOptions options = gameService.GetChannelOptions();
|
||||
|
||||
if (options.ErrorKind is ChannelOptionsErrorKind.None)
|
||||
switch (options.ErrorKind)
|
||||
{
|
||||
try
|
||||
{
|
||||
return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||
case ChannelOptionsErrorKind.None:
|
||||
try
|
||||
{
|
||||
// 后台收集
|
||||
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}");
|
||||
return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath));
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||
{
|
||||
// 后台收集
|
||||
HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case ChannelOptionsErrorKind.ConfigurationFileNotFound:
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath), SH.ViewModelLaunchGameFixConfigurationFileButtonText, HandleConfigurationFileNotFoundCommand);
|
||||
break;
|
||||
case ChannelOptionsErrorKind.GamePathNullOrEmpty:
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath), SH.ViewModelLaunchGameSetGamePathButtonText, HandleGamePathNullOrEmptyCommand);
|
||||
break;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
[Command("HandleConfigurationFileNotFoundCommand")]
|
||||
private async Task HandleConfigurationFileNotFoundAsync()
|
||||
{
|
||||
if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName);
|
||||
string dataFolder = isOversea ? GameConstants.GenshinImpactData : GameConstants.YuanShenData;
|
||||
string persistentScriptVersionFile = Path.Combine(gameFileSystem.GameDirectory, dataFolder, "Persistent", "ScriptVersion");
|
||||
string version = await File.ReadAllTextAsync(persistentScriptVersionFile).ConfigureAwait(false);
|
||||
|
||||
LaunchGameConfigurationFixDialog dialog = await contentDialogFactory
|
||||
.CreateInstanceAsync<LaunchGameConfigurationFixDialog>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea);
|
||||
dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly);
|
||||
(bool isOk, LaunchScheme launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
string gameBiz = launchScheme.IsOversea ? "hk4e_global" : "hk4e_cn";
|
||||
string content = $"""
|
||||
[General]
|
||||
channel={launchScheme.Channel:D}
|
||||
cps=mihoyo
|
||||
game_version={version}
|
||||
sub_channel={launchScheme.SubChannel:D}
|
||||
sdk_version=
|
||||
game_biz={gameBiz}
|
||||
""";
|
||||
|
||||
await File.WriteAllTextAsync(gameFileSystem.GameConfigFilePath, content).ConfigureAwait(false);
|
||||
infoBarService.Success(SH.ViewModelLaunchGameFixConfigurationFileSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("HandleGamePathNullOrEmptyCommand")]
|
||||
private void HandleGamePathNullOrEmpty()
|
||||
{
|
||||
navigationService.Navigate<LaunchGamePage>(INavigationAwaiter.Default);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
private readonly LaunchStatusOptions launchStatusOptions;
|
||||
private readonly IGameLocatorFactory gameLocatorFactory;
|
||||
private readonly ILogger<LaunchGameViewModel> logger;
|
||||
private readonly LaunchGameShared launchGameShared;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ResourceClient resourceClient;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
@@ -59,6 +60,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
private GamePathEntry? selectedGamePathEntry;
|
||||
private GameAccountFilter? gameAccountFilter;
|
||||
|
||||
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
|
||||
|
||||
public LaunchOptions LaunchOptions { get => launchOptions; }
|
||||
|
||||
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
|
||||
@@ -82,7 +85,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
|
||||
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
||||
|
||||
[AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))]
|
||||
public bool GamePathSelectedAndValid
|
||||
{
|
||||
get => gamePathSelectedAndValid;
|
||||
@@ -99,7 +101,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
||||
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
await SetSelectedSchemeAsync(scheme).ConfigureAwait(true);
|
||||
@@ -148,7 +150,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
|
||||
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
|
||||
|
||||
[AlsoSets(nameof(GamePathSelectedAndValid))]
|
||||
public GamePathEntry? SelectedGamePathEntry
|
||||
{
|
||||
get => selectedGamePathEntry;
|
||||
@@ -180,6 +181,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
private static async Task IdentifyMonitorsAsync()
|
||||
{
|
||||
await IdentifyMonitorWindow.IdentifyAllMonitorsAsync(3);
|
||||
}
|
||||
|
||||
[Command("SetGamePathCommand")]
|
||||
private async Task SetGamePathAsync()
|
||||
{
|
||||
@@ -209,21 +216,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
LaunchExecutionContext context = new(Ioc.Default, this, SelectedScheme, SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
await this.LaunchExecutionAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("DetectGameAccountCommand")]
|
||||
@@ -244,7 +237,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
SelectedGameAccount = account;
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
@@ -253,47 +246,54 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("AttachGameAccountCommand")]
|
||||
private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
if (userService.Current?.SelectedUserGameRole is { } role)
|
||||
{
|
||||
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (userService.Current?.SelectedUserGameRole is { } role)
|
||||
{
|
||||
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ModifyGameAccountCommand")]
|
||||
private async Task ModifyGameAccountAsync(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("RemoveGameAccountCommand")]
|
||||
private async Task RemoveGameAccountAsync(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("OpenScreenshotFolderCommand")]
|
||||
private async Task OpenScreenshotFolderAsync()
|
||||
{
|
||||
string game = LaunchOptions.GamePath;
|
||||
string? directory = Path.GetDirectoryName(game);
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
string screenshot = Path.Combine(directory, "ScreenShot");
|
||||
if (Directory.Exists(screenshot))
|
||||
if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
{
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(screenshot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(gameFileSystem.ScreenShotDirectory))
|
||||
{
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,12 +301,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
if (SetProperty(ref selectedScheme, value, nameof(SelectedScheme)))
|
||||
{
|
||||
UpdateGameResourceAsync(value).SafeForget();
|
||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
||||
|
||||
// Clear the selected game account to prevent setting
|
||||
// incorrect CN/OS account when scheme not match
|
||||
SelectedGameAccount = default;
|
||||
|
||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
||||
UpdateGameResourceAsync(value).SafeForget();
|
||||
}
|
||||
|
||||
async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme)
|
||||
@@ -340,28 +340,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
private async Task IdentifyMonitorsAsync()
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(3).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Snap.Hutao.ViewModel.Game;
|
||||
internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>, IViewModelSupportLaunchExecution
|
||||
{
|
||||
private readonly LaunchStatusOptions launchStatusOptions;
|
||||
private readonly ILogger<LaunchGameViewModelSlim> logger;
|
||||
private readonly LaunchGameShared launchGameShared;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IGameServiceFacade gameService;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -31,6 +31,8 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
private GameAccount? selectedGameAccount;
|
||||
private GameAccountFilter? gameAccountFilter;
|
||||
|
||||
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
|
||||
|
||||
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
|
||||
|
||||
public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
|
||||
@@ -40,14 +42,10 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
/// </summary>
|
||||
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
|
||||
|
||||
public void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
||||
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
||||
|
||||
try
|
||||
@@ -58,7 +56,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme);
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
@@ -75,23 +73,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
IInfoBarService infoBarService = ServiceProvider.GetRequiredService<IInfoBarService>();
|
||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
||||
|
||||
try
|
||||
{
|
||||
LaunchExecutionContext context = new(Ioc.Default, this, scheme, SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
await this.LaunchExecutionAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -7,105 +7,107 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
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.Compression;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
internal sealed class DownloadSummary : ObservableObject
|
||||
{
|
||||
private static readonly FrozenSet<string?> AllowedMediaTypes = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
"application/octet-stream",
|
||||
"application/zip",
|
||||
]);
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IImageCache imageCache;
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private readonly string fileName;
|
||||
private readonly string fileUrl;
|
||||
private readonly Progress<StreamCopyStatus> progress;
|
||||
private readonly IProgress<StreamCopyStatus> progress;
|
||||
|
||||
private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
|
||||
private double progressValue;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的下载信息
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="fileName">压缩文件名称</param>
|
||||
public DownloadSummary(IServiceProvider serviceProvider, string fileName)
|
||||
{
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
httpRequestMessageBuilderFactory = serviceProvider.GetRequiredService<IHttpRequestMessageBuilderFactory>();
|
||||
httpClient = serviceProvider.GetRequiredService<HttpClient>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(serviceProvider.GetRequiredService<RuntimeOptions>().UserAgent);
|
||||
imageCache = serviceProvider.GetRequiredService<IImageCache>();
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
DisplayName = fileName;
|
||||
this.fileName = fileName;
|
||||
fileUrl = Web.HutaoEndpoints.StaticZip(fileName);
|
||||
|
||||
progress = new(UpdateProgressStatus);
|
||||
progress = serviceProvider.GetRequiredService<IProgressFactory>().CreateForMainThread<StreamCopyStatus>(UpdateProgressStatus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示名称
|
||||
/// </summary>
|
||||
public string DisplayName { get; init; }
|
||||
public string DisplayName { get => fileName; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get => description; private set => SetProperty(ref description, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 进度值,最大1
|
||||
/// </summary>
|
||||
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 异步下载并解压
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public async ValueTask<bool> DownloadAndExtractAsync()
|
||||
{
|
||||
ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>();
|
||||
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);
|
||||
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))
|
||||
using (HttpResponseMessage response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
|
||||
ExtractFiles(temp);
|
||||
if (!AllowedMediaTypes.Contains(response.Content.Headers.ContentType?.MediaType))
|
||||
{
|
||||
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();
|
||||
ProgressValue = 1;
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
|
||||
return true;
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
logger.LogInformation("Begin download, size: {length}", Converters.ToFileSizeString(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);
|
||||
ExtractFiles(temp);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ProgressValue = 1;
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Download Static Zip failed");
|
||||
logger.LogError(ex, "Download static zip failed");
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Description = ex is HttpRequestException httpRequestException
|
||||
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestException.StatusCode:D}'] [Error '{httpRequestException.HttpRequestError}']"
|
||||
Description = ex is HttpRequestException httpRequestEx
|
||||
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestEx.StatusCode:D}'] [Error '{httpRequestEx.HttpRequestError}']"
|
||||
: ex.Message;
|
||||
return false;
|
||||
}
|
||||
@@ -114,7 +116,7 @@ internal sealed class DownloadSummary : ObservableObject
|
||||
private void UpdateProgressStatus(StreamCopyStatus status)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -24,7 +24,17 @@ internal enum GuideState : uint
|
||||
Environment,
|
||||
|
||||
/// <summary>
|
||||
/// 开始下载资源
|
||||
/// 正在查看常用设置
|
||||
/// </summary>
|
||||
CommonSetting,
|
||||
|
||||
/// <summary>
|
||||
/// 正在查看图像资源设置
|
||||
/// </summary>
|
||||
StaticResourceSetting,
|
||||
|
||||
/// <summary>
|
||||
/// 开始下载图像资源
|
||||
/// </summary>
|
||||
StaticResourceBegin,
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
|
||||
@@ -20,12 +21,15 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly StaticResourceOptions staticResourceOptions;
|
||||
private readonly CultureOptions cultureOptions;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
private string nextOrCompleteButtonText = SH.ViewModelGuideActionNext;
|
||||
private bool isNextOrCompleteButtonEnabled = true;
|
||||
private NameValue<CultureInfo>? selectedCulture;
|
||||
private NameValue<Region>? selectedRegion;
|
||||
private bool isTermOfServiceAgreed;
|
||||
private bool isPrivacyPolicyAgreed;
|
||||
private bool isIssueReportAgreed;
|
||||
@@ -36,8 +40,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
get
|
||||
{
|
||||
uint value = LocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, 0U);
|
||||
GuideState state = (GuideState)value;
|
||||
GuideState state = UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
|
||||
|
||||
if (state is GuideState.Document)
|
||||
{
|
||||
@@ -61,12 +64,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
(NextOrCompleteButtonText, IsNextOrCompleteButtonEnabled) = (SH.ViewModelGuideActionNext, true);
|
||||
}
|
||||
|
||||
return value;
|
||||
return (uint)state;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, value);
|
||||
LocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -79,6 +82,10 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
|
||||
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
|
||||
|
||||
public AppOptions AppOptions { get => appOptions; }
|
||||
|
||||
public StaticResourceOptions StaticResourceOptions { get => staticResourceOptions; }
|
||||
|
||||
public NameValue<CultureInfo>? SelectedCulture
|
||||
{
|
||||
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
|
||||
{
|
||||
get => isTermOfServiceAgreed; set
|
||||
{
|
||||
if (SetProperty(ref isTermOfServiceAgreed, value))
|
||||
{
|
||||
OnAgreeSateChanged();
|
||||
OnAgreementStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +130,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SetProperty(ref isPrivacyPolicyAgreed, value))
|
||||
{
|
||||
OnAgreeSateChanged();
|
||||
OnAgreementStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +141,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SetProperty(ref isIssueReportAgreed, value))
|
||||
{
|
||||
OnAgreeSateChanged();
|
||||
OnAgreementStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,14 +152,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SetProperty(ref isOpenSourceLicenseAgreed, value))
|
||||
{
|
||||
OnAgreeSateChanged();
|
||||
OnAgreementStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
public ObservableCollection<DownloadSummary>? DownloadSummaries
|
||||
{
|
||||
get => downloadSummaries;
|
||||
@@ -152,7 +170,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
++State;
|
||||
}
|
||||
|
||||
private void OnAgreeSateChanged()
|
||||
private void OnAgreementStateChanged()
|
||||
{
|
||||
IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed;
|
||||
}
|
||||
@@ -173,7 +191,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
StaticResource.FulfillAll();
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Completed);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Completed);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ internal static class StaticResource
|
||||
|
||||
private static readonly ApplicationDataCompositeValue DefaultResourceVersionMap = new()
|
||||
{
|
||||
// DO NOT MIDIFY THIS MAP
|
||||
// DO NOT MODIFY THIS MAP
|
||||
{ "AchievementIcon", 0 },
|
||||
{ "AvatarCard", 0 },
|
||||
{ "AvatarIcon", 0 },
|
||||
|
||||
@@ -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)}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -224,7 +224,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
private static void ResetStaticResource()
|
||||
{
|
||||
StaticResource.FailAll();
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
[Command("ResetGuideStateCommand")]
|
||||
private static void ResetGuideState()
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
|
||||
}
|
||||
|
||||
[Command("ExceptionCommand")]
|
||||
|
||||
@@ -35,8 +35,7 @@ internal static class HttpContentBuilderExtension
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static T SetFormUrlEncodedContent<T>(
|
||||
this T builder, IEnumerable<KeyValuePair<string, string>> content)
|
||||
public static T SetFormUrlEncodedContent<T>(this T builder, IEnumerable<KeyValuePair<string, string>> content)
|
||||
where T : IHttpContentBuilder
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
@@ -41,12 +41,6 @@ internal static partial class HttpHeadersBuilderExtension
|
||||
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]
|
||||
public static T SetHeader<T>(this T builder, string name)
|
||||
where T : IHttpHeadersBuilder<HttpHeaders>
|
||||
@@ -80,6 +74,12 @@ internal static partial class HttpHeadersBuilderExtension
|
||||
.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]
|
||||
public static T RemoveHeader<T>(this T builder, params string?[]? names)
|
||||
where T : IHttpHeadersBuilder<HttpHeaders>
|
||||
|
||||
@@ -12,7 +12,7 @@ internal static class HttpHeadersExtension
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
// 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);
|
||||
|
||||
if (values.Any())
|
||||
|
||||
Reference in New Issue
Block a user