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