Compare commits

..

28 Commits

Author SHA1 Message Date
qhy040404
a845dff6ee Revert "temporary fix qr login"
This reverts commit d4bd610fe2.
2024-04-14 13:55:16 +08:00
DismissedLight
ee99d0b665 refactor static resource 2024-04-12 21:40:00 +08:00
Lightczx
72b62aa9c6 ServerGarbageCollection 2024-04-12 17:30:10 +08:00
Lightczx
6b031e1866 update static resource setting 2024-04-12 16:52:54 +08:00
Lightczx
59c03c7f3b update WAS to 1.5.2 2024-04-12 11:57:49 +08:00
Lightczx
c03a96b44f refine guide ui 2024-04-11 16:14:04 +08:00
DismissedLight
d5a97903d3 Merge pull request #1545 from DGP-Studio/feat/version1.10guide 2024-04-11 15:57:44 +08:00
Lightczx
7f998dc87f impl #1430 2024-04-11 15:48:11 +08:00
Lightczx
2367c4759d refactor 2024-04-10 16:53:21 +08:00
DismissedLight
043e3f07d8 Merge pull request #1539 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-017b70aa21 2024-04-08 16:15:33 +08:00
dependabot[bot]
cd075c4dab Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx) and [MSTest.TestFramework](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.2 to 3.3.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.2...v3.3.1)

Updates `MSTest.TestFramework` from 3.2.2 to 3.3.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.2...v3.3.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 07:37:05 +00:00
Lightczx
65252f1f69 refactor launch game view model 2024-04-08 15:33:42 +08:00
DismissedLight
f5dd5f4c1d Merge pull request #1531 from DGP-Studio/feat/fix_config 2024-04-08 14:08:21 +08:00
Lightczx
27ce55f3f7 code style 2024-04-08 14:06:04 +08:00
qhy040404
311941bb89 code style 2024-04-08 14:01:59 +08:00
qhy040404
07e2489cab use header instead of another textblock 2024-04-08 12:11:46 +08:00
qhy040404
699ac60aaf apply suggestions 2024-04-08 12:00:16 +08:00
qhy040404
dc7bc7e35d fix compile error 2024-04-08 11:59:05 +08:00
qhy040404
bf67fcf3a2 add ability to fix missing config.ini 2024-04-08 11:59:05 +08:00
DismissedLight
5093246571 Merge pull request #1538 from DGP-Studio/feat/IFileOperation 2024-04-08 11:34:09 +08:00
Lightczx
94fe192581 add IFileOperation 2024-04-08 11:25:32 +08:00
Lightczx
c679032387 button builder 2024-04-08 09:40:53 +08:00
Lightczx
d119b056c7 refactor achievement viewmodel 2024-04-07 16:50:35 +08:00
Lightczx
ab95ce8ce8 code style 2024-04-07 15:07:53 +08:00
Lightczx
0ede5b158f button builder 2024-04-07 14:49:36 +08:00
Lightczx
6c9a98c2c9 refactor infobarservice 2024-04-07 13:30:38 +08:00
Lightczx
a725fc0e9e fix clamp upper bound 2024-04-07 10:46:09 +08:00
Lightczx
5251dd9343 clamp monitor index 2024-04-07 10:44:42 +08:00
89 changed files with 1818 additions and 567 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

@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System; using System;
namespace Snap.Hutao.Test.PlatformExtensions; namespace Snap.Hutao.Test.PlatformExtensions;
@@ -11,6 +12,7 @@ public sealed class DependencyInjectionTest
.AddSingleton<IService, ServiceB>() .AddSingleton<IService, ServiceB>()
.AddScoped<IScopedService, ServiceA>() .AddScoped<IScopedService, ServiceA>()
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>)) .AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
.AddLogging(builder => builder.AddConsole())
.BuildServiceProvider(); .BuildServiceProvider();
[TestMethod] [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 private interface IService
{ {
Guid Id { get; } Guid Id { get; }

View File

@@ -12,9 +12,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <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="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" /> <PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" /> <PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -0,0 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Builder.ButtonBase;
internal class ButtonBaseBuilder<TButton> : IButtonBaseBuilder<TButton>
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase, new()
{
public TButton Button { get; } = new();
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
namespace Snap.Hutao.Control.Builder.ButtonBase;
internal static class ButtonBaseBuilderExtension
{
public static TBuilder SetContent<TBuilder, TButton>(this TBuilder builder, object? content)
where TBuilder : IButtonBaseBuilder<TButton>
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
{
builder.Configure(builder => builder.Button.Content = content);
return builder;
}
public static TBuilder SetCommand<TBuilder, TButton>(this TBuilder builder, ICommand command)
where TBuilder : IButtonBaseBuilder<TButton>
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
{
builder.Configure(builder => builder.Button.Command = command);
return builder;
}
}

View File

@@ -0,0 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Builder.ButtonBase;
internal sealed class ButtonBuilder : ButtonBaseBuilder<Button>;

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Builder.ButtonBase;
internal static class ButtonBuilderExtension
{
public static ButtonBuilder SetContent(this ButtonBuilder builder, object? content)
{
return builder.SetContent<ButtonBuilder, Button>(content);
}
public static ButtonBuilder SetCommand(this ButtonBuilder builder, ICommand command)
{
return builder.SetCommand<ButtonBuilder, Button>(command);
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Control.Builder.ButtonBase;
internal interface IButtonBaseBuilder<TButton> : IBuilder
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
{
TButton Button { get; }
}

View File

@@ -1,10 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Diagnostics; using System.Diagnostics;
namespace Snap.Hutao.Web.Request.Builder; namespace Snap.Hutao.Core.Abstraction.Extension;
internal static class BuilderExtension internal static class BuilderExtension
{ {

View File

@@ -1,6 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Core.Abstraction;
internal interface IBuilder; internal interface IBuilder;

View File

@@ -5,6 +5,9 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing; using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.ViewModel.Guide;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Frozen; using System.Collections.Frozen;
using System.IO; using System.IO;
@@ -36,6 +39,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new(); private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
private readonly IHttpClientFactory httpClientFactory; private readonly IHttpClientFactory httpClientFactory;
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ILogger<ImageCache> logger; private readonly ILogger<ImageCache> logger;
@@ -169,38 +173,49 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache)); HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
while (retryCount < 3) while (retryCount < 3)
{ {
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) HttpRequestMessageBuilder requestMessageBuilder = httpRequestMessageBuilderFactory
{ .Create()
if (message.RequestMessage is { RequestUri: { } target } && target != uri) .SetRequestUri(uri)
{
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
}
if (message.IsSuccessStatusCode) // These headers are only available for our own api
.SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase))
.Get();
using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage)
{
using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{ {
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false)) if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri)
{ {
using (FileStream fileStream = File.Create(baseFile)) logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
}
if (responseMessage.IsSuccessStatusCode)
{
using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
{ {
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false); using (FileStream fileStream = File.Create(baseFile))
return; {
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
return;
}
} }
} }
}
switch (message.StatusCode) switch (responseMessage.StatusCode)
{ {
case HttpStatusCode.TooManyRequests: case HttpStatusCode.TooManyRequests:
{ {
retryCount++; retryCount++;
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount]; TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay); logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false); await Task.Delay(delay).ConfigureAwait(false);
break; break;
} }
default: default:
return; return;
}
} }
} }
} }

View File

@@ -38,7 +38,7 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
return; return;
} }
if (serviceProvider.IsDisposedSlow()) if (serviceProvider.IsDisposed())
{ {
return; return;
} }
@@ -96,7 +96,7 @@ internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
return; return;
} }
if (serviceProvider.IsDisposedSlow()) if (serviceProvider.IsDisposed())
{ {
return; return;
} }

View File

@@ -18,13 +18,22 @@ internal static class ServiceProviderExtension
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDisposedSlow(this IServiceProvider? serviceProvider) public static bool IsDisposed(this IServiceProvider? serviceProvider)
{ {
if (serviceProvider is null) if (serviceProvider is null)
{ {
return true; return true;
} }
if (serviceProvider is ServiceProvider serviceProviderImpl)
{
return GetPrivateDisposed(serviceProviderImpl);
}
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true; 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);
} }

View File

@@ -40,15 +40,23 @@ internal sealed class HutaoException : Exception
} }
} }
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default) [DoesNotReturn]
{
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
}
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default) public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
{ {
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id); string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException); throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
} }
[DoesNotReturn]
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
{
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
throw new InvalidCastException(message, innerException);
}
[DoesNotReturn]
public static OperationCanceledException OperationCanceled(string message, Exception? innerException = default)
{
return new OperationCanceledException(message, innerException);
}
} }

View File

@@ -8,7 +8,6 @@ internal enum HutaoExceptionKind
None, None,
// Foundation // Foundation
ServiceTypeCastFailed,
ImageCacheInvalidUri, ImageCacheInvalidUri,
// IO // IO
@@ -18,4 +17,5 @@ internal enum HutaoExceptionKind
// Service // Service
GachaStatisticsInvalidItemId, GachaStatisticsInvalidItemId,
GameFpsUnlockingFailed, GameFpsUnlockingFailed,
GameConfigInvalidChannelOptions,
} }

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 StaticResourceImageArchive = "StaticResourceImageArchive";
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

@@ -14,10 +14,10 @@ namespace Snap.Hutao;
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
{ {
private const int MinWidth = 1000; private const int MinWidth = 1000;
private const int MinHeight = 600; private const int MinHeight = 650;
private const int MaxWidth = 1200; private const int MaxWidth = 1200;
private const int MaxHeight = 750; private const int MaxHeight = 800;
private readonly WindowOptions windowOptions; private readonly WindowOptions windowOptions;

View File

@@ -27,4 +27,27 @@ internal sealed partial class IdentifyMonitorWindow : Window
} }
public string Monitor { get; private set; } 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();
}
}
} }

View File

@@ -1334,6 +1334,9 @@
<data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve"> <data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve">
<value>为账号命名</value> <value>为账号命名</value>
</data> </data>
<data name="ViewDialogLaunchGameConfigurationFixDialogHint" xml:space="preserve">
<value>请选择当前游戏路径对应的游戏服务器</value>
</data>
<data name="ViewDialogLaunchGameConfigurationFixDialogTitle" xml:space="preserve"> <data name="ViewDialogLaunchGameConfigurationFixDialogTitle" xml:space="preserve">
<value>正在修复配置文件</value> <value>正在修复配置文件</value>
</data> </data>
@@ -1379,6 +1382,9 @@
<data name="ViewGachaLogHeader" xml:space="preserve"> <data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈愿记录</value> <value>祈愿记录</value>
</data> </data>
<data name="ViewGuideStaticResourceDownloadSize" xml:space="preserve">
<value>预计下载大小:{0}</value>
</data>
<data name="ViewGuideStepAgreementIHaveReadText" xml:space="preserve"> <data name="ViewGuideStepAgreementIHaveReadText" xml:space="preserve">
<value>我已阅读并同意</value> <value>我已阅读并同意</value>
</data> </data>
@@ -1394,6 +1400,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>
@@ -1404,7 +1416,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>
@@ -1421,6 +1433,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>完整包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOn" xml:space="preserve">
<value>精简包体</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>
@@ -1610,6 +1640,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>高质量</value>
</data>
<data name="ViewModelGuideStaticResourceQualityRaw" xml:space="preserve">
<value>原图</value>
</data>
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve"> <data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
<value>请输入正确的邮箱</value> <value>请输入正确的邮箱</value>
</data> </data>

View File

@@ -36,4 +36,6 @@ internal sealed class GameFileSystem
public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); } public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); }
public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); } public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); }
public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); }
} }

View File

@@ -178,7 +178,16 @@ internal sealed class LaunchOptions : DbStoreOptions
[AllowNull] [AllowNull]
public NameValue<int> Monitor public NameValue<int> Monitor
{ {
get => GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[int.Parse(index, CultureInfo.InvariantCulture) - 1], Monitors[0]); get
{
return GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[RestrictIndex(Monitors, index)], Monitors[0]);
static int RestrictIndex(List<NameValue<int>> monitors, string index)
{
return Math.Clamp(int.Parse(index, CultureInfo.InvariantCulture) - 1, 0, monitors.Count - 1);
}
}
set set
{ {
if (value is not null) if (value is not null)

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Service.Notification;
internal interface IInfoBarOptionsBuilder : IBuilder
{
public InfoBarOptions Options { get; }
}

View File

@@ -11,5 +11,5 @@ internal interface IInfoBarService
{ {
ObservableCollection<InfoBar> Collection { get; } ObservableCollection<InfoBar> Collection { get; }
void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay, string? buttonContent, ICommand? buttonCommand); void PrepareInfoBarAndShow(Action<IInfoBarOptionsBuilder> configure);
} }

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
namespace Snap.Hutao.Service.Notification;
internal sealed class InfoBarOptions
{
public InfoBarSeverity Severity { get; set; }
public string? Title { get; set; }
public string? Message { get; set; }
public object? Content { get; set; }
public ButtonBase? ActionButton { get; set; }
public int MilliSecondsDelay { get; set; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Notification;
internal sealed class InfoBarOptionsBuilder : IInfoBarOptionsBuilder
{
public InfoBarOptions Options { get; } = new();
}

View File

@@ -0,0 +1,64 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Snap.Hutao.Control.Builder.ButtonBase;
using Snap.Hutao.Core.Abstraction.Extension;
namespace Snap.Hutao.Service.Notification;
internal static class InfoBarOptionsBuilderExtension
{
public static TBuilder SetSeverity<TBuilder>(this TBuilder builder, InfoBarSeverity severity)
where TBuilder : IInfoBarOptionsBuilder
{
builder.Configure(builder => builder.Options.Severity = severity);
return builder;
}
public static TBuilder SetTitle<TBuilder>(this TBuilder builder, string? title)
where TBuilder : IInfoBarOptionsBuilder
{
builder.Configure(builder => builder.Options.Title = title);
return builder;
}
public static IInfoBarOptionsBuilder SetMessage<TBuilder>(this TBuilder builder, string? message)
where TBuilder : IInfoBarOptionsBuilder
{
builder.Configure(builder => builder.Options.Message = message);
return builder;
}
public static IInfoBarOptionsBuilder SetContent<TBuilder>(this TBuilder builder, object? content)
where TBuilder : IInfoBarOptionsBuilder
{
builder.Configure(builder => builder.Options.Content = content);
return builder;
}
public static IInfoBarOptionsBuilder SetActionButton<TBuilder, TButton>(this TBuilder builder, Action<ButtonBaseBuilder<TButton>> configureButton)
where TBuilder : IInfoBarOptionsBuilder
where TButton : ButtonBase, new()
{
ButtonBaseBuilder<TButton> buttonBaseBuilder = new ButtonBaseBuilder<TButton>().Configure(configureButton);
builder.Configure(builder => builder.Options.ActionButton = buttonBaseBuilder.Button);
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
{
builder.Configure(builder => builder.Options.MilliSecondsDelay = milliSeconds);
return builder;
}
}

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Animation;
using Snap.Hutao.Core.Abstraction.Extension;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Windows.Foundation; using Windows.Foundation;
@@ -34,37 +35,30 @@ internal sealed class InfoBarService : IInfoBarService
get => collection ??= []; get => collection ??= [];
} }
public void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay, string? buttonContent = default, ICommand? buttonCommand = default) public void PrepareInfoBarAndShow(Action<IInfoBarOptionsBuilder> configure)
{ {
if (collection is null) if (collection is null)
{ {
return; return;
} }
PrepareInfoBarAndShowCoreAsync(severity, title, message, delay, buttonContent, buttonCommand).SafeForget(logger); PrepareInfoBarAndShowCoreAsync(configure).SafeForget(logger);
} }
private async ValueTask PrepareInfoBarAndShowCoreAsync(InfoBarSeverity severity, string? title, string? message, int delay, string? buttonContent, ICommand? buttonCommand) private async ValueTask PrepareInfoBarAndShowCoreAsync(Action<IInfoBarOptionsBuilder> configure)
{ {
await taskContext.SwitchToMainThreadAsync(); IInfoBarOptionsBuilder builder = new InfoBarOptionsBuilder().Configure(configure);
Button? actionButton = default; await taskContext.SwitchToMainThreadAsync();
if (buttonContent is not null)
{
actionButton = new()
{
Content = buttonContent,
Command = buttonCommand,
};
}
InfoBar infoBar = new() InfoBar infoBar = new()
{ {
ActionButton = actionButton, Severity = builder.Options.Severity,
Severity = severity, Title = builder.Options.Title,
Title = title, Message = builder.Options.Message,
Message = message, Content = builder.Options.Content,
IsOpen = true, IsOpen = true,
ActionButton = builder.Options.ActionButton,
Transitions = [new AddDeleteThemeTransition()], Transitions = [new AddDeleteThemeTransition()],
}; };
@@ -72,9 +66,9 @@ internal sealed class InfoBarService : IInfoBarService
ArgumentNullException.ThrowIfNull(collection); ArgumentNullException.ThrowIfNull(collection);
collection.Add(infoBar); collection.Add(infoBar);
if (delay > 0) if (builder.Options.MilliSecondsDelay > 0)
{ {
await Delay.FromMilliSeconds(delay).ConfigureAwait(true); await Delay.FromMilliSeconds(builder.Options.MilliSecondsDelay).ConfigureAwait(true);
collection.Remove(infoBar); collection.Remove(infoBar);
infoBar.IsOpen = false; infoBar.IsOpen = false;
} }

View File

@@ -2,108 +2,100 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Builder.ButtonBase;
using Snap.Hutao.Core.Abstraction.Extension;
namespace Snap.Hutao.Service.Notification; namespace Snap.Hutao.Service.Notification;
internal static class InfoBarServiceExtension internal static class InfoBarServiceExtension
{ {
public static void Information(this IInfoBarService infoBarService, string message, int delay = 5000) public static void Information(this IInfoBarService infoBarService, string message, int milliSeconds = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, null, message, delay, null, null); infoBarService.Information(builder => builder.SetMessage(message).SetDelay(milliSeconds));
} }
public static void Information(this IInfoBarService infoBarService, string title, string message, int delay = 5000) public static void Information(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay, null, null); infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
} }
public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 5000) public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay, buttonContent, null); infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
} }
public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 5000) public static void Information(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay, buttonContent, buttonCommand); infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Informational).Configure(configure));
} }
public static void Success(this IInfoBarService infoBarService, string message, int delay = 5000) public static void Success(this IInfoBarService infoBarService, string message, int milliSeconds = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, null, message, delay, null, null); infoBarService.Success(builder => builder.SetMessage(message).SetDelay(milliSeconds));
} }
public static void Success(this IInfoBarService infoBarService, string title, string message, int delay = 5000) public static void Success(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay, null, null); infoBarService.Success(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
} }
public static void Success(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 5000) public static void Success(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay, buttonContent, null); infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Success).Configure(configure));
} }
public static void Success(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 5000) public static void Warning(this IInfoBarService infoBarService, string message, int milliSeconds = 30000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay, buttonContent, buttonCommand); infoBarService.Warning(builder => builder.SetMessage(message).SetDelay(milliSeconds));
} }
public static void Warning(this IInfoBarService infoBarService, string message, int delay = 30000) public static void Warning(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 30000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, null, message, delay, null, null); infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
} }
public static void Warning(this IInfoBarService infoBarService, string title, string message, int delay = 30000) public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 30000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay, null, null); infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
} }
public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 30000) public static void Warning(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay, buttonContent, null); infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Warning).Configure(configure));
} }
public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 30000) public static void Error(this IInfoBarService infoBarService, string message, int milliSeconds = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay, buttonContent, buttonCommand); infoBarService.Error(builder => builder.SetMessage(message).SetDelay(milliSeconds));
} }
public static void Error(this IInfoBarService infoBarService, string message, int delay = 0) public static void Error(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, null, message, delay, null, null); infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
} }
public static void Error(this IInfoBarService infoBarService, string title, string message, int delay = 0) public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay, null, null); infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
} }
public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 0) public static void Error(this IInfoBarService infoBarService, Exception ex, int milliSeconds = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay, buttonContent, null); infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage(ex.Message).SetDelay(milliSeconds));
} }
public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 0) public static void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, int milliSeconds = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay, buttonContent, buttonCommand); infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetDelay(milliSeconds));
} }
public static void Error(this IInfoBarService infoBarService, Exception ex, int delay = 0) public static void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, string buttonContent, ICommand buttonCommand, int milliSeconds = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, ex.Message, delay, null, null); 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, Exception ex, string title, int delay = 0) public static void Error(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay, null, null); infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Error).Configure(configure));
}
public static void Error(this IInfoBarService infoBarService, Exception ex, string title, string buttonContent, int delay = 0)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay, buttonContent, null);
}
public static void Error(this IInfoBarService infoBarService, Exception ex, string title, string buttonContent, ICommand buttonCommand, int delay = 0)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay, buttonContent, buttonCommand);
} }
} }

View File

@@ -44,6 +44,8 @@
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<WindowsAppSdkUndockedRegFreeWinRTInitialize>false</WindowsAppSdkUndockedRegFreeWinRTInitialize> <WindowsAppSdkUndockedRegFreeWinRTInitialize>false</WindowsAppSdkUndockedRegFreeWinRTInitialize>
<ServerGarbageCollection>true</ServerGarbageCollection>
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -303,8 +305,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -319,7 +321,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" /> <PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" /> <PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0"> <PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0">
@@ -347,9 +349,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> <ItemGroup>
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml"> <Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -1,4 +1,4 @@
<ContentDialog <ContentDialog
x:Class="Snap.Hutao.View.Dialog.LaunchGameConfigurationFixDialog" x:Class="Snap.Hutao.View.Dialog.LaunchGameConfigurationFixDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -19,6 +19,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
DisplayMemberPath="DisplayName" DisplayMemberPath="DisplayName"
EnableMemberPath="IsNotCompatOnly" EnableMemberPath="IsNotCompatOnly"
Header="{shcm:ResourceString Name=ViewDialogLaunchGameConfigurationFixDialogHint}"
ItemsSource="{x:Bind KnownSchemes}" ItemsSource="{x:Bind KnownSchemes}"
SelectedItem="{x:Bind SelectedScheme, Mode=TwoWay}" SelectedItem="{x:Bind SelectedScheme, Mode=TwoWay}"
Style="{StaticResource DefaultComboBoxStyle}"/> Style="{StaticResource DefaultComboBoxStyle}"/>

View File

@@ -10,15 +10,19 @@ namespace Snap.Hutao.View.Dialog;
[DependencyProperty("SelectedScheme", typeof(LaunchScheme))] [DependencyProperty("SelectedScheme", typeof(LaunchScheme))]
internal sealed partial class LaunchGameConfigurationFixDialog : ContentDialog internal sealed partial class LaunchGameConfigurationFixDialog : ContentDialog
{ {
public LaunchGameConfigurationFixDialog() private readonly ITaskContext taskContext;
public LaunchGameConfigurationFixDialog(IServiceProvider serviceProvider)
{ {
InitializeComponent(); InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
} }
public async ValueTask<ValueResult<bool, LaunchScheme?>> GetLaunchSchemeAsync() public async ValueTask<ValueResult<bool, LaunchScheme>> GetLaunchSchemeAsync()
{ {
await taskContext.SwitchToMainThreadAsync();
ContentDialogResult result = await ShowAsync(); ContentDialogResult result = await ShowAsync();
return new(result is ContentDialogResult.Primary, SelectedScheme);
return new(result == ContentDialogResult.Primary, SelectedScheme);
} }
} }

View File

@@ -49,7 +49,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 +125,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 +156,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 +173,78 @@
</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}"/>
<ListView
MinWidth="320"
Margin="0,8,0,32"
DisplayMemberPath="Name"
ItemsSource="{Binding StaticResourceOptions.ImageArchives}"
SelectedItem="{Binding StaticResourceOptions.ImageArchive, Mode=TwoWay}"/>
<TextBlock Margin="0,16,0,0" Text="{Binding StaticResourceOptions.SizeInformationText, Mode=OneWay}"/>
</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,12 +266,23 @@
<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:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xF2B7;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xF28B;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE81E;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE713;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE8BA;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE8B9;}"/>
</cwc:Segmented> </cwc:Segmented>
<Button <Button
Command="{Binding NextOrCompleteCommand}" Command="{Binding NextOrCompleteCommand}"

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.ViewModel.Guide; using Snap.Hutao.ViewModel.Guide;
namespace Snap.Hutao.View.Guide; namespace Snap.Hutao.View.Guide;
@@ -14,6 +15,6 @@ internal sealed partial class GuideView : UserControl
public GuideView() public GuideView()
{ {
InitializeComponent(); InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<GuideViewModel>(); DataContext = this.ServiceProvider().GetRequiredService<GuideViewModel>();
} }
} }

View File

@@ -11,6 +11,7 @@ namespace Snap.Hutao.ViewModel.Abstraction;
/// 视图模型抽象类 /// 视图模型抽象类
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[SuppressMessage("", "SA1124")]
internal abstract partial class ViewModel : ObservableObject, IViewModel internal abstract partial class ViewModel : ObservableObject, IViewModel
{ {
private bool isInitialized; private bool isInitialized;
@@ -28,9 +29,15 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
[Command("OpenUICommand")] [Command("OpenUICommand")]
protected virtual async Task OpenUIAsync() protected virtual async Task OpenUIAsync()
{ {
// Set value on UI thread try
IsInitialized = await InitializeUIAsync().ConfigureAwait(true); {
Initialization.TrySetResult(IsInitialized); // ConfigureAwait(true) sets value on UI thread
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
Initialization.TrySetResult(IsInitialized);
}
catch (OperationCanceledException)
{
}
} }
protected virtual ValueTask<bool> InitializeUIAsync() protected virtual ValueTask<bool> InitializeUIAsync()
@@ -46,6 +53,8 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
return disposable; return disposable;
} }
#region SetProperty
protected new bool SetProperty<T>([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null) protected new bool SetProperty<T>([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
{ {
return !IsViewDisposed && base.SetProperty(ref field, newValue, propertyName); return !IsViewDisposed && base.SetProperty(ref field, newValue, propertyName);
@@ -99,12 +108,13 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
return false; return false;
} }
#endregion
private void ThrowIfViewDisposed() private void ThrowIfViewDisposed()
{ {
if (IsViewDisposed) if (IsViewDisposed)
{ {
ThrowHelper.OperationCanceled(SH.ViewModelViewDisposedOperationCancel); HutaoException.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
} }
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -33,19 +34,18 @@ internal static class AchievementFinishPercent
if (achievements.SourceCollection is not List<AchievementView> list) if (achievements.SourceCollection is not List<AchievementView> list)
{ {
// Fast path throw HutaoException.InvalidCast<IEnumerable<AchievementView>, List<AchievementView>>("AchievementViewModel.Achievements.SourceCollection");
throw Must.NeverHappen("AchievementViewModel.Achievements.SourceCollection 应为 List<AchievementView>");
} }
Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.From); Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.From);
foreach (ref readonly AchievementView achievement in CollectionsMarshal.AsSpan(list)) foreach (ref readonly AchievementView achievementView in CollectionsMarshal.AsSpan(list))
{ {
ref AchievementGoalStatistics goalStat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal); ref AchievementGoalStatistics goalStat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievementView.Inner.Goal);
goalStat.TotalCount += 1; goalStat.TotalCount += 1;
totalCount += 1; totalCount += 1;
if (achievement.IsChecked) if (achievementView.IsChecked)
{ {
goalStat.Finished += 1; goalStat.Finished += 1;
totalFinished += 1; totalFinished += 1;

View File

@@ -15,123 +15,99 @@ using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
namespace Snap.Hutao.ViewModel.Achievement; namespace Snap.Hutao.ViewModel.Achievement;
/// <summary>
/// 成就导入器
/// </summary>
[HighQuality]
[ConstructorGenerated] [ConstructorGenerated]
[Injection(InjectAs.Scoped)] [Injection(InjectAs.Scoped)]
internal sealed partial class AchievementImporter internal sealed partial class AchievementImporter
{ {
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction; private readonly AchievementImporterDependencies dependencies;
private readonly IContentDialogFactory contentDialogFactory;
private readonly IAchievementService achievementService;
private readonly IClipboardProvider clipboardInterop;
private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions options;
private readonly ITaskContext taskContext;
/// <summary>
/// 从剪贴板导入
/// </summary>
/// <returns>是否导入成功</returns>
public async ValueTask<bool> FromClipboardAsync() public async ValueTask<bool> FromClipboardAsync()
{ {
if (achievementService.CurrentArchive is { } archive) if (dependencies.AchievementService.CurrentArchive is not { } archive)
{ {
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is { } uiaf) dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
{ return false;
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
}
else
{
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
}
}
else
{
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
} }
return false; if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is not { } uiaf)
{
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
return false;
}
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
} }
/// <summary>
/// 从文件导入
/// </summary>
/// <returns>是否导入成功</returns>
public async ValueTask<bool> FromFileAsync() public async ValueTask<bool> FromFileAsync()
{ {
if (achievementService.CurrentArchive is { } archive) if (dependencies.AchievementService.CurrentArchive is not { } archive)
{ {
ValueResult<bool, ValueFile> pickerResult = fileSystemPickerInteraction.PickFile( dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
SH.ServiceAchievementUIAFImportPickerTitile, return false;
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
if (pickerResult.TryGetValue(out ValueFile file))
{
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(options).ConfigureAwait(false);
if (uiafResult.TryGetValue(out UIAF? uiaf))
{
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
}
else
{
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
}
}
}
else
{
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
} }
return false; ValueResult<bool, ValueFile> pickerResult = dependencies.FileSystemPickerInteraction.PickFile(
SH.ServiceAchievementUIAFImportPickerTitile,
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
if (!pickerResult.TryGetValue(out ValueFile file))
{
return false;
}
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(dependencies.JsonSerializerOptions).ConfigureAwait(false);
if (!uiafResult.TryGetValue(out UIAF? uiaf))
{
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
return false;
}
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
} }
private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync() private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync()
{ {
try try
{ {
return await clipboardInterop.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false); return await dependencies.ClipboardProvider.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
infoBarService?.Error(ex, SH.ViewModelImportFromClipboardErrorTitle); dependencies.InfoBarService.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
return null; return null;
} }
} }
private async ValueTask<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf) private async ValueTask<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf)
{ {
if (uiaf.IsCurrentVersionSupported()) if (!uiaf.IsCurrentVersionSupported())
{ {
AchievementImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false); dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false); return false;
if (isOk)
{
await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
.ConfigureAwait(false);
ImportResult result;
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
}
infoBarService.Success($"{result}");
return true;
}
}
else
{
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
} }
return false; AchievementImportDialog importDialog = await dependencies.ContentDialogFactory
.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
if (!isOk)
{
return false;
}
await dependencies.TaskContext.SwitchToMainThreadAsync();
ContentDialog dialog = await dependencies.ContentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
.ConfigureAwait(false);
ImportResult result;
using (await dialog.BlockAsync(dependencies.TaskContext).ConfigureAwait(false))
{
result = await dependencies.AchievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
}
dependencies.InfoBarService.Success($"{result}");
return true;
} }
} }

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Factory.Picker;
using Snap.Hutao.Service.Achievement;
using Snap.Hutao.Service.Notification;
namespace Snap.Hutao.ViewModel.Achievement;
[ConstructorGenerated]
[Injection(InjectAs.Scoped)]
internal sealed partial class AchievementImporterDependencies
{
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly JsonSerializerOptions jsonSerializerOptions;
private readonly IContentDialogFactory contentDialogFactory;
private readonly IAchievementService achievementService;
private readonly IClipboardProvider clipboardProvider;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext;
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
public IAchievementService AchievementService { get => achievementService; }
public IClipboardProvider ClipboardProvider { get => clipboardProvider; }
public IInfoBarService InfoBarService { get => infoBarService; }
public ITaskContext TaskContext { get => taskContext; }
}

View File

@@ -5,8 +5,6 @@ using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Collection.AdvancedCollectionView; using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Factory.Picker;
using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Service.Achievement; using Snap.Hutao.Service.Achievement;
using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata;
@@ -23,9 +21,6 @@ using SortDirection = CommunityToolkit.WinUI.Collections.SortDirection;
namespace Snap.Hutao.ViewModel.Achievement; namespace Snap.Hutao.ViewModel.Achievement;
/// <summary>
/// 成就视图模型
/// </summary>
[HighQuality] [HighQuality]
[ConstructorGenerated] [ConstructorGenerated]
[Injection(InjectAs.Scoped)] [Injection(InjectAs.Scoped)]
@@ -34,14 +29,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
private readonly SortDescription uncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending); private readonly SortDescription uncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending);
private readonly SortDescription completionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending); private readonly SortDescription completionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction; private readonly AchievementViewModelDependencies dependencies;
private readonly IContentDialogFactory contentDialogFactory;
private readonly AchievementImporter achievementImporter;
private readonly IAchievementService achievementService;
private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions options;
private readonly ITaskContext taskContext;
private AdvancedCollectionView<AchievementView>? achievements; private AdvancedCollectionView<AchievementView>? achievements;
private List<AchievementGoalView>? achievementGoals; private List<AchievementGoalView>? achievementGoals;
@@ -52,18 +40,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
private string searchText = string.Empty; private string searchText = string.Empty;
private string? finishDescription; private string? finishDescription;
/// <summary>
/// 成就存档集合
/// </summary>
public ObservableCollection<EntityAchievementArchive>? Archives public ObservableCollection<EntityAchievementArchive>? Archives
{ {
get => archives; get => archives;
set => SetProperty(ref archives, value); set => SetProperty(ref archives, value);
} }
/// <summary>
/// 选中的成就存档
/// </summary>
public EntityAchievementArchive? SelectedArchive public EntityAchievementArchive? SelectedArchive
{ {
get => selectedArchive; get => selectedArchive;
@@ -71,38 +53,24 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
if (SetProperty(ref selectedArchive, value)) if (SetProperty(ref selectedArchive, value))
{ {
if (IsViewDisposed) dependencies.AchievementService.CurrentArchive = value;
{
return;
}
achievementService.CurrentArchive = value;
UpdateAchievementsAsync(value).SafeForget(); UpdateAchievementsAsync(value).SafeForget();
} }
} }
} }
/// <summary>
/// 成就视图
/// </summary>
public AdvancedCollectionView<AchievementView>? Achievements public AdvancedCollectionView<AchievementView>? Achievements
{ {
get => achievements; get => achievements;
set => SetProperty(ref achievements, value); set => SetProperty(ref achievements, value);
} }
/// <summary>
/// 成就分类
/// </summary>
public List<AchievementGoalView>? AchievementGoals public List<AchievementGoalView>? AchievementGoals
{ {
get => achievementGoals; get => achievementGoals;
set => SetProperty(ref achievementGoals, value); set => SetProperty(ref achievementGoals, value);
} }
/// <summary>
/// 选中的成就分类
/// </summary>
public AchievementGoalView? SelectedAchievementGoal public AchievementGoalView? SelectedAchievementGoal
{ {
get => selectedAchievementGoal; get => selectedAchievementGoal;
@@ -116,27 +84,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
} }
} }
/// <summary>
/// 搜索文本
/// </summary>
public string SearchText public string SearchText
{ {
get => searchText; get => searchText;
set => SetProperty(ref searchText, value); set => SetProperty(ref searchText, value);
} }
/// <summary>
/// 未完成优先
/// </summary>
public bool IsUncompletedItemsFirst public bool IsUncompletedItemsFirst
{ {
get => isUncompletedItemsFirst; get => isUncompletedItemsFirst;
set => SetProperty(ref isUncompletedItemsFirst, value); set => SetProperty(ref isUncompletedItemsFirst, value);
} }
/// <summary>
/// 完成进度描述
/// </summary>
public string? FinishDescription public string? FinishDescription
{ {
get => finishDescription; get => finishDescription;
@@ -160,36 +119,30 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
protected override async ValueTask<bool> InitializeUIAsync() protected override async ValueTask<bool> InitializeUIAsync()
{ {
if (await metadataService.InitializeAsync().ConfigureAwait(false)) if (!await dependencies.MetadataService.InitializeAsync().ConfigureAwait(false))
{ {
try return false;
{
List<AchievementGoalView> sortedGoals;
ObservableCollection<EntityAchievementArchive> archives;
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
List<MetadataAchievementGoal> goals = await metadataService
.GetAchievementGoalListAsync(CancellationToken)
.ConfigureAwait(false);
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
archives = achievementService.ArchiveCollection;
}
await taskContext.SwitchToMainThreadAsync();
AchievementGoals = sortedGoals;
Archives = archives;
SelectedArchive = achievementService.CurrentArchive;
return true;
}
catch (OperationCanceledException)
{
}
} }
return false; List<AchievementGoalView> sortedGoals;
ObservableCollection<EntityAchievementArchive> archives;
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
List<MetadataAchievementGoal> goals = await dependencies.MetadataService
.GetAchievementGoalListAsync(CancellationToken)
.ConfigureAwait(false);
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
archives = dependencies.AchievementService.ArchiveCollection;
}
await dependencies.TaskContext.SwitchToMainThreadAsync();
AchievementGoals = sortedGoals;
Archives = archives;
SelectedArchive = dependencies.AchievementService.CurrentArchive;
return true;
} }
[GeneratedRegex("\\d\\.\\d")] [GeneratedRegex("\\d\\.\\d")]
@@ -200,25 +153,25 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
if (Archives is not null) if (Archives is not null)
{ {
AchievementArchiveCreateDialog dialog = await contentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false); AchievementArchiveCreateDialog dialog = await dependencies.ContentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
(bool isOk, string name) = await dialog.GetInputAsync().ConfigureAwait(false); (bool isOk, string name) = await dialog.GetInputAsync().ConfigureAwait(false);
if (isOk) if (isOk)
{ {
ArchiveAddResult result = await achievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false); ArchiveAddResult result = await dependencies.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
switch (result) switch (result)
{ {
case ArchiveAddResult.Added: case ArchiveAddResult.Added:
await taskContext.SwitchToMainThreadAsync(); await dependencies.TaskContext.SwitchToMainThreadAsync();
SelectedArchive = achievementService.CurrentArchive; SelectedArchive = dependencies.AchievementService.CurrentArchive;
infoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name)); dependencies.InfoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
break; break;
case ArchiveAddResult.InvalidName: case ArchiveAddResult.InvalidName:
infoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName); dependencies.InfoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
break; break;
case ArchiveAddResult.AlreadyExists: case ArchiveAddResult.AlreadyExists:
infoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name)); dependencies.InfoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
break; break;
default: default:
throw Must.NeverHappen(); throw Must.NeverHappen();
@@ -234,7 +187,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
string title = SH.FormatViewModelAchievementRemoveArchiveTitle(SelectedArchive.Name); string title = SH.FormatViewModelAchievementRemoveArchiveTitle(SelectedArchive.Name);
string content = SH.ViewModelAchievementRemoveArchiveContent; string content = SH.ViewModelAchievementRemoveArchiveContent;
ContentDialogResult result = await contentDialogFactory ContentDialogResult result = await dependencies.ContentDialogFactory
.CreateForConfirmCancelAsync(title, content) .CreateForConfirmCancelAsync(title, content)
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -244,11 +197,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{ {
await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false); await dependencies.AchievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
} }
// Re-select first archive // Re-select first archive
await taskContext.SwitchToMainThreadAsync(); await dependencies.TaskContext.SwitchToMainThreadAsync();
SelectedArchive = Archives.FirstOrDefault(); SelectedArchive = Archives.FirstOrDefault();
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -263,21 +216,21 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
if (SelectedArchive is not null && Achievements is not null) if (SelectedArchive is not null && Achievements is not null)
{ {
(bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile( (bool isOk, ValueFile file) = dependencies.FileSystemPickerInteraction.SaveFile(
SH.ViewModelAchievementUIAFExportPickerTitle, SH.ViewModelAchievementUIAFExportPickerTitle,
$"{achievementService.CurrentArchive?.Name}.json", $"{dependencies.AchievementService.CurrentArchive?.Name}.json",
[(SH.ViewModelAchievementExportFileType, "*.json")]); [(SH.ViewModelAchievementExportFileType, "*.json")]);
if (isOk) if (isOk)
{ {
UIAF uiaf = await achievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false); UIAF uiaf = await dependencies.AchievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
if (await file.SerializeToJsonAsync(uiaf, options).ConfigureAwait(false)) if (await file.SerializeToJsonAsync(uiaf, dependencies.JsonSerializerOptions).ConfigureAwait(false))
{ {
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage); dependencies.InfoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
} }
else else
{ {
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage); dependencies.InfoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
} }
} }
} }
@@ -286,20 +239,20 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
[Command("ImportUIAFFromClipboardCommand")] [Command("ImportUIAFFromClipboardCommand")]
private async Task ImportUIAFFromClipboardAsync() private async Task ImportUIAFFromClipboardAsync()
{ {
if (await achievementImporter.FromClipboardAsync().ConfigureAwait(false)) if (await dependencies.AchievementImporter.FromClipboardAsync().ConfigureAwait(false))
{ {
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive); ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false); await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
} }
} }
[Command("ImportUIAFFromFileCommand")] [Command("ImportUIAFFromFileCommand")]
private async Task ImportUIAFFromFileAsync() private async Task ImportUIAFFromFileAsync()
{ {
if (await achievementImporter.FromFileAsync().ConfigureAwait(false)) if (await dependencies.AchievementImporter.FromFileAsync().ConfigureAwait(false))
{ {
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive); ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false); await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
} }
} }
@@ -311,11 +264,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
return; return;
} }
List<MetadataAchievement> achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false); List<MetadataAchievement> achievements = await dependencies.MetadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined)) if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
{ {
await taskContext.SwitchToMainThreadAsync(); await dependencies.TaskContext.SwitchToMainThreadAsync();
Achievements = new(combined, true); Achievements = new(combined, true);
UpdateAchievementsFinishPercent(); UpdateAchievementsFinishPercent();
@@ -328,12 +281,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
try try
{ {
combined = achievementService.GetAchievementViewList(archive, achievements); combined = dependencies.AchievementService.GetAchievementViewList(archive, achievements);
return true; return true;
} }
catch (Core.ExceptionService.UserdataCorruptedException ex) catch (Core.ExceptionService.UserdataCorruptedException ex)
{ {
infoBarService.Error(ex); dependencies.InfoBarService.Error(ex);
combined = default; combined = default;
return false; return false;
} }
@@ -418,7 +371,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{ {
if (achievement is not null) if (achievement is not null)
{ {
achievementService.SaveAchievement(achievement); dependencies.AchievementService.SaveAchievement(achievement);
UpdateAchievementsFinishPercent(); UpdateAchievementsFinishPercent();
} }
} }

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Factory.Picker;
using Snap.Hutao.Service.Achievement;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification;
namespace Snap.Hutao.ViewModel.Achievement;
[ConstructorGenerated]
[Injection(InjectAs.Scoped)]
internal sealed partial class AchievementViewModelDependencies
{
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly IContentDialogFactory contentDialogFactory;
private readonly AchievementImporter achievementImporter;
private readonly IAchievementService achievementService;
private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions jsonSerializerOptions;
private readonly ITaskContext taskContext;
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
public AchievementImporter AchievementImporter { get => achievementImporter; }
public IAchievementService AchievementService { get => achievementService; }
public IMetadataService MetadataService { get => metadataService; }
public IInfoBarService InfoBarService { get => infoBarService; }
public ITaskContext TaskContext { get => taskContext; }
}

View File

@@ -1,12 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game.PathAbstraction; using Snap.Hutao.Service.Game.PathAbstraction;
using Snap.Hutao.Service.Notification;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace Snap.Hutao.ViewModel.Game; namespace Snap.Hutao.ViewModel.Game;
internal interface IViewModelSupportLaunchExecution 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
}
} }

View File

@@ -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);
}
}
}

View File

@@ -25,7 +25,7 @@ internal sealed partial class LaunchGameShared
private readonly LaunchOptions launchOptions; private readonly LaunchOptions launchOptions;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
public LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService) public LaunchScheme? GetCurrentLaunchSchemeFromConfigFile()
{ {
ChannelOptions options = gameService.GetChannelOptions(); ChannelOptions options = gameService.GetChannelOptions();
@@ -41,7 +41,7 @@ internal sealed partial class LaunchGameShared
if (!IgnoredInvalidChannelOptions.Contains(options)) if (!IgnoredInvalidChannelOptions.Contains(options))
{ {
// 后台收集 // 后台收集
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}"); HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}");
} }
} }
@@ -58,32 +58,38 @@ internal sealed partial class LaunchGameShared
} }
[Command("HandleConfigurationFileNotFoundCommand")] [Command("HandleConfigurationFileNotFoundCommand")]
private async void HandleConfigurationFileNotFoundAsync() private async Task HandleConfigurationFileNotFoundAsync()
{ {
launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem); if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
ArgumentNullException.ThrowIfNull(gameFileSystem); {
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); return;
string version = await File.ReadAllTextAsync(Path.Combine(gameFileSystem.GameDirectory, isOversea ? GameConstants.GenshinImpactData : GameConstants.YuanShenData, "Persistent", "ScriptVersion")).ConfigureAwait(false); }
LaunchGameConfigurationFixDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameConfigurationFixDialog>().ConfigureAwait(false); 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(); await taskContext.SwitchToMainThreadAsync();
dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea); dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea);
dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly); dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly);
(bool isOk, LaunchScheme? launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false); (bool isOk, LaunchScheme launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false);
if (isOk) if (isOk)
{ {
ArgumentNullException.ThrowIfNull(launchScheme); string gameBiz = launchScheme.IsOversea ? "hk4e_global" : "hk4e_cn";
string content = $""" string content = $"""
[General] [General]
channel={(int)launchScheme.Channel} channel={launchScheme.Channel:D}
cps=mihoyo cps=mihoyo
game_version={version} game_version={version}
sub_channel={(int)launchScheme.SubChannel} sub_channel={launchScheme.SubChannel:D}
sdk_version= sdk_version=
game_biz=hk4e_{(launchScheme.IsOversea ? "global" : "cn")} game_biz={gameBiz}
"""; """;
await File.WriteAllTextAsync(gameFileSystem.GameConfigFilePath, content).ConfigureAwait(false); await File.WriteAllTextAsync(gameFileSystem.GameConfigFilePath, content).ConfigureAwait(false);

View File

@@ -60,6 +60,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
private GamePathEntry? selectedGamePathEntry; private GamePathEntry? selectedGamePathEntry;
private GameAccountFilter? gameAccountFilter; private GameAccountFilter? gameAccountFilter;
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
public LaunchOptions LaunchOptions { get => launchOptions; } public LaunchOptions LaunchOptions { get => launchOptions; }
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
@@ -83,7 +85,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); } public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
[AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))]
public bool GamePathSelectedAndValid public bool GamePathSelectedAndValid
{ {
get => gamePathSelectedAndValid; get => gamePathSelectedAndValid;
@@ -100,7 +101,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
{ {
using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{ {
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService); LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
await SetSelectedSchemeAsync(scheme).ConfigureAwait(true); await SetSelectedSchemeAsync(scheme).ConfigureAwait(true);
@@ -149,7 +150,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); } public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
[AlsoSets(nameof(GamePathSelectedAndValid))]
public GamePathEntry? SelectedGamePathEntry public GamePathEntry? SelectedGamePathEntry
{ {
get => selectedGamePathEntry; get => selectedGamePathEntry;
@@ -181,6 +181,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
return ValueTask.FromResult(true); return ValueTask.FromResult(true);
} }
[Command("IdentifyMonitorsCommand")]
private static async Task IdentifyMonitorsAsync()
{
await IdentifyMonitorWindow.IdentifyAllMonitorsAsync(3);
}
[Command("SetGamePathCommand")] [Command("SetGamePathCommand")]
private async Task SetGamePathAsync() private async Task SetGamePathAsync()
{ {
@@ -210,21 +216,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
[Command("LaunchCommand")] [Command("LaunchCommand")]
private async Task LaunchAsync() private async Task LaunchAsync()
{ {
try await this.LaunchExecutionAsync().ConfigureAwait(false);
{
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);
}
} }
[Command("DetectGameAccountCommand")] [Command("DetectGameAccountCommand")]
@@ -245,7 +237,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
SelectedGameAccount = account; SelectedGameAccount = account;
} }
} }
catch (UserdataCorruptedException ex) catch (Exception ex)
{ {
infoBarService.Error(ex); infoBarService.Error(ex);
} }
@@ -254,47 +246,54 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
[Command("AttachGameAccountCommand")] [Command("AttachGameAccountCommand")]
private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount) private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount)
{ {
if (gameAccount is not null) if (gameAccount is null)
{ {
if (userService.Current?.SelectedUserGameRole is { } role) return;
{ }
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
} if (userService.Current?.SelectedUserGameRole is { } role)
else {
{ gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
infoBarService.Warning(SH.MustSelectUserAndUid); }
} else
{
infoBarService.Warning(SH.MustSelectUserAndUid);
} }
} }
[Command("ModifyGameAccountCommand")] [Command("ModifyGameAccountCommand")]
private async Task ModifyGameAccountAsync(GameAccount? gameAccount) 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")] [Command("RemoveGameAccountCommand")]
private async Task RemoveGameAccountAsync(GameAccount? gameAccount) 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")] [Command("OpenScreenshotFolderCommand")]
private async Task OpenScreenshotFolderAsync() private async Task OpenScreenshotFolderAsync()
{ {
string game = LaunchOptions.GamePath; if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
string? directory = Path.GetDirectoryName(game);
ArgumentException.ThrowIfNullOrEmpty(directory);
string screenshot = Path.Combine(directory, "ScreenShot");
if (Directory.Exists(screenshot))
{ {
await Windows.System.Launcher.LaunchFolderPathAsync(screenshot); return;
}
if (Directory.Exists(gameFileSystem.ScreenShotDirectory))
{
await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory);
} }
} }
@@ -302,12 +301,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
{ {
if (SetProperty(ref selectedScheme, value, nameof(SelectedScheme))) if (SetProperty(ref selectedScheme, value, nameof(SelectedScheme)))
{ {
UpdateGameResourceAsync(value).SafeForget();
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
// Clear the selected game account to prevent setting // Clear the selected game account to prevent setting
// incorrect CN/OS account when scheme not match // incorrect CN/OS account when scheme not match
SelectedGameAccount = default; SelectedGameAccount = default;
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
UpdateGameResourceAsync(value).SafeForget();
} }
async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme) async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme)
@@ -341,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();
}
}
} }

View File

@@ -22,7 +22,6 @@ namespace Snap.Hutao.ViewModel.Game;
internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>, IViewModelSupportLaunchExecution internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>, IViewModelSupportLaunchExecution
{ {
private readonly LaunchStatusOptions launchStatusOptions; private readonly LaunchStatusOptions launchStatusOptions;
private readonly ILogger<LaunchGameViewModelSlim> logger;
private readonly LaunchGameShared launchGameShared; private readonly LaunchGameShared launchGameShared;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly IGameServiceFacade gameService; private readonly IGameServiceFacade gameService;
@@ -32,6 +31,8 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
private GameAccount? selectedGameAccount; private GameAccount? selectedGameAccount;
private GameAccountFilter? gameAccountFilter; private GameAccountFilter? gameAccountFilter;
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); } public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
@@ -41,14 +42,10 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
/// </summary> /// </summary>
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); } public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
public void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
{
}
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task OpenUIAsync() protected override async Task OpenUIAsync()
{ {
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService); LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection; ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
try try
@@ -59,7 +56,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme); SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme);
} }
} }
catch (UserdataCorruptedException ex) catch (Exception ex)
{ {
infoBarService.Error(ex); infoBarService.Error(ex);
} }
@@ -76,23 +73,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
[Command("LaunchCommand")] [Command("LaunchCommand")]
private async Task LaunchAsync() private async Task LaunchAsync()
{ {
IInfoBarService infoBarService = ServiceProvider.GetRequiredService<IInfoBarService>(); await this.LaunchExecutionAsync().ConfigureAwait(false);
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);
}
} }
} }

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,14 +116,14 @@ 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)
{ {
if (imageCache is not IImageCacheFilePathOperation imageCacheFilePathOperation) if (imageCache is not IImageCacheFilePathOperation imageCacheFilePathOperation)
{ {
throw HutaoException.ServiceTypeCastFailed<IImageCache, IImageCacheFilePathOperation>(nameof(imageCache)); throw HutaoException.InvalidCast<IImageCache, IImageCacheFilePathOperation>(nameof(imageCache));
} }
using (ZipArchive archive = new(stream)) using (ZipArchive archive = new(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,9 @@ 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 Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.Response;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Globalization; using System.Globalization;
@@ -20,12 +23,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 +42,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 +66,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 +84,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 +102,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 +132,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
if (SetProperty(ref isPrivacyPolicyAgreed, value)) if (SetProperty(ref isPrivacyPolicyAgreed, value))
{ {
OnAgreeSateChanged(); OnAgreementStateChanged();
} }
} }
} }
@@ -121,7 +143,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
{ {
if (SetProperty(ref isIssueReportAgreed, value)) if (SetProperty(ref isIssueReportAgreed, value))
{ {
OnAgreeSateChanged(); OnAgreementStateChanged();
} }
} }
} }
@@ -132,27 +154,38 @@ 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;
set => SetProperty(ref downloadSummaries, value); set => SetProperty(ref downloadSummaries, value);
} }
protected override async ValueTask<bool> InitializeUIAsync()
{
HutaoInfrastructureClient hutaoInfrastructureClient = serviceProvider.GetRequiredService<HutaoInfrastructureClient>();
HutaoResponse<StaticResourceSizeInformation> response = await hutaoInfrastructureClient.GetStaticSizeAsync().ConfigureAwait(false);
if (response.IsOk())
{
await taskContext.SwitchToMainThreadAsync();
StaticResourceOptions.SizeInformation = response.Data;
}
return true;
}
[Command("NextOrCompleteCommand")] [Command("NextOrCompleteCommand")]
private void NextOrComplete() private void NextOrComplete()
{ {
++State; ++State;
} }
private void OnAgreeSateChanged() private void OnAgreementStateChanged()
{ {
IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed; IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed;
} }
@@ -173,7 +206,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 },
@@ -46,31 +46,31 @@ internal static class StaticResource
private static readonly ApplicationDataCompositeValue LatestResourceVersionMap = new() private static readonly ApplicationDataCompositeValue LatestResourceVersionMap = new()
{ {
{ "AchievementIcon", 1 }, { "AchievementIcon", 2 },
{ "AvatarCard", 1 }, { "AvatarCard", 2 },
{ "AvatarIcon", 4 }, { "AvatarIcon", 5 },
{ "Bg", 2 }, { "Bg", 3 },
{ "ChapterIcon", 2 }, { "ChapterIcon", 3 },
{ "CodexMonster", 0 }, { "CodexMonster", 0 },
{ "Costume", 1 }, { "Costume", 2 },
{ "EmotionIcon", 2 }, { "EmotionIcon", 3 },
{ "EquipIcon", 3 }, { "EquipIcon", 4 },
{ "GachaAvatarIcon", 3 }, { "GachaAvatarIcon", 4 },
{ "GachaAvatarImg", 3 }, { "GachaAvatarImg", 4 },
{ "GachaEquipIcon", 3 }, { "GachaEquipIcon", 4 },
{ "GcgCharAvatarIcon", 0 }, { "GcgCharAvatarIcon", 0 },
{ "IconElement", 2 }, { "IconElement", 3 },
{ "ItemIcon", 3 }, { "ItemIcon", 4 },
{ "LoadingPic", 1 }, { "LoadingPic", 2 },
{ "MonsterIcon", 2 }, { "MonsterIcon", 3 },
{ "MonsterSmallIcon", 1 }, { "MonsterSmallIcon", 2 },
{ "NameCardIcon", 2 }, { "NameCardIcon", 3 },
{ "NameCardPic", 3 }, { "NameCardPic", 4 },
{ "NameCardPicAlpha", 0 }, { "NameCardPicAlpha", 0 },
{ "Property", 1 }, { "Property", 2 },
{ "RelicIcon", 3 }, { "RelicIcon", 4 },
{ "Skill", 3 }, { "Skill", 4 },
{ "Talent", 3 }, { "Talent", 4 },
}; };
public static void FulfillAll() public static void FulfillAll()

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 StaticResourceArchive
{
[LocalizationKey(nameof(SH.ViewGuideStepStaticResourceSettingMinimumOff))]
Full,
[LocalizationKey(nameof(SH.ViewGuideStepStaticResourceSettingMinimumOn))]
Minimum,
}

View File

@@ -0,0 +1,26 @@
// 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-archive", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full)}");
}
public static TBuilder SetStaticResourceControlHeadersIf<TBuilder>(this TBuilder builder, bool condition)
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
{
return condition ? builder.SetStaticResourceControlHeaders() : builder;
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Common;
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
using Snap.Hutao.Web.Hutao;
namespace Snap.Hutao.ViewModel.Guide;
[Injection(InjectAs.Singleton)]
internal sealed class StaticResourceOptions : ObservableObject
{
private readonly List<NameValue<StaticResourceQuality>> imageQualities = CollectionsNameValue.FromEnum<StaticResourceQuality>(q => q.GetLocalizedDescription());
private readonly List<NameValue<StaticResourceArchive>> imageArchives = CollectionsNameValue.FromEnum<StaticResourceArchive>(a => a.GetLocalizedDescription());
private NameValue<StaticResourceQuality>? imageQuality;
private NameValue<StaticResourceArchive>? imageArchive;
private string? sizeInformationText;
private StaticResourceSizeInformation? sizeInformation;
public List<NameValue<StaticResourceQuality>> ImageQualities { get => imageQualities; }
public NameValue<StaticResourceQuality>? ImageQuality
{
get => imageQuality ??= ImageQualities.First(q => q.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw));
set
{
if (SetProperty(ref imageQuality, value) && value is not null)
{
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageQuality, value.Value);
UpdateSizeInformationText();
}
}
}
public List<NameValue<StaticResourceArchive>> ImageArchives { get => imageArchives; }
public NameValue<StaticResourceArchive>? ImageArchive
{
get => imageArchive ??= ImageArchives.First(a => a.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full));
set
{
if (SetProperty(ref imageArchive, value) && value is not null)
{
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageArchive, value.Value);
UpdateSizeInformationText();
}
}
}
public StaticResourceSizeInformation? SizeInformation
{
get => sizeInformation;
set
{
sizeInformation = value;
UpdateSizeInformationText();
}
}
public string? SizeInformationText { get => sizeInformationText; set => SetProperty(ref sizeInformationText, value); }
private void UpdateSizeInformationText()
{
if (SizeInformation is not null)
{
long result = (ImageQuality?.Value, ImageArchive?.Value) switch
{
(StaticResourceQuality.Raw, StaticResourceArchive.Full) => SizeInformation.RawFull,
(StaticResourceQuality.Raw, StaticResourceArchive.Minimum) => SizeInformation.RawMinimum,
(StaticResourceQuality.High, StaticResourceArchive.Full) => SizeInformation.HighFull,
(StaticResourceQuality.High, StaticResourceArchive.Minimum) => SizeInformation.HighMinimum,
_ => 0,
};
SizeInformationText = SH.FormatViewGuideStaticResourceDownloadSize(Converters.ToFileSizeString(result));
}
}
}

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

@@ -19,12 +19,10 @@ internal sealed partial class PandaClient
public async ValueTask<Response<UrlWrapper>> QRCodeFetchAsync(CancellationToken token = default) public async ValueTask<Response<UrlWrapper>> QRCodeFetchAsync(CancellationToken token = default)
{ {
// Use 12 (zzz) instead of 4 (gi) temporarily to get legacy game token GameLoginRequest options = GameLoginRequest.Create(4, HoyolabOptions.DeviceId40);
GameLoginRequest options = GameLoginRequest.Create(12, HoyolabOptions.DeviceId40);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeFetch) .SetRequestUri(ApiEndpoints.QrCodeFetch)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(options); .PostJson(options);
Response<UrlWrapper>? resp = await builder Response<UrlWrapper>? resp = await builder
@@ -36,11 +34,10 @@ internal sealed partial class PandaClient
public async ValueTask<Response<GameLoginResult>> QRCodeQueryAsync(string ticket, CancellationToken token = default) public async ValueTask<Response<GameLoginResult>> QRCodeQueryAsync(string ticket, CancellationToken token = default)
{ {
GameLoginRequest options = GameLoginRequest.Create(12, HoyolabOptions.DeviceId40, ticket); GameLoginRequest options = GameLoginRequest.Create(4, HoyolabOptions.DeviceId40, ticket);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeQuery) .SetRequestUri(ApiEndpoints.QrCodeQuery)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(options); .PostJson(options);
Response<GameLoginResult>? resp = await builder Response<GameLoginResult>? resp = await builder

View File

@@ -79,7 +79,6 @@ internal sealed partial class PassportClient2
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.AccountGetSTokenByGameToken) .SetRequestUri(ApiEndpoints.AccountGetSTokenByGameToken)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(data); .PostJson(data);
Response<LoginResult>? resp = await builder Response<LoginResult>? resp = await builder

View File

@@ -17,6 +17,16 @@ internal sealed partial class HutaoInfrastructureClient
private readonly ILogger<HutaoInfrastructureClient> logger; private readonly ILogger<HutaoInfrastructureClient> logger;
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
public async ValueTask<HutaoResponse<StaticResourceSizeInformation>> GetStaticSizeAsync(CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(HutaoEndpoints.StaticSize)
.Get();
HutaoResponse<StaticResourceSizeInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<StaticResourceSizeInformation>>(httpClient, logger, token).ConfigureAwait(false);
return Web.Response.Response.DefaultIfNull(resp);
}
public async ValueTask<HutaoResponse<IPInformation>> GetIPInformationAsync(CancellationToken token = default) public async ValueTask<HutaoResponse<IPInformation>> GetIPInformationAsync(CancellationToken token = default)
{ {
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hutao;
internal sealed partial class StaticResourceSizeInformation
{
[JsonPropertyName("raw_full")]
public long RawFull { get; set; }
[JsonPropertyName("raw_minimum")]
public long RawMinimum { get; set; }
[JsonPropertyName("tiny_full")]
public long HighFull { get; set; }
[JsonPropertyName("tiny_minimum")]
public long HighMinimum { get; set; }
}

View File

@@ -270,6 +270,8 @@ internal static class HutaoEndpoints
{ {
return $"{ApiSnapGenshinStaticZip}/{fileName}.zip"; return $"{ApiSnapGenshinStaticZip}/{fileName}.zip";
} }
public const string StaticSize = $"{ApiSnapGenshin}/static/size";
#endregion #endregion
#region Wallpaper #region Wallpaper

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using System.Net.Http; using System.Net.Http;
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using System.Net.Http; using System.Net.Http;
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;
internal interface IHttpProtocolVersionBuilder : IBuilder internal interface IHttpProtocolVersionBuilder : IBuilder

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using System.Net.Http; using System.Net.Http;
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using System.Net.Http; using System.Net.Http;
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;
internal interface IRequestUriBuilder : IBuilder internal interface IRequestUriBuilder : IBuilder

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http; using System.Net.Http;
@@ -34,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

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@@ -40,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>
@@ -79,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())

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http; using System.Net.Http;

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Diagnostics; using System.Diagnostics;

View File

@@ -0,0 +1,60 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.Foundation;
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1300")]
[SuppressMessage("", "SA1307")]
internal struct DECIMAL
{
public ushort wReserved;
public byte scale;
public byte sign;
public unsafe ushort signscale
{
get
{
fixed (DECIMAL* pThis = &this)
{
return *(ushort*)&pThis->scale;
}
}
set
{
fixed (DECIMAL* pThis = &this)
{
*(ushort*)&pThis->scale = value;
}
}
}
public uint Hi32;
public uint Lo32;
public uint Mid32;
public unsafe ulong Lo64
{
get
{
fixed (DECIMAL* pThis = &this)
{
return *(ulong*)&pThis->Lo32;
}
}
set
{
fixed (DECIMAL* pThis = &this)
{
*(ulong*)&pThis->Lo32 = value;
}
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Variant;
namespace Snap.Hutao.Win32.System.Com.StructuredStorage;
[SuppressMessage("", "IDE1006")]
[SuppressMessage("", "SA1300")]
[SuppressMessage("", "SA1307")]
internal struct PROPVARIANT
{
public VARENUM vt;
public ushort wReserved1;
public ushort wReserved2;
public ushort wReserved3;
public nint Value;
// https://learn.microsoft.com/zh-cn/windows/win32/api/propidlbase/ns-propidlbase-propvariant
public unsafe DECIMAL decVal
{
get
{
fixed (PROPVARIANT* pThis = &this)
{
return *(DECIMAL*)pThis;
}
}
set
{
fixed (PROPVARIANT* pThis = &this)
{
*(DECIMAL*)pThis = value;
}
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.System.Variant;
internal enum VARENUM : ushort
{
VT_EMPTY = 0,
VT_NULL = 1,
VT_I2 = 2,
VT_I4 = 3,
VT_R4 = 4,
VT_R8 = 5,
VT_CY = 6,
VT_DATE = 7,
VT_BSTR = 8,
VT_DISPATCH = 9,
VT_ERROR = 10,
VT_BOOL = 11,
VT_VARIANT = 12,
VT_UNKNOWN = 13,
VT_DECIMAL = 14,
VT_I1 = 16,
VT_UI1 = 17,
VT_UI2 = 18,
VT_UI4 = 19,
VT_I8 = 20,
VT_UI8 = 21,
VT_INT = 22,
VT_UINT = 23,
VT_VOID = 24,
VT_HRESULT = 25,
VT_PTR = 26,
VT_SAFEARRAY = 27,
VT_CARRAY = 28,
VT_USERDEFINED = 29,
VT_LPSTR = 30,
VT_LPWSTR = 31,
VT_RECORD = 36,
VT_INT_PTR = 37,
VT_UINT_PTR = 38,
VT_FILETIME = 64,
VT_BLOB = 65,
VT_STREAM = 66,
VT_STORAGE = 67,
VT_STREAMED_OBJECT = 68,
VT_STORED_OBJECT = 69,
VT_BLOB_OBJECT = 70,
VT_CF = 71,
VT_CLSID = 72,
VT_VERSIONED_STREAM = 73,
VT_BSTR_BLOB = 4095,
VT_VECTOR = 4096,
VT_ARRAY = 8192,
VT_BYREF = 16384,
VT_RESERVED = 32768,
VT_ILLEGAL = 65535,
VT_ILLEGALMASKED = 4095,
VT_TYPEMASK = 4095,
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell;
[Flags]
internal enum FILEOPERATION_FLAGS : uint
{
FOFX_NOSKIPJUNCTIONS = 0x00010000,
FOFX_PREFERHARDLINK = 0x00020000,
FOFX_SHOWELEVATIONPROMPT = 0x00040000,
FOFX_RECYCLEONDELETE = 0x00080000,
FOFX_EARLYFAILURE = 0x00100000,
FOFX_PRESERVEFILEEXTENSIONS = 0x00200000,
FOFX_KEEPNEWERFILE = 0x00400000,
FOFX_NOCOPYHOOKS = 0x00800000,
FOFX_NOMINIMIZEBOX = 0x01000000,
FOFX_MOVEACLSACROSSVOLUMES = 0x02000000,
FOFX_DONTDISPLAYSOURCEPATH = 0x04000000,
FOFX_DONTDISPLAYDESTPATH = 0x08000000,
FOFX_REQUIREELEVATION = 0x10000000,
FOFX_ADDUNDORECORD = 0x20000000,
FOFX_COPYASDOWNLOAD = 0x40000000,
FOFX_DONTDISPLAYLOCATIONS = 0x80000000,
FOF_MULTIDESTFILES = 0x00000001,
FOF_CONFIRMMOUSE = 0x00000002,
FOF_SILENT = 0x00000004,
FOF_RENAMEONCOLLISION = 0x00000008,
FOF_NOCONFIRMATION = 0x00000010,
FOF_WANTMAPPINGHANDLE = 0x00000020,
FOF_ALLOWUNDO = 0x00000040,
FOF_FILESONLY = 0x00000080,
FOF_SIMPLEPROGRESS = 0x00000100,
FOF_NOCONFIRMMKDIR = 0x00000200,
FOF_NOERRORUI = 0x00000400,
FOF_NOCOPYSECURITYATTRIBS = 0x00000800,
FOF_NORECURSION = 0x00001000,
FOF_NO_CONNECTED_ELEMENTS = 0x00002000,
FOF_WANTNUKEWARNING = 0x00004000,
FOF_NORECURSEREPARSE = 0x00008000,
FOF_NO_UI = 0x00000614,
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Snap.Hutao.Win32.UI.Shell;
[SupportedOSPlatform("windows6.0.6000")]
[Guid("947AAB5F-0A5C-4C13-B4D6-4BF7836FC9F8")]
internal unsafe struct IFileOperation
{
public readonly Vftbl* ThisPtr;
internal static unsafe ref readonly Guid IID
{
get
{
ReadOnlySpan<byte> data = [0x5F, 0xAB, 0x7A, 0x94, 0x5C, 0x0A, 0x13, 0x4C, 0xB4, 0xD6, 0x4B, 0xF7, 0x83, 0x6F, 0xC9, 0xF8];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public unsafe HRESULT QueryInterface<TInterface>(ref readonly Guid riid, out TInterface* pvObject)
where TInterface : unmanaged
{
fixed (Guid* riid2 = &riid)
{
fixed (TInterface** ppvObject = &pvObject)
{
return ThisPtr->IUnknownVftbl.QueryInterface((IUnknown*)Unsafe.AsPointer(ref this), riid2, (void**)ppvObject);
}
}
}
public uint AddRef()
{
return ThisPtr->IUnknownVftbl.AddRef((IUnknown*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl
{
internal readonly IUnknown.Vftbl IUnknownVftbl;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IFileOperationProgressSink*, uint*, HRESULT> Advise;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, uint, HRESULT> Unadvise;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, FILEOPERATION_FLAGS, HRESULT> SetOperationFlags;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, PCWSTR, HRESULT> SetProgressMessage;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IOperationsProgressDialog*, HRESULT> SetProgressDialog;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IPropertyChangeArray*, HRESULT> SetProperties;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, HWND, HRESULT> SetOwnerWindow;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IShellItem*, HRESULT> ApplyPropertiesToItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IUnknown*, HRESULT> ApplyPropertiesToItems;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IShellItem*, PCWSTR, IFileOperationProgressSink*, HRESULT> RenameItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IUnknown*, PCWSTR, HRESULT> RenameItems;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IShellItem*, IShellItem*, PCWSTR, IFileOperationProgressSink*, HRESULT> MoveItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IUnknown*, IShellItem*, HRESULT> MoveItems;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IShellItem*, IShellItem*, PCWSTR, IFileOperationProgressSink*, HRESULT> CopyItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IUnknown*, IShellItem*, HRESULT> CopyItems;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IShellItem*, IFileOperationProgressSink*, HRESULT> DeleteItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IUnknown*, HRESULT> DeleteItems;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, IShellItem*, uint, PCWSTR, PCWSTR, IFileOperationProgressSink*, HRESULT> NewItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, HRESULT> PerformOperations;
internal readonly delegate* unmanaged[Stdcall]<IFileOperation*, BOOL*, HRESULT> GetAnyOperationsAborted;
}
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Snap.Hutao.Win32.UI.Shell;
[SupportedOSPlatform("windows6.0.6000")]
[Guid("04B0F1A7-9490-44BC-96E1-4296A31252E2")]
internal unsafe struct IFileOperationProgressSink
{
public readonly Vftbl* ThisPtr;
internal static unsafe ref readonly Guid IID
{
get
{
ReadOnlySpan<byte> data = [0xA7, 0xF1, 0xB0, 0x04, 0x90, 0x94, 0xBC, 0x44, 0x96, 0xE1, 0x42, 0x96, 0xA3, 0x12, 0x52, 0xE2];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public unsafe HRESULT QueryInterface<TInterface>(ref readonly Guid riid, out TInterface* pvObject)
where TInterface : unmanaged
{
fixed (Guid* riid2 = &riid)
{
fixed (TInterface** ppvObject = &pvObject)
{
return ThisPtr->IUnknownVftbl.QueryInterface((IUnknown*)Unsafe.AsPointer(ref this), riid2, (void**)ppvObject);
}
}
}
public uint AddRef()
{
return ThisPtr->IUnknownVftbl.AddRef((IUnknown*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl
{
internal readonly IUnknown.Vftbl IUnknownVftbl;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, HRESULT> StartOperations;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, HRESULT, HRESULT> FinishOperations;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, PCWSTR, HRESULT> PreRenameItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, PCWSTR, HRESULT, IShellItem*, HRESULT> PostRenameItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, IShellItem*, PCWSTR, HRESULT> PreMoveItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, IShellItem*, PCWSTR, HRESULT, IShellItem*, HRESULT> PostMoveItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, IShellItem*, PCWSTR, HRESULT> PreCopyItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, IShellItem*, PCWSTR, HRESULT, IShellItem*, HRESULT> PostCopyItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, HRESULT> PreDeleteItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, HRESULT, IShellItem*, HRESULT> PostDeleteItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, PCWSTR, HRESULT> PreNewItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, IShellItem*, PCWSTR, PCWSTR, uint, HRESULT, IShellItem*, HRESULT> PostNewItem;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, uint, uint, HRESULT> UpdateProgress;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, HRESULT> ResetTimer;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, HRESULT> PauseTimer;
internal readonly delegate* unmanaged[Stdcall]<IFileOperationProgressSink*, HRESULT> ResumeTimer;
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Snap.Hutao.Win32.UI.Shell;
[SupportedOSPlatform("windows6.0.6000")]
[Guid("0C9FB851-E5C9-43EB-A370-F0677B13874C")]
internal unsafe struct IOperationsProgressDialog
{
public readonly Vftbl* ThisPtr;
internal static unsafe ref readonly Guid IID
{
get
{
ReadOnlySpan<byte> data = [0x51, 0xB8, 0x9F, 0x0C, 0xC9, 0xE5, 0xEB, 0x43, 0xA3, 0x70, 0xF0, 0x67, 0x7B, 0x13, 0x87, 0x4C];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public unsafe HRESULT QueryInterface<TInterface>(ref readonly Guid riid, out TInterface* pvObject)
where TInterface : unmanaged
{
fixed (Guid* riid2 = &riid)
{
fixed (TInterface** ppvObject = &pvObject)
{
return ThisPtr->IUnknownVftbl.QueryInterface((IUnknown*)Unsafe.AsPointer(ref this), riid2, (void**)ppvObject);
}
}
}
public uint AddRef()
{
return ThisPtr->IUnknownVftbl.AddRef((IUnknown*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl
{
internal readonly IUnknown.Vftbl IUnknownVftbl;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, HWND, uint, HRESULT> StartProgressDialog;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, HRESULT> StopProgressDialog;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, SPACTION, HRESULT> SetOperation;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, uint, HRESULT> SetMode;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, ulong, ulong, ulong, ulong, ulong, ulong, HRESULT> UpdateProgress;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, IShellItem*, IShellItem*, IShellItem*, HRESULT> UpdateLocations;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, HRESULT> ResetTimer;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, HRESULT> PauseTimer;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, HRESULT> ResumeTimer;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, ulong*, ulong*, HRESULT> GetMilliseconds;
internal readonly delegate* unmanaged[Stdcall]<IOperationsProgressDialog*, PDOPSTATUS*, HRESULT> GetOperationStatus;
}
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
[SupportedOSPlatform("windows6.0.6000")]
[Guid("FC0CA0A7-C316-4FD2-9031-3E628E6D4F23")]
internal unsafe struct IObjectWithPropertyKey
{
public readonly Vftbl* ThisPtr;
internal static unsafe ref readonly Guid IID
{
get
{
ReadOnlySpan<byte> data = [0xA7, 0xA0, 0x0C, 0xFC, 0x16, 0xC3, 0xD2, 0x4F, 0x90, 0x31, 0x3E, 0x62, 0x8E, 0x6D, 0x4F, 0x23];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public unsafe HRESULT QueryInterface<TInterface>(ref readonly Guid riid, out TInterface* pvObject)
where TInterface : unmanaged
{
fixed (Guid* riid2 = &riid)
{
fixed (TInterface** ppvObject = &pvObject)
{
return ThisPtr->IUnknownVftbl.QueryInterface((IUnknown*)Unsafe.AsPointer(ref this), riid2, (void**)ppvObject);
}
}
}
public uint AddRef()
{
return ThisPtr->IUnknownVftbl.AddRef((IUnknown*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl
{
internal readonly IUnknown.Vftbl IUnknownVftbl;
internal readonly delegate* unmanaged[Stdcall]<IObjectWithPropertyKey*, PROPERTYKEY*, HRESULT> SetPropertyKey;
internal readonly delegate* unmanaged[Stdcall]<IObjectWithPropertyKey*, PROPERTYKEY*, HRESULT> GetPropertyKey;
}
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.System.Com.StructuredStorage;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
[SupportedOSPlatform("windows6.0.6000")]
[Guid("F917BC8A-1BBA-4478-A245-1BDE03EB9431")]
internal unsafe struct IPropertyChange
{
public readonly Vftbl* ThisPtr;
internal static unsafe ref readonly Guid IID
{
get
{
ReadOnlySpan<byte> data = [0x8A, 0xBC, 0x17, 0xF9, 0xBA, 0x1B, 0x78, 0x44, 0xA2, 0x45, 0x1B, 0xDE, 0x03, 0xEB, 0x94, 0x31];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public unsafe HRESULT QueryInterface<TInterface>(ref readonly Guid riid, out TInterface* pvObject)
where TInterface : unmanaged
{
fixed (Guid* riid2 = &riid)
{
fixed (TInterface** ppvObject = &pvObject)
{
return ThisPtr->IObjectWithPropertyKeyVftbl.IUnknownVftbl.QueryInterface((IUnknown*)Unsafe.AsPointer(ref this), riid2, (void**)ppvObject);
}
}
}
public uint AddRef()
{
return ThisPtr->IObjectWithPropertyKeyVftbl.IUnknownVftbl.AddRef((IUnknown*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ThisPtr->IObjectWithPropertyKeyVftbl.IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl
{
internal readonly IObjectWithPropertyKey.Vftbl IObjectWithPropertyKeyVftbl;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChange*, PROPVARIANT*, PROPVARIANT*, HRESULT> ApplyToPropVariant;
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
[SupportedOSPlatform("windows6.0.6000")]
[Guid("380F5CAD-1B5E-42F2-805D-637FD392D31E")]
internal unsafe struct IPropertyChangeArray
{
public readonly Vftbl* ThisPtr;
internal static unsafe ref readonly Guid IID
{
get
{
ReadOnlySpan<byte> data = [0xAD, 0x5C, 0x0F, 0x38, 0x5E, 0x1B, 0xF2, 0x42, 0x80, 0x5D, 0x63, 0x7F, 0xD3, 0x92, 0xD3, 0x1E];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public unsafe HRESULT QueryInterface<TInterface>(ref readonly Guid riid, out TInterface* pvObject)
where TInterface : unmanaged
{
fixed (Guid* riid2 = &riid)
{
fixed (TInterface** ppvObject = &pvObject)
{
return ThisPtr->IUnknownVftbl.QueryInterface((IUnknown*)Unsafe.AsPointer(ref this), riid2, (void**)ppvObject);
}
}
}
public uint AddRef()
{
return ThisPtr->IUnknownVftbl.AddRef((IUnknown*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl
{
internal readonly IUnknown.Vftbl IUnknownVftbl;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, uint*, HRESULT> GetCount;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, uint, Guid*, void**, HRESULT> GetAt;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, uint, IPropertyChange*, HRESULT> InsertAt;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, IPropertyChange*, HRESULT> Append;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, IPropertyChange*, HRESULT> AppendOrReplace;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, uint, HRESULT> RemoveAt;
internal readonly delegate* unmanaged[Stdcall]<IPropertyChangeArray*, PROPERTYKEY*, HRESULT> IsKeyInArray;
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
internal enum PDOPSTATUS
{
PDOPS_RUNNING = 1,
PDOPS_PAUSED = 2,
PDOPS_CANCELLED = 3,
PDOPS_STOPPED = 4,
PDOPS_ERRORS = 5,
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell.PropertiesSystem;
[SuppressMessage("", "SA1307")]
internal struct PROPERTYKEY
{
public Guid fmtid;
public uint pid;
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell;
internal enum SPACTION
{
SPACTION_NONE = 0,
SPACTION_MOVING = 1,
SPACTION_COPYING = 2,
SPACTION_RECYCLING = 3,
SPACTION_APPLYINGATTRIBS = 4,
SPACTION_DOWNLOADING = 5,
SPACTION_SEARCHING_INTERNET = 6,
SPACTION_CALCULATING = 7,
SPACTION_UPLOADING = 8,
SPACTION_SEARCHING_FILES = 9,
SPACTION_DELETING = 10,
SPACTION_RENAMING = 11,
SPACTION_FORMATTING = 12,
SPACTION_COPY_MOVING = 13,
}