diff --git a/res/HutaoIconSourceTransparentBackgroundGradient1.png b/res/HutaoIconSourceTransparentBackgroundGradient1.png
new file mode 100644
index 00000000..5273dfcb
Binary files /dev/null and b/res/HutaoIconSourceTransparentBackgroundGradient1.png differ
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs
index cd1a79b7..6006c3b8 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs
@@ -4,50 +4,63 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-using System;
+using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
+using System.Threading;
namespace Snap.Hutao.SourceGeneration.DependencyInjection;
-///
-/// 注入HttpClient代码生成器
-/// 旨在使用源生成器提高注入效率
-/// 防止在运行时动态查找注入类型
-///
-[Generator]
-internal sealed class HttpClientGenerator : ISourceGenerator
+[Generator(LanguageNames.CSharp)]
+internal sealed class HttpClientGenerator : IIncrementalGenerator
{
+ private const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute";
+
private const string DefaultName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.Default";
private const string XRpcName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.XRpc";
private const string XRpc2Name = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.XRpc2";
private const string XRpc3Name = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.XRpc3";
private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute";
- private const string DynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute";
+ private const string UseDynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute";
+ private const string CRLF = "\r\n";
- ///
- public void Initialize(GeneratorInitializationContext context)
+ private static readonly DiagnosticDescriptor invalidConfigurationDescriptor = new("SH100", "无效的 HttpClientConfiguration", "尚未支持生成 {0} 配置", "Quality", DiagnosticSeverity.Error, true);
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
- // Register a syntax receiver that will be created for each generation pass
- context.RegisterForSyntaxNotifications(() => new HttpClientSyntaxContextReceiver());
+ IncrementalValueProvider> injectionClasses =
+ context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
+ .Where(GeneratorSyntaxContext2.NotNull)!
+ .Collect();
+
+ context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddHttpClientsImplementation);
}
- ///
- public void Execute(GeneratorExecutionContext context)
+ private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
- // retrieve the populated receiver
- if (context.SyntaxContextReceiver is not HttpClientSyntaxContextReceiver receiver)
+ return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0;
+ }
+
+ private static GeneratorSyntaxContext2 HttpClientClass(GeneratorSyntaxContext context, CancellationToken token)
+ {
+ if (context.SemanticModel.GetDeclaredSymbol(context.Node, token) is INamedTypeSymbol classSymbol)
{
- return;
+ ImmutableArray attributes = classSymbol.GetAttributes();
+ if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
+ {
+ return new(context, classSymbol, attributes);
+ }
}
- StringBuilder sourceCodeBuilder = new();
+ return default;
+ }
- sourceCodeBuilder.Append($$"""
+ private static void GenerateAddHttpClientsImplementation(SourceProductionContext context, ImmutableArray context2s)
+ {
+ StringBuilder sourceBuilder = new StringBuilder().Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
@@ -64,45 +77,40 @@ internal sealed class HttpClientGenerator : ISourceGenerator
{
""");
- FillWithHttpClients(receiver, sourceCodeBuilder);
+ FillUpWithAddHttpClient(sourceBuilder, context, context2s);
- sourceCodeBuilder.Append("""
+ sourceBuilder.Append("""
return services;
}
}
""");
- context.AddSource("IocHttpClientConfiguration.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
+ context.AddSource("IocHttpClientConfiguration.g.cs", sourceBuilder.ToString());
}
- private static void FillWithHttpClients(HttpClientSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
+ private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray contexts)
{
List lines = new();
StringBuilder lineBuilder = new();
- foreach (INamedTypeSymbol classSymbol in receiver.Classes)
+ foreach (GeneratorSyntaxContext2 context in contexts)
{
- lineBuilder.Clear().Append("\r\n");
+ lineBuilder.Clear().Append(CRLF);
lineBuilder.Append(@" services.AddHttpClient<");
- AttributeData httpClientInfo = classSymbol
- .GetAttributes()
- .Single(attr => attr.AttributeClass!.ToDisplayString() == HttpClientSyntaxContextReceiver.AttributeName);
- ImmutableArray arguments = httpClientInfo.ConstructorArguments;
+ AttributeData httpClientData = context.SingleAttributeWithName(AttributeName);
+ ImmutableArray arguments = httpClientData.ConstructorArguments;
if (arguments.Length == 2)
{
- TypedConstant interfaceType = arguments[1];
- lineBuilder.Append($"{interfaceType.Value}, ");
+ lineBuilder.Append($"{arguments[1].Value}, ");
}
- TypedConstant configuration = arguments[0];
+ lineBuilder.Append($"{context.Symbol.ToDisplayString()}>(");
- lineBuilder.Append($"{classSymbol.ToDisplayString()}>(");
-
- string injectAsName = configuration.ToCSharpString();
- switch (injectAsName)
+ string configurationName = arguments[0].ToCSharpString();
+ switch (configurationName)
{
case DefaultName:
lineBuilder.Append("DefaultConfiguration)");
@@ -117,16 +125,13 @@ internal sealed class HttpClientGenerator : ISourceGenerator
lineBuilder.Append("XRpc3Configuration)");
break;
default:
- throw new InvalidOperationException($"非法的 HttpClientConfiguration 值: [{injectAsName}]");
+ production.ReportDiagnostic(Diagnostic.Create(invalidConfigurationDescriptor, null, configurationName));
+ break;
}
- AttributeData? handlerInfo = classSymbol
- .GetAttributes()
- .SingleOrDefault(attr => attr.AttributeClass!.ToDisplayString() == PrimaryHttpMessageHandlerAttributeName);
-
- if (handlerInfo != null)
+ if (context.SingleOrDefaultAttributeWithName(PrimaryHttpMessageHandlerAttributeName) is AttributeData handlerData)
{
- ImmutableArray> properties = handlerInfo.NamedArguments;
+ ImmutableArray> properties = handlerData.NamedArguments;
lineBuilder.Append(@".ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() {");
foreach (KeyValuePair property in properties)
@@ -141,7 +146,7 @@ internal sealed class HttpClientGenerator : ISourceGenerator
lineBuilder.Append(" })");
}
- if (classSymbol.GetAttributes().Any(attr => attr.AttributeClass!.ToDisplayString() == DynamicSecretAttributeName))
+ if (context.HasAttributeWithName(UseDynamicSecretAttributeName))
{
lineBuilder.Append(".AddHttpMessageHandler()");
}
@@ -153,37 +158,7 @@ internal sealed class HttpClientGenerator : ISourceGenerator
foreach (string line in lines.OrderBy(x => x))
{
- sourceCodeBuilder.Append(line);
- }
- }
-
- private class HttpClientSyntaxContextReceiver : ISyntaxContextReceiver
- {
- ///
- /// 注入特性的名称
- ///
- public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute";
-
- ///
- /// 所有需要注入的类型符号
- ///
- public List Classes { get; } = new();
-
- ///
- public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
- {
- // any class with at least one attribute is a candidate for injection generation
- if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
- {
- // get as named type symbol
- if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
- {
- if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
- {
- Classes.Add(classSymbol);
- }
- }
- }
+ sourceBuilder.Append(line);
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs
index 89cf0b4a..567fbd7c 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs
@@ -4,46 +4,58 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-using System;
+using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
+using System.Threading;
namespace Snap.Hutao.SourceGeneration.DependencyInjection;
-///
-/// 注入代码生成器
-/// 旨在使用源生成器提高注入效率
-/// 防止在运行时动态查找注入类型
-///
-[Generator]
-internal sealed class InjectionGenerator : ISourceGenerator
+[Generator(LanguageNames.CSharp)]
+internal sealed class InjectionGenerator : IIncrementalGenerator
{
+ private const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
private const string CRLF = "\r\n";
- ///
- public void Initialize(GeneratorInitializationContext context)
+ private static readonly DiagnosticDescriptor invalidInjectionDescriptor = new("SH101", "无效的 InjectAs 枚举值", "尚未支持生成 {0} 配置", "Quality", DiagnosticSeverity.Error, true);
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
- // Register a syntax receiver that will be created for each generation pass
- context.RegisterForSyntaxNotifications(() => new InjectionSyntaxContextReceiver());
+ IncrementalValueProvider> injectionClasses =
+ context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
+ .Where(GeneratorSyntaxContext2.NotNull)!
+ .Collect();
+
+ context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddInjectionsImplementation);
}
- ///
- public void Execute(GeneratorExecutionContext context)
+ private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
- // retrieve the populated receiver
- if (context.SyntaxContextReceiver is not InjectionSyntaxContextReceiver receiver)
+ return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0;
+ }
+
+ private static GeneratorSyntaxContext2 HttpClientClass(GeneratorSyntaxContext context, CancellationToken token)
+ {
+ if (context.SemanticModel.GetDeclaredSymbol(context.Node, token) is INamedTypeSymbol classSymbol)
{
- return;
+ ImmutableArray attributes = classSymbol.GetAttributes();
+ if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
+ {
+ return new(context, classSymbol, attributes);
+ }
}
- StringBuilder sourceCodeBuilder = new();
- sourceCodeBuilder.Append($$"""
+ return default;
+ }
+
+ private static void GenerateAddInjectionsImplementation(SourceProductionContext context, ImmutableArray context2s)
+ {
+ StringBuilder sourceBuilder = new StringBuilder().Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
@@ -57,36 +69,30 @@ internal sealed class InjectionGenerator : ISourceGenerator
{
""");
- FillWithInjectionServices(receiver, sourceCodeBuilder);
- sourceCodeBuilder.Append("""
+ FillUpWithAddServices(sourceBuilder, context, context2s);
+ sourceBuilder.Append("""
return services;
}
}
""");
- context.AddSource("ServiceCollectionExtension.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
+ context.AddSource("ServiceCollectionExtension.g.cs", sourceBuilder.ToString());
}
- private static void FillWithInjectionServices(InjectionSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
+ private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray contexts)
{
List lines = new();
StringBuilder lineBuilder = new();
- foreach (INamedTypeSymbol classSymbol in receiver.Classes)
+ foreach (GeneratorSyntaxContext2 context in contexts)
{
- AttributeData injectionInfo = classSymbol
- .GetAttributes()
- .Single(attr => attr.AttributeClass!.ToDisplayString() == InjectionSyntaxContextReceiver.AttributeName);
-
- lineBuilder
- .Clear()
- .Append(CRLF);
+ lineBuilder.Clear().Append(CRLF);
+ AttributeData injectionInfo = context.SingleAttributeWithName(AttributeName);
ImmutableArray arguments = injectionInfo.ConstructorArguments;
- TypedConstant injectAs = arguments[0];
- string injectAsName = injectAs.ToCSharpString();
+ string injectAsName = arguments[0].ToCSharpString();
switch (injectAsName)
{
case InjectAsSingletonName:
@@ -99,53 +105,23 @@ internal sealed class InjectionGenerator : ISourceGenerator
lineBuilder.Append(@" services.AddScoped<");
break;
default:
- throw new InvalidOperationException($"非法的 InjectAs 值: [{injectAsName}]");
+ production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, null, injectAsName));
+ break;
}
if (arguments.Length == 2)
{
- TypedConstant interfaceType = arguments[1];
- lineBuilder.Append($"{interfaceType.Value}, ");
+ lineBuilder.Append($"{arguments[1].Value}, ");
}
- lineBuilder.Append($"{classSymbol.ToDisplayString()}>();");
+ lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();");
lines.Add(lineBuilder.ToString());
}
foreach (string line in lines.OrderBy(x => x))
{
- sourceCodeBuilder.Append(line);
- }
- }
-
- private class InjectionSyntaxContextReceiver : ISyntaxContextReceiver
- {
- ///
- /// 注入特性的名称
- ///
- public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
-
- ///
- /// 所有需要注入的类型符号
- ///
- public List Classes { get; } = new();
-
- ///
- public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
- {
- // any class with at least one attribute is a candidate for injection generation
- if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
- {
- // get as named type symbol
- if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
- {
- if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
- {
- Classes.Add(classSymbol);
- }
- }
- }
+ sourceBuilder.Append(line);
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs
index 6502c517..7d458146 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs
@@ -5,4 +5,4 @@
using System.Diagnostics.CodeAnalysis;
-[assembly: SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:启用分析器发布跟踪", Justification = "<挂起>", Scope = "member", Target = "~F:Snap.Hutao.SourceGeneration.TypeInternalAnalyzer.typeInternalDescriptor")]
+[assembly: SuppressMessage("", "RS2008")]
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs
new file mode 100644
index 00000000..9f2835dc
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs
@@ -0,0 +1,44 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Snap.Hutao.SourceGeneration.Primitive;
+
+internal readonly struct GeneratorSyntaxContext2
+{
+ public readonly GeneratorSyntaxContext Context;
+ public readonly INamedTypeSymbol Symbol;
+ public readonly ImmutableArray Attributes;
+ public readonly bool HasValue = false;
+
+ public GeneratorSyntaxContext2(GeneratorSyntaxContext context, INamedTypeSymbol symbol, ImmutableArray attributes)
+ {
+ Context = context;
+ Symbol = symbol;
+ Attributes = attributes;
+ HasValue = true;
+ }
+
+ public static bool NotNull(GeneratorSyntaxContext2 context)
+ {
+ return context.HasValue;
+ }
+
+ public bool HasAttributeWithName(string name)
+ {
+ return Attributes.Any(attr => attr.AttributeClass!.ToDisplayString() == name);
+ }
+
+ public AttributeData SingleAttributeWithName(string name)
+ {
+ return Attributes.Single(attribute => attribute.AttributeClass!.ToDisplayString() == name);
+ }
+
+ public AttributeData? SingleOrDefaultAttributeWithName(string name)
+ {
+ return Attributes.SingleOrDefault(attribute => attribute.AttributeClass!.ToDisplayString() == name);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/ReliquaryWeightConfigurationGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/ReliquaryWeightConfigurationGenerator.cs
index 7ea7bceb..cc2e1d68 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/ReliquaryWeightConfigurationGenerator.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/ReliquaryWeightConfigurationGenerator.cs
@@ -1,7 +1,6 @@
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Text;
-using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
@@ -9,29 +8,32 @@ using System.Text;
namespace Snap.Hutao.SourceGeneration;
-[Generator]
-internal sealed class ReliquaryWeightConfigurationGenerator : ISourceGenerator
+[Generator(LanguageNames.CSharp)]
+internal sealed class ReliquaryWeightConfigurationGenerator : IIncrementalGenerator
{
- private const string ReliquaryWeightConfigurationFileName = "ReliquaryWeightConfiguration.json";
+ private const string FileName = "ReliquaryWeightConfiguration.json";
- public void Initialize(GeneratorInitializationContext context)
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
+ IncrementalValueProvider> provider = context.AdditionalTextsProvider.Where(MatchFileName).Collect();
+
+ context.RegisterSourceOutput(provider, GenerateReliquaryWeightConfiguration);
}
- public void Execute(GeneratorExecutionContext context)
+ private static bool MatchFileName(AdditionalText text)
{
- try
- {
+ return Path.GetFileName(text.Path) == FileName;
+ }
- AdditionalText configurationJsonFile = context.AdditionalFiles
- .First(af => Path.GetFileName(af.Path) == ReliquaryWeightConfigurationFileName);
+ private static void GenerateReliquaryWeightConfiguration(SourceProductionContext context,ImmutableArray additionalTexts)
+ {
+ AdditionalText jsonFile = additionalTexts.Single();
- string configurationJson = configurationJsonFile.GetText(context.CancellationToken)!.ToString();
+ string configurationJson = jsonFile.GetText(context.CancellationToken)!.ToString();
Dictionary metadataMap =
JsonParser.FromJson>(configurationJson)!;
- StringBuilder sourceCodeBuilder = new();
- sourceCodeBuilder.Append($$"""
+ StringBuilder sourceBuilder = new StringBuilder().Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
@@ -59,24 +61,18 @@ internal sealed class ReliquaryWeightConfigurationGenerator : ISourceGenerator
foreach (KeyValuePair kvp in metadataMap.OrderBy(kvp => kvp.Key))
{
- AppendAffixWeight(sourceCodeBuilder, kvp.Key, kvp.Value);
+ AppendAffixWeight(sourceBuilder, kvp.Key, kvp.Value);
}
- sourceCodeBuilder.Append($$"""
+ sourceBuilder.Append($$"""
};
}
""");
- context.AddSource("ReliquaryWeightConfiguration.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
-
- }
- catch (Exception ex)
- {
- context.AddSource("ReliquaryWeightConfiguration.g.cs", ex.ToString());
- }
+ context.AddSource("ReliquaryWeightConfiguration.g.cs", sourceBuilder.ToString());
}
- private void AppendAffixWeight(StringBuilder builder, string id, ReliquaryWeightConfigurationMetadata metadata)
+ private static void AppendAffixWeight(StringBuilder builder, string id, ReliquaryWeightConfigurationMetadata metadata)
{
StringBuilder lineBuilder = new StringBuilder()
.Append(" new AffixWeight(").Append(id).Append(',')
@@ -97,7 +93,6 @@ internal sealed class ReliquaryWeightConfigurationGenerator : ISourceGenerator
lineBuilder.Append(',');
-
builder.AppendLine(lineBuilder.ToString());
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
index 16ed0ca9..58d9be50 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
@@ -61,4 +61,9 @@ internal static class SettingKeys
/// 静态资源合约V4 刷新 AvatarIcon
///
public const string StaticResourceV4Contract = "StaticResourceV4Contract";
+
+ ///
+ /// 静态资源合约V5 刷新 AvatarIcon
+ ///
+ public const string StaticResourceV5Contract = "StaticResourceV5Contract";
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/StaticResource.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/StaticResource.cs
index 56c2201b..187b40c6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/StaticResource.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/StaticResource.cs
@@ -53,5 +53,6 @@ internal static class StaticResource
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, state);
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, state);
LocalSetting.Set(SettingKeys.StaticResourceV4Contract, state);
+ LocalSetting.Set(SettingKeys.StaticResourceV5Contract, state);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/HutaoIconSourceTransparentBackgroundGradient1.png b/src/Snap.Hutao/Snap.Hutao/Resource/HutaoIconSourceTransparentBackgroundGradient1.png
new file mode 100644
index 00000000..5273dfcb
Binary files /dev/null and b/src/Snap.Hutao/Snap.Hutao/Resource/HutaoIconSourceTransparentBackgroundGradient1.png differ
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
index 5e2f2d99..2d444890 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
@@ -1575,9 +1575,9 @@ namespace Snap.Hutao.Resource.Localization {
///
/// 查找类似 保底 的本地化字符串。
///
- internal static string ViewControlStatisticsCardGuarenteeText {
+ internal static string ViewControlStatisticsCardGuaranteeText {
get {
- return ResourceManager.GetString("ViewControlStatisticsCardGuarenteeText", resourceCulture);
+ return ResourceManager.GetString("ViewControlStatisticsCardGuaranteeText", resourceCulture);
}
}
@@ -3516,6 +3516,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
+ ///
+ /// 查找类似 设置 的本地化字符串。
+ ///
+ internal static string ViewPageHomeLaunchGameSettingAction {
+ get {
+ return ResourceManager.GetString("ViewPageHomeLaunchGameSettingAction", resourceCulture);
+ }
+ }
+
///
/// 查找类似 详情 的本地化字符串。
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
index fc9daadd..e5f887dd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
@@ -609,7 +609,7 @@
三星
-
+
保底
@@ -1959,4 +1959,7 @@
突破后
+
+ 设置
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
index 6e8764cc..eb0b1f87 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
@@ -142,9 +142,9 @@ internal sealed class TypedWishSummaryBuilder
MaxOrangePull = maxOrangePullTracker,
MinOrangePull = minOrangePullTracker,
LastOrangePull = lastOrangePullTracker,
- GuarenteeOrangeThreshold = guarenteeOrangeThreshold,
+ GuaranteeOrangeThreshold = guarenteeOrangeThreshold,
LastPurplePull = lastPurplePullTracker,
- GuarenteePurpleThreshold = guarenteePurpleThreshold,
+ GuaranteePurpleThreshold = guarenteePurpleThreshold,
TotalOrangePull = totalOrangePullTracker,
TotalPurplePull = totalPurplePullTracker,
TotalBluePull = totalBluePullTracker,
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
index 04f01d78..9b237a93 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
@@ -407,4 +407,41 @@ internal sealed class GachaLogService : IGachaLogService
appDbContext.GachaItems.AddRangeAndSave(itemsToAdd);
}
}
-}
\ No newline at end of file
+}
+
+///
+/// 祈愿记录导出服务
+///
+internal sealed class GachaLogExportService
+{
+ AppDbContext appDbContext;
+
+ ///
+ public Task ExportToUIGFAsync(GachaArchive archive)
+ {
+ List list = appDbContext.GachaItems
+ .Where(i => i.ArchiveId == archive.InnerId)
+ .AsEnumerable()
+ .Select(i => i.ToUIGFItem(GetNameQualityByItemId(i.ItemId)))
+ .ToList();
+
+ UIGF uigf = new()
+ {
+ Info = UIGFInfo.Create(archive.Uid),
+ List = list,
+ };
+
+ return Task.FromResult(uigf);
+ }
+
+ private INameQuality GetNameQualityByItemId(int id)
+ {
+ int place = id.Place();
+ return place switch
+ {
+ 8 => idAvatarMap![id],
+ 5 => idWeaponMap![id],
+ _ => throw Must.NeverHappen($"Id places: {place}"),
+ };
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
index 9c665a73..3a17830d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
@@ -289,6 +289,7 @@ internal sealed class GameService : IGameService
string gamePath = appOptions.GamePath;
if (string.IsNullOrWhiteSpace(gamePath))
{
+ // TODO: throw exception
return;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs
index 5a23cca4..d5f552e2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs
@@ -44,6 +44,7 @@ internal sealed class LaunchOptions : DbStoreOptions
primaryScreenHeight = primaryRect.Height;
// This list can't use foreach
+ // https://github.com/microsoft/microsoft-ui-xaml/issues/6454
IReadOnlyList displayAreas = DisplayArea.FindAll();
for (int i = 0; i < displayAreas.Count; i++)
{
@@ -148,7 +149,13 @@ internal sealed class LaunchOptions : DbStoreOptions
public NameValue Monitor
{
get => GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[int.Parse(index) - 1], Monitors[0]);
- set => SetOption(ref monitor, SettingEntry.LaunchMonitor, value, selected => selected?.Value.ToString() ?? "1");
+ set
+ {
+ if (value != null)
+ {
+ SetOption(ref monitor, SettingEntry.LaunchMonitor, value, selected => selected.Value.ToString() ?? "1");
+ }
+ }
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index c01c9736..29f73ff5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -78,6 +78,7 @@
+
@@ -199,6 +200,7 @@
+
@@ -263,7 +265,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+ Text="{Binding UserGameRole}"
+ TextTrimming="CharacterEllipsis"
+ TextWrapping="NoWrap"/>
@@ -179,214 +187,159 @@
-
+ Spacing="12">
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+ Height="40"
+ Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
+ IsIndeterminate="False"
+ Maximum="{Binding DailyNote.MaxResin, Mode=OneWay}"
+ Value="{Binding DailyNote.CurrentResin, Mode=OneWay}"/>
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+ Height="40"
+ Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
+ IsIndeterminate="False"
+ Maximum="{Binding DailyNote.MaxHomeCoin, Mode=OneWay}"
+ Value="{Binding DailyNote.CurrentHomeCoin, Mode=OneWay}"/>
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+ Height="40"
+ Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
+ IsIndeterminate="False"
+ Maximum="{Binding DailyNote.TotalTaskNum, Mode=OneWay}"
+ Value="{Binding DailyNote.FinishedTaskNum, Mode=OneWay}"/>
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+ Height="40"
+ Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
+ IsIndeterminate="False"
+ Maximum="{Binding DailyNote.ResinDiscountNumLimit, Mode=OneWay}"
+ Value="{Binding DailyNote.ResinDiscountUsedNum, Mode=OneWay}"/>
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+ Height="40"
+ Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
+ IsIndeterminate="False"
+ Maximum="604800"
+ Value="{Binding DailyNote.Transformer.RecoveryTime.TotalSeconds, Mode=OneWay}"/>
+
+
+
+
-
-
+
+
+
-
+
@@ -463,8 +416,7 @@
-
+
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
index cefdbc66..55fcc5d0 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
@@ -253,6 +253,16 @@
DataContext="{Binding GameResource.PreDownloadGame.Latest, Mode=OneWay}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameResourcePreDownloadHeader}"
Visibility="{Binding FallbackValue={StaticResource VisibilityCollapsed}, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
+
+
+
+
+
+
+
-
+
@@ -47,7 +51,6 @@
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs
index 138efb94..39904b18 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs
@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Snap.Hutao.Service.Navigation;
namespace Snap.Hutao.ViewModel.Abstraction;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModelSlim.cs
new file mode 100644
index 00000000..f3bd113a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModelSlim.cs
@@ -0,0 +1,60 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Snap.Hutao.Service.Navigation;
+
+namespace Snap.Hutao.ViewModel.Abstraction;
+
+///
+/// 简化的视图模型抽象类
+///
+/// 页面类型
+internal abstract class ViewModelSlim : ObservableObject
+ where TPage : Microsoft.UI.Xaml.Controls.Page
+{
+ ///
+ /// 构造一个新的简化的视图模型抽象类
+ ///
+ /// 服务提供器
+ public ViewModelSlim(IServiceProvider serviceProvider)
+ {
+ ServiceProvider = serviceProvider;
+
+ OpenUICommand = new AsyncRelayCommand(OpenUIAsync);
+ NavigateCommand = new RelayCommand(Navigate);
+ }
+
+ ///
+ /// 打开页面命令
+ ///
+ public ICommand OpenUICommand { get; }
+
+ ///
+ /// 导航命令
+ ///
+ public ICommand NavigateCommand { get; }
+
+ ///
+ /// 服务提供器
+ ///
+ protected IServiceProvider ServiceProvider { get; }
+
+ ///
+ /// 打开界面执行
+ ///
+ /// 任务
+ protected virtual Task OpenUIAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// 导航到指定的页面类型
+ ///
+ protected virtual void Navigate()
+ {
+ ServiceProvider.GetRequiredService().Navigate(INavigationAwaiter.Default, true);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
index bd039c8e..a79b24d3 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
@@ -20,20 +20,23 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
///
/// 构造一个公告视图模型
///
- /// 公告服务
- public AnnouncementViewModel(IAnnouncementService announcementService)
+ /// 服务提供器
+ public AnnouncementViewModel(IServiceProvider serviceProvider)
{
- this.announcementService = announcementService;
+ announcementService = serviceProvider.GetRequiredService();
+
+ LaunchGameViewModelSlim = serviceProvider.GetRequiredService();
}
///
/// 公告
///
- public AnnouncementWrapper? Announcement
- {
- get => announcement;
- set => SetProperty(ref announcement, value);
- }
+ public AnnouncementWrapper? Announcement { get => announcement; set => SetProperty(ref announcement, value); }
+
+ ///
+ /// 启动游戏视图模型
+ ///
+ public Game.LaunchGameViewModelSlim LaunchGameViewModelSlim { get; }
///
protected override async Task OpenUIAsync()
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
index 0a6ca61d..7b82d72d 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
@@ -332,7 +332,7 @@ internal sealed class GachaLogViewModel : Abstraction.ViewModel
/// 需要从主线程调用
///
/// 存档
- /// 强制刷新,即使Uid相同也刷新该Uid的记录
+ /// 强制刷新,即使Uid相同也刷新该 Uid 的记录
private void SetSelectedArchiveAndUpdateStatistics(GachaArchive? archive, bool forceUpdate = false)
{
bool changed = false;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModelSlim.cs
new file mode 100644
index 00000000..e691f1bd
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModelSlim.cs
@@ -0,0 +1,32 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.ViewModel.GachaLog;
+
+///
+/// 简化的祈愿记录视图模型
+///
+[Injection(InjectAs.Scoped)]
+internal sealed class GachaLogViewModelSlim : Abstraction.ViewModelSlim
+{
+ private List? statisticsList;
+
+ ///
+ /// 构造一个新的简化的祈愿记录视图模型
+ ///
+ /// 服务提供器
+ public GachaLogViewModelSlim(IServiceProvider serviceProvider)
+ : base(serviceProvider)
+ {
+ }
+
+ ///
+ /// 统计列表
+ ///
+ public List? StatisticsList { get => statisticsList; set => SetProperty(ref statisticsList, value); }
+
+ ///
+ protected override Task OpenUIAsync()
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaStatisticsSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaStatisticsSlim.cs
new file mode 100644
index 00000000..21b39696
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaStatisticsSlim.cs
@@ -0,0 +1,30 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.ViewModel.GachaLog;
+
+///
+/// 简化的祈愿统计
+///
+internal sealed class GachaStatisticsSlim
+{
+ ///
+ /// Uid
+ ///
+ public string Uid { get; set; } = default!;
+
+ ///
+ /// 角色活动
+ ///
+ public TypedWishSummary AvatarWish { get; set; } = default!;
+
+ ///
+ /// 神铸赋形
+ ///
+ public TypedWishSummary WeaponWish { get; set; } = default!;
+
+ ///
+ /// 奔行世间
+ ///
+ public TypedWishSummary StandardWish { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs
index 744a138f..da916d83 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs
@@ -26,24 +26,24 @@ internal sealed class TypedWishSummary : Wish
}
///
- /// 据上个五星抽数
+ /// 距上个五星抽数
///
public int LastOrangePull { get; set; }
///
- /// 据上个四星抽数
+ /// 距上个四星抽数
///
public int LastPurplePull { get; set; }
///
/// 五星保底阈值
///
- public int GuarenteeOrangeThreshold { get; set; }
+ public int GuaranteeOrangeThreshold { get; set; }
///
/// 四星保底阈值
///
- public int GuarenteePurpleThreshold { get; set; }
+ public int GuaranteePurpleThreshold { get; set; }
///
/// 五星格式化字符串
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummarySlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummarySlim.cs
new file mode 100644
index 00000000..bb205ac6
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummarySlim.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.ViewModel.GachaLog;
+
+///
+/// 简化的类型化的祈愿概览
+///
+internal sealed class TypedWishSummarySlim
+{
+ ///
+ /// 卡池名称
+ ///
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 距上个五星抽数
+ ///
+ public int LastOrangePull { get; set; }
+
+ ///
+ /// 距上个四星抽数
+ ///
+ public int LastPurplePull { get; set; }
+
+ ///
+ /// 五星保底阈值
+ ///
+ public int GuaranteeOrangeThreshold { get; set; }
+
+ ///
+ /// 四星保底阈值
+ ///
+ public int GuaranteePurpleThreshold { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs
index 8522e433..8d0dc901 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs
@@ -149,7 +149,9 @@ internal sealed class LaunchGameViewModel : Abstraction.ViewModel
///
protected override async Task OpenUIAsync()
{
- if (File.Exists(serviceProvider.GetRequiredService().GamePath))
+ IInfoBarService infoBarService = serviceProvider.GetRequiredService();
+
+ if (File.Exists(AppOptions.GamePath))
{
try
{
@@ -162,7 +164,7 @@ internal sealed class LaunchGameViewModel : Abstraction.ViewModel
}
else
{
- serviceProvider.GetRequiredService().Warning(SH.ViewModelLaunchGameMultiChannelReadFail);
+ infoBarService.Warning(SH.ViewModelLaunchGameMultiChannelReadFail);
}
ObservableCollection accounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(false);
@@ -186,7 +188,7 @@ internal sealed class LaunchGameViewModel : Abstraction.ViewModel
}
else
{
- serviceProvider.GetRequiredService().Warning(SH.ViewModelLaunchGamePathInvalid);
+ infoBarService.Warning(SH.ViewModelLaunchGamePathInvalid);
await ThreadHelper.SwitchToMainThreadAsync();
await serviceProvider.GetRequiredService()
.NavigateAsync(INavigationAwaiter.Default, true)
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs
new file mode 100644
index 00000000..0e29c2de
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs
@@ -0,0 +1,83 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.Input;
+using Snap.Hutao.Model.Entity;
+using Snap.Hutao.Service.Abstraction;
+using Snap.Hutao.Service.Game;
+using System.Collections.ObjectModel;
+
+namespace Snap.Hutao.ViewModel.Game;
+
+///
+/// 简化的启动游戏视图模型
+///
+[Injection(InjectAs.Scoped)]
+internal sealed class LaunchGameViewModelSlim : Abstraction.ViewModelSlim
+{
+ private readonly IGameService gameService;
+
+ private ObservableCollection? gameAccounts;
+ private GameAccount? selectedGameAccount;
+
+ ///
+ /// 构造一个新的简化的启动游戏视图模型
+ ///
+ /// 服务提供器
+ public LaunchGameViewModelSlim(IServiceProvider serviceProvider)
+ : base(serviceProvider)
+ {
+ gameService = serviceProvider.GetRequiredService();
+
+ LaunchCommand = new AsyncRelayCommand(LaunchAsync);
+ }
+
+ ///
+ /// 游戏账号集合
+ ///
+ public ObservableCollection? GameAccounts { get => gameAccounts; set => SetProperty(ref gameAccounts, value); }
+
+ ///
+ /// 选中的账号
+ ///
+ public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
+
+ ///
+ /// 启动游戏命令
+ ///
+ public ICommand LaunchCommand { get; }
+
+ ///
+ protected override async Task OpenUIAsync()
+ {
+ ObservableCollection accounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(false);
+ await ThreadHelper.SwitchToMainThreadAsync();
+ GameAccounts = accounts;
+
+ // Try set to the current account.
+ SelectedGameAccount ??= gameService.DetectCurrentGameAccount();
+ }
+
+ private async Task LaunchAsync()
+ {
+ IInfoBarService infoBarService = ServiceProvider.GetRequiredService();
+
+ try
+ {
+ if (SelectedGameAccount != null)
+ {
+ if (!gameService.SetGameAccount(SelectedGameAccount))
+ {
+ infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
+ return;
+ }
+ }
+
+ await gameService.LaunchAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ infoBarService.Error(ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
index 19aff8bf..40071f23 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
@@ -115,6 +115,11 @@ internal sealed class WelcomeViewModel : ObservableObject
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
}
+ if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV5Contract))
+ {
+ downloadSummaries.TryAdd("MonsterIcon", new(serviceProvider, "MonsterIcon"));
+ }
+
return downloadSummaries.Select(x => x.Value);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
index 9861016c..b3927daa 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
@@ -115,6 +115,7 @@ internal class WikiWeaponViewModel : Abstraction.ViewModel
.Where(weapon => !skippedWeapons.Contains(weapon.Id))
.OrderByDescending(weapon => weapon.RankLevel)
.ThenBy(weapon => weapon.WeaponType)
+ .ThenByDescending(weapon => weapon.Id.Value)
.ToList();
await CombineWithWeaponCollocationsAsync(sorted).ConfigureAwait(false);