mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor viewmodels
This commit is contained in:
@@ -79,7 +79,6 @@ internal sealed class CommandGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
string className = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
|
string className = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
|
||||||
|
|
||||||
// TODO: 支持嵌套类
|
|
||||||
string code = $$"""
|
string code = $$"""
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
|||||||
{
|
{
|
||||||
private const string AttributeName = "Snap.Hutao.Core.Annotation.ConstructorGeneratedAttribute";
|
private const string AttributeName = "Snap.Hutao.Core.Annotation.ConstructorGeneratedAttribute";
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor genericTypeNotSupportedDescriptor = new("SH102", "Generic type is not supported to generate .ctor", "Type [{0}] is not supported", "Quality", DiagnosticSeverity.Error, true);
|
//private static readonly DiagnosticDescriptor genericTypeNotSupportedDescriptor = new("SH102", "Generic type is not supported to generate .ctor", "Type [{0}] is not supported", "Quality", DiagnosticSeverity.Error, true);
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
@@ -61,12 +61,6 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
private static void GenerateConstructorImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2)
|
private static void GenerateConstructorImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2)
|
||||||
{
|
{
|
||||||
if (context2.Symbol.IsGenericType)
|
|
||||||
{
|
|
||||||
production.ReportDiagnostic(Diagnostic.Create(genericTypeNotSupportedDescriptor, context2.Context.Node.GetLocation(), context2.Symbol));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AttributeData constructorInfo = context2.SingleAttribute(AttributeName);
|
AttributeData constructorInfo = context2.SingleAttribute(AttributeName);
|
||||||
|
|
||||||
bool resolveHttpClient = constructorInfo.HasNamedArgumentWith<bool>("ResolveHttpClient", value => value);
|
bool resolveHttpClient = constructorInfo.HasNamedArgumentWith<bool>("ResolveHttpClient", value => value);
|
||||||
@@ -79,10 +73,9 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
|||||||
namespace {{context2.Symbol.ContainingNamespace}};
|
namespace {{context2.Symbol.ContainingNamespace}};
|
||||||
|
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(ConstructorGenerator)}}", "1.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(ConstructorGenerator)}}", "1.0.0.0")]
|
||||||
partial class {{context2.Symbol.Name}}
|
partial class {{context2.Symbol.ToDisplayString(SymbolDisplayFormats.QualifiedNonNullableFormat)}}
|
||||||
{
|
{
|
||||||
public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}})
|
public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}}){{(options.CallBaseConstructor? " : base(serviceProvider)" : string.Empty)}}
|
||||||
{{(options.CallBaseConstructor? ": base(serviceProvider)" : string.Empty)}}
|
|
||||||
{
|
{
|
||||||
|
|
||||||
""");
|
""");
|
||||||
@@ -93,8 +86,9 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
production.AddSource($"{context2.Symbol.ToDisplayString()}.ctor.g.cs", sourceBuilder.ToString());
|
string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
|
||||||
|
production.AddSource($"{normalizedClassName}.ctor.g.cs", sourceBuilder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FillUpWithFieldValueAssignment(StringBuilder builder, GeneratorSyntaxContext2 context2, FieldValueAssignmentOptions options)
|
private static void FillUpWithFieldValueAssignment(StringBuilder builder, GeneratorSyntaxContext2 context2, FieldValueAssignmentOptions options)
|
||||||
|
|||||||
@@ -12,4 +12,10 @@ internal static class SymbolDisplayFormats
|
|||||||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
|
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
|
||||||
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
|
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
|
||||||
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
|
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
|
||||||
|
|
||||||
|
public static SymbolDisplayFormat QualifiedNonNullableFormat { get; } = new(
|
||||||
|
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
|
||||||
|
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
|
||||||
|
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
|
||||||
|
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Snap.Hutao.SourceGeneration.Primitive;
|
using Snap.Hutao.SourceGeneration.Primitive;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -18,6 +19,10 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
|||||||
private static readonly DiagnosticDescriptor typeInternalDescriptor = new("SH001", "Type should be internal", "Type [{0}] should be internal", "Quality", DiagnosticSeverity.Info, true);
|
private static readonly DiagnosticDescriptor typeInternalDescriptor = new("SH001", "Type should be internal", "Type [{0}] should be internal", "Quality", DiagnosticSeverity.Info, true);
|
||||||
private static readonly DiagnosticDescriptor readOnlyStructRefDescriptor = new("SH002", "ReadOnly struct should be passed with ref-like key word", "ReadOnly Struct [{0}] should be passed with ref-like key word", "Quality", DiagnosticSeverity.Info, true);
|
private static readonly DiagnosticDescriptor readOnlyStructRefDescriptor = new("SH002", "ReadOnly struct should be passed with ref-like key word", "ReadOnly Struct [{0}] should be passed with ref-like key word", "Quality", DiagnosticSeverity.Info, true);
|
||||||
private static readonly DiagnosticDescriptor useValueTaskIfPossibleDescriptor = new("SH003", "Use ValueTask instead of Task whenever possible", "Use ValueTask instead of Task", "Quality", DiagnosticSeverity.Info, true);
|
private static readonly DiagnosticDescriptor useValueTaskIfPossibleDescriptor = new("SH003", "Use ValueTask instead of Task whenever possible", "Use ValueTask instead of Task", "Quality", DiagnosticSeverity.Info, true);
|
||||||
|
private static readonly DiagnosticDescriptor useIsNotNullPatternMatchingDescriptor = new("SH004", "Use \"is not null\" instead of \"!= null\" whenever possible", "Use \"is not null\" instead of \"!= null\"", "Quality", DiagnosticSeverity.Info, true);
|
||||||
|
private static readonly DiagnosticDescriptor useIsNullPatternMatchingDescriptor = new("SH005", "Use \"is null\" instead of \"== null\" whenever possible", "Use \"is null\" instead of \"== null\"", "Quality", DiagnosticSeverity.Info, true);
|
||||||
|
private static readonly DiagnosticDescriptor useIsPatternRecursiveMatchingDescriptor = new("SH006", "Use \"is { } obj\" whenever possible", "Use \"is {{ }} {0}\"", "Quality", DiagnosticSeverity.Info, true);
|
||||||
|
|
||||||
|
|
||||||
private static readonly ImmutableHashSet<string> RefLikeKeySkipTypes = new HashSet<string>()
|
private static readonly ImmutableHashSet<string> RefLikeKeySkipTypes = new HashSet<string>()
|
||||||
{
|
{
|
||||||
@@ -34,6 +39,9 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
|||||||
typeInternalDescriptor,
|
typeInternalDescriptor,
|
||||||
readOnlyStructRefDescriptor,
|
readOnlyStructRefDescriptor,
|
||||||
useValueTaskIfPossibleDescriptor,
|
useValueTaskIfPossibleDescriptor,
|
||||||
|
useIsNotNullPatternMatchingDescriptor,
|
||||||
|
useIsNullPatternMatchingDescriptor,
|
||||||
|
useIsPatternRecursiveMatchingDescriptor
|
||||||
}.ToImmutableArray();
|
}.ToImmutableArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,18 +56,25 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
|||||||
|
|
||||||
private static void CompilationStart(CompilationStartAnalysisContext context)
|
private static void CompilationStart(CompilationStartAnalysisContext context)
|
||||||
{
|
{
|
||||||
SyntaxKind[] types = new SyntaxKind[]
|
SyntaxKind[] types =
|
||||||
{
|
{
|
||||||
SyntaxKind.ClassDeclaration,
|
SyntaxKind.ClassDeclaration,
|
||||||
SyntaxKind.InterfaceDeclaration,
|
SyntaxKind.InterfaceDeclaration,
|
||||||
SyntaxKind.StructDeclaration,
|
SyntaxKind.StructDeclaration,
|
||||||
SyntaxKind.EnumDeclaration
|
SyntaxKind.EnumDeclaration,
|
||||||
};
|
};
|
||||||
|
|
||||||
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
|
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
|
||||||
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
|
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
|
||||||
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
|
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
|
||||||
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
|
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
|
||||||
|
|
||||||
|
SyntaxKind[] expressions =
|
||||||
|
{
|
||||||
|
SyntaxKind.EqualsExpression,
|
||||||
|
SyntaxKind.NotEqualsExpression,
|
||||||
|
};
|
||||||
|
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
|
||||||
|
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context)
|
private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context)
|
||||||
@@ -211,6 +226,39 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching(SyntaxNodeAnalysisContext context)
|
||||||
|
{
|
||||||
|
BinaryExpressionSyntax syntax = (BinaryExpressionSyntax)context.Node;
|
||||||
|
if (syntax.IsKind(SyntaxKind.NotEqualsExpression) && syntax.Right.IsKind(SyntaxKind.NullLiteralExpression))
|
||||||
|
{
|
||||||
|
Location location = syntax.OperatorToken.GetLocation();
|
||||||
|
Diagnostic diagnostic = Diagnostic.Create(useIsNotNullPatternMatchingDescriptor, location);
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
else if(syntax.IsKind(SyntaxKind.EqualsExpression) && syntax.Right.IsKind(SyntaxKind.NullLiteralExpression))
|
||||||
|
{
|
||||||
|
Location location = syntax.OperatorToken.GetLocation();
|
||||||
|
Diagnostic diagnostic = Diagnostic.Create(useIsNullPatternMatchingDescriptor, location);
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleIsPatternShouldUseRecursivePattern(SyntaxNodeAnalysisContext context)
|
||||||
|
{
|
||||||
|
IsPatternExpressionSyntax syntax = (IsPatternExpressionSyntax)context.Node;
|
||||||
|
if (syntax.Pattern is DeclarationPatternSyntax declaration)
|
||||||
|
{
|
||||||
|
ITypeSymbol? leftType = context.SemanticModel.GetTypeInfo(syntax.Expression).ConvertedType;
|
||||||
|
ITypeSymbol? rightType = context.SemanticModel.GetTypeInfo(declaration).ConvertedType;
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(leftType, rightType))
|
||||||
|
{
|
||||||
|
Location location = declaration.GetLocation();
|
||||||
|
Diagnostic diagnostic = Diagnostic.Create(useIsPatternRecursiveMatchingDescriptor, location, declaration.Designation);
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsBuiltInType(ITypeSymbol symbol)
|
private static bool IsBuiltInType(ITypeSymbol symbol)
|
||||||
{
|
{
|
||||||
return symbol.SpecialType switch
|
return symbol.SpecialType switch
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void OnAssociatedObjectLoaded()
|
protected override void OnAssociatedObjectLoaded()
|
||||||
{
|
{
|
||||||
if (Command != null && Command.CanExecute(CommandParameter))
|
if (Command is not null && Command.CanExecute(CommandParameter))
|
||||||
{
|
{
|
||||||
Command.Execute(CommandParameter);
|
Command.Execute(CommandParameter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
|||||||
{
|
{
|
||||||
await HideAsync(token).ConfigureAwait(true);
|
await HideAsync(token).ConfigureAwait(true);
|
||||||
|
|
||||||
if (uri != null)
|
if (uri is not null)
|
||||||
{
|
{
|
||||||
LoadedImageSurface? imageSurface = null;
|
LoadedImageSurface? imageSurface = null;
|
||||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||||
@@ -147,7 +147,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
|||||||
imageCache.Remove(uri);
|
imageCache.Remove(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageSurface != null)
|
if (imageSurface is not null)
|
||||||
{
|
{
|
||||||
using (imageSurface)
|
using (imageSurface)
|
||||||
{
|
{
|
||||||
@@ -221,7 +221,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
|||||||
|
|
||||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.NewSize != e.PreviousSize && spriteVisual != null)
|
if (e.NewSize != e.PreviousSize && spriteVisual is not null)
|
||||||
{
|
{
|
||||||
UpdateVisual(spriteVisual);
|
UpdateVisual(spriteVisual);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ internal sealed class MonoChrome : CompositionImage
|
|||||||
|
|
||||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||||
{
|
{
|
||||||
if (backgroundBrush != null)
|
if (backgroundBrush is not null)
|
||||||
{
|
{
|
||||||
SetBackgroundColor(backgroundBrush);
|
SetBackgroundColor(backgroundBrush);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ internal class ScopedPage : Page
|
|||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
public async ValueTask NotifyRecipientAsync(INavigationData extra)
|
public async ValueTask NotifyRecipientAsync(INavigationData extra)
|
||||||
{
|
{
|
||||||
if (extra.Data != null && DataContext is INavigationRecipient recipient)
|
if (extra.Data is not null && DataContext is INavigationRecipient recipient)
|
||||||
{
|
{
|
||||||
await recipient.ReceiveAsync(extra).ConfigureAwait(false);
|
await recipient.ReceiveAsync(extra).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ internal sealed class CommandLineBuilder
|
|||||||
/// <returns>命令行建造器</returns>
|
/// <returns>命令行建造器</returns>
|
||||||
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
|
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
|
||||||
{
|
{
|
||||||
return AppendIf(name, value != null, value);
|
return AppendIf(name, value is not null, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ namespace Snap.Hutao.Core.Database;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||||
internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
[ConstructorGenerated]
|
||||||
|
internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||||
where TEntity : class, ISelectable
|
where TEntity : class, ISelectable
|
||||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||||
{
|
{
|
||||||
@@ -23,16 +24,6 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
|||||||
|
|
||||||
private TEntity? current;
|
private TEntity? current;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的数据库当前项
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
public ScopedDbCurrent(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
messenger = serviceProvider.GetRequiredService<IMessenger>();
|
|
||||||
this.serviceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前选中的项
|
/// 当前选中的项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,9 +45,9 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
|||||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||||
|
|
||||||
// only update when not processing a deletion
|
// only update when not processing a deletion
|
||||||
if (value != null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
if (current != null)
|
if (current is not null)
|
||||||
{
|
{
|
||||||
current.IsSelected = false;
|
current.IsSelected = false;
|
||||||
dbSet.UpdateAndSave(current);
|
dbSet.UpdateAndSave(current);
|
||||||
@@ -67,7 +58,7 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
|||||||
|
|
||||||
current = value;
|
current = value;
|
||||||
|
|
||||||
if (current != null)
|
if (current is not null)
|
||||||
{
|
{
|
||||||
current.IsSelected = true;
|
current.IsSelected = true;
|
||||||
dbSet.UpdateAndSave(current);
|
dbSet.UpdateAndSave(current);
|
||||||
@@ -79,8 +70,8 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "SA1402")]
|
[ConstructorGenerated]
|
||||||
internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||||
where TEntityOnly : class, IEntityOnly<TEntity>
|
where TEntityOnly : class, IEntityOnly<TEntity>
|
||||||
where TEntity : class, ISelectable
|
where TEntity : class, ISelectable
|
||||||
where TMessage : Message.ValueChangedMessage<TEntityOnly>, new()
|
where TMessage : Message.ValueChangedMessage<TEntityOnly>, new()
|
||||||
@@ -90,16 +81,6 @@ internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
|||||||
|
|
||||||
private TEntityOnly? current;
|
private TEntityOnly? current;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的数据库当前项
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
public ScopedDbCurrent(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
messenger = serviceProvider.GetRequiredService<IMessenger>();
|
|
||||||
this.serviceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前选中的项
|
/// 当前选中的项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -121,9 +102,9 @@ internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
|||||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||||
|
|
||||||
// only update when not processing a deletion
|
// only update when not processing a deletion
|
||||||
if (value != null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
if (current != null)
|
if (current is not null)
|
||||||
{
|
{
|
||||||
current.Entity.IsSelected = false;
|
current.Entity.IsSelected = false;
|
||||||
dbSet.UpdateAndSave(current.Entity);
|
dbSet.UpdateAndSave(current.Entity);
|
||||||
@@ -134,7 +115,7 @@ internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
|||||||
|
|
||||||
current = value;
|
current = value;
|
||||||
|
|
||||||
if (current != null)
|
if (current is not null)
|
||||||
{
|
{
|
||||||
current.Entity.IsSelected = true;
|
current.Entity.IsSelected = true;
|
||||||
dbSet.UpdateAndSave(current.Entity);
|
dbSet.UpdateAndSave(current.Entity);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ internal static class PickerExtension
|
|||||||
file = null;
|
file = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file != null)
|
if (file is not null)
|
||||||
{
|
{
|
||||||
return new(true, file.Path);
|
return new(true, file.Path);
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ internal static class PickerExtension
|
|||||||
file = null;
|
file = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file != null)
|
if (file is not null)
|
||||||
{
|
{
|
||||||
return new(true, file.Path);
|
return new(true, file.Path);
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ internal static class PickerExtension
|
|||||||
folder = null;
|
folder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folder != null)
|
if (folder is not null)
|
||||||
{
|
{
|
||||||
return new(true, folder.Path);
|
return new(true, folder.Path);
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ internal static class PickerExtension
|
|||||||
|
|
||||||
private static void InfoBarWaringPickerException(Exception? exception)
|
private static void InfoBarWaringPickerException(Exception? exception)
|
||||||
{
|
{
|
||||||
if (exception != null)
|
if (exception is not null)
|
||||||
{
|
{
|
||||||
Ioc.Default
|
Ioc.Default
|
||||||
.GetRequiredService<IInfoBarService>()
|
.GetRequiredService<IInfoBarService>()
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ internal sealed class DebugLogger : ILogger
|
|||||||
|
|
||||||
message = $"{logLevel}: {message}";
|
message = $"{logLevel}: {message}";
|
||||||
|
|
||||||
if (exception != null)
|
if (exception is not null)
|
||||||
{
|
{
|
||||||
message += Environment.NewLine + Environment.NewLine + exception;
|
message += Environment.NewLine + Environment.NewLine + exception;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ internal static class TypeNameHelper
|
|||||||
[return: NotNullIfNotNull(nameof(item))]
|
[return: NotNullIfNotNull(nameof(item))]
|
||||||
public static string? GetTypeDisplayName(object? item, bool fullName = true)
|
public static string? GetTypeDisplayName(object? item, bool fullName = true)
|
||||||
{
|
{
|
||||||
return item == null ? null : GetTypeDisplayName(item.GetType(), fullName);
|
return item is null ? null : GetTypeDisplayName(item.GetType(), fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -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 System.Diagnostics.Contracts;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ internal static partial class EnumerableExtension
|
|||||||
/// <param name="list">列表</param>
|
/// <param name="list">列表</param>
|
||||||
/// <param name="selector">选择器</param>
|
/// <param name="selector">选择器</param>
|
||||||
/// <returns>新类型的列表</returns>
|
/// <returns>新类型的列表</returns>
|
||||||
|
[Pure]
|
||||||
public static List<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, TResult> selector)
|
public static List<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, TResult> selector)
|
||||||
{
|
{
|
||||||
Span<TSource> span = CollectionsMarshal.AsSpan(list);
|
Span<TSource> span = CollectionsMarshal.AsSpan(list);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ internal sealed class Achievement
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Equals(Achievement? other)
|
public bool Equals(Achievement? other)
|
||||||
{
|
{
|
||||||
if (other == null)
|
if (other is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ internal sealed class UIAF
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string CurrentVersion = "v1.1";
|
public const string CurrentVersion = "v1.1";
|
||||||
|
|
||||||
private static readonly List<string> SupportedVersion = new()
|
// TODO use FrozenSet
|
||||||
|
private static readonly HashSet<string> SupportedVersion = new()
|
||||||
{
|
{
|
||||||
CurrentVersion,
|
CurrentVersion,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ internal sealed class UIGF
|
|||||||
{
|
{
|
||||||
foreach (UIGFItem item in List)
|
foreach (UIGFItem item in List)
|
||||||
{
|
{
|
||||||
if (item.ItemType != SH.ModelInterchangeUIGFItemTypeAvatar && item.ItemType != SH.ModelInterchangeUIGFItemTypeWeapon)
|
if (item.ItemType != SH.ModelInterchangeUIGFItemTypeAvatar || item.ItemType != SH.ModelInterchangeUIGFItemTypeWeapon)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ internal sealed class SkillDepot
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (compositeSkills == null)
|
if (compositeSkills is null)
|
||||||
{
|
{
|
||||||
compositeSkills = new(Skills.Count + 1 + Inherents.Count);
|
compositeSkills = new(Skills.Count + 1 + Inherents.Count);
|
||||||
compositeSkills.AddRange(Skills);
|
compositeSkills.AddRange(Skills);
|
||||||
@@ -60,7 +60,7 @@ internal sealed class SkillDepot
|
|||||||
/// <returns>天赋列表</returns>
|
/// <returns>天赋列表</returns>
|
||||||
public List<ProudableSkill> CompositeSkillsNoInherents()
|
public List<ProudableSkill> CompositeSkillsNoInherents()
|
||||||
{
|
{
|
||||||
if (compositeSkillsNoInherents == null)
|
if (compositeSkillsNoInherents is null)
|
||||||
{
|
{
|
||||||
compositeSkillsNoInherents = new(Skills.Count + 1);
|
compositeSkillsNoInherents = new(Skills.Count + 1);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ internal sealed class AvatarNameCardPicConverter : ValueConverter<Avatar.Avatar?
|
|||||||
/// <returns>名片</returns>
|
/// <returns>名片</returns>
|
||||||
public static Uri AvatarToUri(Avatar.Avatar? avatar)
|
public static Uri AvatarToUri(Avatar.Avatar? avatar)
|
||||||
{
|
{
|
||||||
if (avatar == null)
|
if (avatar is null)
|
||||||
{
|
{
|
||||||
return null!;
|
return null!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ internal sealed class ParameterFormat : IFormatProvider, ICustomFormatter
|
|||||||
case 'P':
|
case 'P':
|
||||||
return string.Format($"{{0:P0}}", arg);
|
return string.Format($"{{0:P0}}", arg);
|
||||||
case 'I':
|
case 'I':
|
||||||
return arg == null ? "0" : ((IConvertible)arg).ToInt32(default).ToString();
|
return arg is null ? "0" : ((IConvertible)arg).ToInt32(default).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ internal sealed class ReliquarySet
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public EquipAffixId EquipAffixId { get; set; }
|
public EquipAffixId EquipAffixId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 装备被动的被动Id
|
||||||
|
/// </summary>
|
||||||
|
public HashSet<ExtendedEquipAffixId> EquipAffixIds { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 套装名称
|
/// 套装名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最大等级
|
/// 最大等级
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal uint MaxLevel { get => ((int)Quality) >= 3 ? 90U : 70U; }
|
internal uint MaxLevel { get => GetMaxLevelByQuality(Quality); }
|
||||||
|
|
||||||
|
public static uint GetMaxLevelByQuality(QualityType quality)
|
||||||
|
{
|
||||||
|
return quality >= QualityType.QUALITY_BLUE ? 90U : 70U;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ICalculableWeapon ToCalculable()
|
public ICalculableWeapon ToCalculable()
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
|||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
||||||
storage = value == null ? defaultValue : bool.Parse(value);
|
storage = value is null ? defaultValue : bool.Parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.Value;
|
return storage.Value;
|
||||||
@@ -85,7 +85,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
|||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
||||||
storage = value == null ? defaultValue : int.Parse(value);
|
storage = value is null ? defaultValue : int.Parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.Value;
|
return storage.Value;
|
||||||
@@ -103,7 +103,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
|||||||
protected T GetOption<T>(ref T? storage, string key, Func<string, T> deserializer, T defaultValue)
|
protected T GetOption<T>(ref T? storage, string key, Func<string, T> deserializer, T defaultValue)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
if (storage != null)
|
if (storage is not null)
|
||||||
{
|
{
|
||||||
return storage;
|
return storage;
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
|||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
||||||
storage = value == null ? defaultValue : deserializer(value);
|
storage = value is null ? defaultValue : deserializer(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage;
|
return storage;
|
||||||
@@ -139,7 +139,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
|||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
||||||
storage = value == null ? defaultValue : deserializer(value);
|
storage = value is null ? defaultValue : deserializer(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.Value;
|
return storage.Value;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ internal sealed partial class AchievementDbBulkOperation
|
|||||||
add++;
|
add++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (entity != null && uiaf == null)
|
else if (entity is not null && uiaf is null)
|
||||||
{
|
{
|
||||||
// skip
|
// skip
|
||||||
continue;
|
continue;
|
||||||
@@ -157,7 +157,7 @@ internal sealed partial class AchievementDbBulkOperation
|
|||||||
add++;
|
add++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (oldEntity != null && newEntity == null)
|
else if (oldEntity is not null && newEntity is null)
|
||||||
{
|
{
|
||||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||||
remove++;
|
remove++;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ internal sealed partial class AchievementService
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (archiveCollection == null)
|
if (archiveCollection is null)
|
||||||
{
|
{
|
||||||
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
|
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
|
||||||
CurrentArchive = archiveCollection.SelectedOrDefault();
|
CurrentArchive = archiveCollection.SelectedOrDefault();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ internal sealed partial class AchievementService : IAchievementService
|
|||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public List<AchievementView> GetAchievementViews(AchievementArchive archive, List<MetadataAchievement> metadata)
|
public List<AchievementView> GetAchievementViewList(AchievementArchive archive, List<MetadataAchievement> metadata)
|
||||||
{
|
{
|
||||||
Dictionary<AchievementId, EntityAchievement> entities = achievementDbService.GetAchievementMapByArchiveId(archive.InnerId);
|
Dictionary<AchievementId, EntityAchievement> entities = achievementDbService.GetAchievementMapByArchiveId(archive.InnerId);
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ internal interface IAchievementService
|
|||||||
/// <param name="archive">用户</param>
|
/// <param name="archive">用户</param>
|
||||||
/// <param name="metadata">元数据</param>
|
/// <param name="metadata">元数据</param>
|
||||||
/// <returns>整合的成就</returns>
|
/// <returns>整合的成就</returns>
|
||||||
List<AchievementView> GetAchievementViews(EntityArchive archive, List<MetadataAchievement> metadata);
|
List<AchievementView> GetAchievementViewList(EntityArchive archive, List<MetadataAchievement> metadata);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步导入UIAF数据
|
/// 异步导入UIAF数据
|
||||||
|
|||||||
@@ -13,17 +13,18 @@ namespace Snap.Hutao.Service;
|
|||||||
/// 应用程序选项
|
/// 应用程序选项
|
||||||
/// 存储服务相关的选项
|
/// 存储服务相关的选项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||||
[Injection(InjectAs.Singleton)]
|
[Injection(InjectAs.Singleton)]
|
||||||
internal sealed partial class AppOptions : DbStoreOptions
|
internal sealed partial class AppOptions : DbStoreOptions
|
||||||
{
|
{
|
||||||
private readonly List<NameValue<BackdropType>> supportedBackdropTypes = new()
|
private static readonly List<NameValue<BackdropType>> SupportedBackdropTypesInner = new()
|
||||||
{
|
{
|
||||||
new("Acrylic", BackdropType.Acrylic),
|
new("Acrylic", BackdropType.Acrylic),
|
||||||
new("Mica", BackdropType.Mica),
|
new("Mica", BackdropType.Mica),
|
||||||
new("MicaAlt", BackdropType.MicaAlt),
|
new("MicaAlt", BackdropType.MicaAlt),
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly List<NameValue<string>> supportedCultures = new()
|
private static readonly List<NameValue<string>> SupportedCulturesInner = new()
|
||||||
{
|
{
|
||||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
|
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
|
||||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
|
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
|
||||||
@@ -38,15 +39,6 @@ internal sealed partial class AppOptions : DbStoreOptions
|
|||||||
private CultureInfo? currentCulture;
|
private CultureInfo? currentCulture;
|
||||||
private bool? isAdvancedLaunchOptionsEnabled;
|
private bool? isAdvancedLaunchOptionsEnabled;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的应用程序选项
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
public AppOptions(IServiceProvider serviceProvider)
|
|
||||||
: base(serviceProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏路径
|
/// 游戏路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -68,7 +60,7 @@ internal sealed partial class AppOptions : DbStoreOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 所有支持的背景样式
|
/// 所有支持的背景样式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<NameValue<BackdropType>> BackdropTypes { get => supportedBackdropTypes; }
|
public List<NameValue<BackdropType>> BackdropTypes { get => SupportedBackdropTypesInner; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 背景类型 默认 Mica
|
/// 背景类型 默认 Mica
|
||||||
@@ -82,7 +74,7 @@ internal sealed partial class AppOptions : DbStoreOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 所有支持的语言
|
/// 所有支持的语言
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<NameValue<string>> Cultures { get => supportedCultures; }
|
public List<NameValue<string>> Cultures { get => SupportedCulturesInner; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化前的语言
|
/// 初始化前的语言
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void AddOrUpdateAvatarInfo<TSource>(ModelAvatarInfo? entity, in AvatarId avatarId, string uid, AppDbContext appDbContext, IAvatarInfoTransformer<TSource> transformer, TSource source)
|
private static void AddOrUpdateAvatarInfo<TSource>(ModelAvatarInfo? entity, in AvatarId avatarId, string uid, AppDbContext appDbContext, IAvatarInfoTransformer<TSource> transformer, TSource source)
|
||||||
{
|
{
|
||||||
if (entity == null)
|
if (entity is null)
|
||||||
{
|
{
|
||||||
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
|
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
|
||||||
transformer.Transform(ref avatarInfo, source);
|
transformer.Transform(ref avatarInfo, source);
|
||||||
@@ -197,7 +197,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void AddOrUpdateAvatarInfo(ModelAvatarInfo? entity, string uid, AppDbContext appDbContext, EnkaAvatarInfo webInfo)
|
private static void AddOrUpdateAvatarInfo(ModelAvatarInfo? entity, string uid, AppDbContext appDbContext, EnkaAvatarInfo webInfo)
|
||||||
{
|
{
|
||||||
if (entity == null)
|
if (entity is null)
|
||||||
{
|
{
|
||||||
entity = ModelAvatarInfo.From(uid, webInfo);
|
entity = ModelAvatarInfo.From(uid, webInfo);
|
||||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
|
|||||||
{
|
{
|
||||||
EnkaResponse? resp = await GetEnkaResponseAsync(userAndUid.Uid, token).ConfigureAwait(false);
|
EnkaResponse? resp = await GetEnkaResponseAsync(userAndUid.Uid, token).ConfigureAwait(false);
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
if (resp == null)
|
if (resp is null)
|
||||||
{
|
{
|
||||||
return new(RefreshResult.APIUnavailable, default);
|
return new(RefreshResult.APIUnavailable, default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ internal sealed class SummaryAvatarFactory
|
|||||||
WeaponStat? subStat = equip.Flat.WeaponStats?.ElementAtOrDefault(1);
|
WeaponStat? subStat = equip.Flat.WeaponStats?.ElementAtOrDefault(1);
|
||||||
|
|
||||||
NameDescription subProperty;
|
NameDescription subProperty;
|
||||||
if (subStat == null)
|
if (subStat is null)
|
||||||
{
|
{
|
||||||
subProperty = new(string.Empty, string.Empty);
|
subProperty = new(string.Empty, string.Empty);
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ internal sealed class SummaryAvatarFactory
|
|||||||
// EquipBase
|
// EquipBase
|
||||||
Level = $"Lv.{equip.Weapon.Level.Value}",
|
Level = $"Lv.{equip.Weapon.Level.Value}",
|
||||||
Quality = weapon.Quality,
|
Quality = weapon.Quality,
|
||||||
MainProperty = mainStat != null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : default!,
|
MainProperty = mainStat is not null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : default!,
|
||||||
|
|
||||||
// Weapon
|
// Weapon
|
||||||
Id = weapon.Id,
|
Id = weapon.Id,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ internal static class SummaryHelper
|
|||||||
|
|
||||||
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
|
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
|
||||||
|
|
||||||
if (proudSkillExtraLevelMap != null)
|
if (proudSkillExtraLevelMap is not null)
|
||||||
{
|
{
|
||||||
foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap)
|
foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ internal sealed partial class CultivationService
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (projects == null)
|
if (projects is null)
|
||||||
{
|
{
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ internal sealed partial class CultivationService : ICultivationService
|
|||||||
.GetCultivateEntryByProjectIdAndItemIdAsync(Current.InnerId, itemId)
|
.GetCultivateEntryByProjectIdAndItemIdAsync(Current.InnerId, itemId)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (entry == null)
|
if (entry is null)
|
||||||
{
|
{
|
||||||
entry = CultivateEntry.From(Current.InnerId, type, itemId);
|
entry = CultivateEntry.From(Current.InnerId, type, itemId);
|
||||||
await cultivationDbService.InsertCultivateEntryAsync(entry).ConfigureAwait(false);
|
await cultivationDbService.InsertCultivateEntryAsync(entry).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ internal sealed class DailyNoteNotificationOperation
|
|||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
public async ValueTask SendAsync()
|
public async ValueTask SendAsync()
|
||||||
{
|
{
|
||||||
if (entry.DailyNote == null)
|
if (entry.DailyNote is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ internal sealed class DailyNoteOptions : DbStoreOptions
|
|||||||
get => GetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, time => RefreshTimes.Single(t => t.Value == int.Parse(time)), RefreshTimes[1]);
|
get => GetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, time => RefreshTimes.Single(t => t.Value == int.Parse(time)), RefreshTimes[1]);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
if (scheduleTaskInterop.RegisterForDailyNoteRefresh(value.Value))
|
if (scheduleTaskInterop.RegisterForDailyNoteRefresh(value.Value))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntryCollectionAsync()
|
public async ValueTask<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntryCollectionAsync()
|
||||||
{
|
{
|
||||||
if (entries == null)
|
if (entries is null)
|
||||||
{
|
{
|
||||||
await RefreshDailyNotesAsync().ConfigureAwait(false);
|
await RefreshDailyNotesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ internal static class GachaArchiveOperation
|
|||||||
{
|
{
|
||||||
archive = archives.SingleOrDefault(a => a.Uid == uid);
|
archive = archives.SingleOrDefault(a => a.Uid == uid);
|
||||||
|
|
||||||
if (archive == null)
|
if (archive is null)
|
||||||
{
|
{
|
||||||
GachaArchive created = GachaArchive.From(uid);
|
GachaArchive created = GachaArchive.From(uid);
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ internal struct GachaLogFetchContext
|
|||||||
/// <param name="gachaLogDbService">祈愿记录数据库服务</param>
|
/// <param name="gachaLogDbService">祈愿记录数据库服务</param>
|
||||||
public void EnsureArchiveAndEndId(GachaLogItem item, ObservableCollection<GachaArchive> archives, IGachaLogDbService gachaLogDbService)
|
public void EnsureArchiveAndEndId(GachaLogItem item, ObservableCollection<GachaArchive> archives, IGachaLogDbService gachaLogDbService)
|
||||||
{
|
{
|
||||||
if (TargetArchive == null)
|
if (TargetArchive is null)
|
||||||
{
|
{
|
||||||
GachaArchiveOperation.GetOrAdd(serviceProvider, item.Uid, archives, out TargetArchive);
|
GachaArchiveOperation.GetOrAdd(serviceProvider, item.Uid, archives, out TargetArchive);
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ internal struct GachaLogFetchContext
|
|||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
// While no item is fetched, archive can be null.
|
// While no item is fetched, archive can be null.
|
||||||
if (TargetArchive != null)
|
if (TargetArchive is not null)
|
||||||
{
|
{
|
||||||
GachaItemSaveContext saveContext = new(ItemsToAdd, isLazy, QueryOptions.EndId, appDbContext.GachaItems);
|
GachaItemSaveContext saveContext = new(ItemsToAdd, isLazy, QueryOptions.EndId, appDbContext.GachaItems);
|
||||||
TargetArchive.SaveItems(saveContext);
|
TargetArchive.SaveItems(saveContext);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
|||||||
return new(false, null);
|
return new(false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (archive == null)
|
if (archive is null)
|
||||||
{
|
{
|
||||||
archive = GachaArchive.From(uid);
|
archive = GachaArchive.From(uid);
|
||||||
await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false);
|
await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false);
|
||||||
@@ -121,7 +121,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
|||||||
EndIds endIds = new();
|
EndIds endIds = new();
|
||||||
foreach (GachaConfigType type in GachaLog.QueryTypes)
|
foreach (GachaConfigType type in GachaLog.QueryTypes)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive is not null)
|
||||||
{
|
{
|
||||||
endIds[type] = await gachaLogDbService
|
endIds[type] = await gachaLogDbService
|
||||||
.GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(archive.InnerId, type, token)
|
.GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(archive.InnerId, type, token)
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
|
|
||||||
(bool authkeyValid, GachaArchive? result) = await FetchGachaLogsAsync(query, isLazy, progress, token).ConfigureAwait(false);
|
(bool authkeyValid, GachaArchive? result) = await FetchGachaLogsAsync(query, isLazy, progress, token).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result != null)
|
if (result is not null)
|
||||||
{
|
{
|
||||||
CurrentArchive = result;
|
CurrentArchive = result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ internal sealed partial class GameService : IGameService
|
|||||||
ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex);
|
ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account == null)
|
if (account is null)
|
||||||
{
|
{
|
||||||
// ContentDialog must be created by main thread.
|
// ContentDialog must be created by main thread.
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator
|
|||||||
.FirstOrDefault(p => p.Key == "game_install_path")?.Value;
|
.FirstOrDefault(p => p.Key == "game_install_path")?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (escapedPath != null)
|
if (escapedPath is not null)
|
||||||
{
|
{
|
||||||
string gamePath = Path.Combine(Unescape(escapedPath), GameConstants.YuanShenFileName);
|
string gamePath = Path.Combine(Unescape(escapedPath), GameConstants.YuanShenFileName);
|
||||||
return new(true, gamePath);
|
return new(true, gamePath);
|
||||||
@@ -56,7 +56,7 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator
|
|||||||
{
|
{
|
||||||
using (RegistryKey? uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神"))
|
using (RegistryKey? uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神"))
|
||||||
{
|
{
|
||||||
if (uninstallKey != null)
|
if (uninstallKey is not null)
|
||||||
{
|
{
|
||||||
if (uninstallKey.GetValue(key) is string path)
|
if (uninstallKey.GetValue(key) is string path)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ namespace Snap.Hutao.Service.Game.Package;
|
|||||||
internal sealed partial class PackageConverter
|
internal sealed partial class PackageConverter
|
||||||
{
|
{
|
||||||
private const string PackageVersion = "pkg_version";
|
private const string PackageVersion = "pkg_version";
|
||||||
private readonly IServiceProvider serviceProvider;
|
|
||||||
private readonly JsonSerializerOptions options;
|
private readonly JsonSerializerOptions options;
|
||||||
private readonly RuntimeOptions runtimeOptions;
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
@@ -97,7 +96,7 @@ internal sealed partial class PackageConverter
|
|||||||
string sdkVersion = Path.Combine(gameFolder, "sdk_pkg_version");
|
string sdkVersion = Path.Combine(gameFolder, "sdk_pkg_version");
|
||||||
|
|
||||||
// Only bilibili's sdk is not null
|
// Only bilibili's sdk is not null
|
||||||
if (resource.Sdk != null)
|
if (resource.Sdk is not null)
|
||||||
{
|
{
|
||||||
// TODO: verify sdk md5
|
// TODO: verify sdk md5
|
||||||
if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup))
|
if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup))
|
||||||
@@ -132,7 +131,7 @@ internal sealed partial class PackageConverter
|
|||||||
FileOperation.Move(sdkVersion, sdkVersionBackup, true);
|
FileOperation.Move(sdkVersion, sdkVersionBackup, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource.DeprecatedFiles != null)
|
if (resource.DeprecatedFiles is not null)
|
||||||
{
|
{
|
||||||
foreach (NameMd5 file in resource.DeprecatedFiles)
|
foreach (NameMd5 file in resource.DeprecatedFiles)
|
||||||
{
|
{
|
||||||
@@ -170,12 +169,6 @@ internal sealed partial class PackageConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MoveToCache(string cacheFilePath, string targetFullPath)
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)!);
|
|
||||||
File.Move(targetFullPath, cacheFilePath, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[GeneratedRegex("^(?:YuanShen_Data|GenshinImpact_Data)(?=/)")]
|
[GeneratedRegex("^(?:YuanShen_Data|GenshinImpact_Data)(?=/)")]
|
||||||
private static partial Regex DataFolderRegex();
|
private static partial Regex DataFolderRegex();
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ internal static class RegistryInterop
|
|||||||
/// <returns>账号是否设置</returns>
|
/// <returns>账号是否设置</returns>
|
||||||
public static bool Set(GameAccount? account)
|
public static bool Set(GameAccount? account)
|
||||||
{
|
{
|
||||||
if (account != null)
|
if (account is not null)
|
||||||
{
|
{
|
||||||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(account.MihoyoSDK));
|
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(account.MihoyoSDK));
|
||||||
string path = $"HKCU:{GenshinKey[@"HKEY_CURRENT_USER\".Length..]}";
|
string path = $"HKCU:{GenshinKey[@"HKEY_CURRENT_USER\".Length..]}";
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
||||||
{
|
{
|
||||||
if (databaseViewModelTaskSource != null)
|
if (databaseViewModelTaskSource is not null)
|
||||||
{
|
{
|
||||||
return await databaseViewModelTaskSource.Task.ConfigureAwait(false);
|
return await databaseViewModelTaskSource.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
|
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
|
||||||
{
|
{
|
||||||
if (wikiAvatarViewModelTaskSource != null)
|
if (wikiAvatarViewModelTaskSource is not null)
|
||||||
{
|
{
|
||||||
return await wikiAvatarViewModelTaskSource.Task.ConfigureAwait(false);
|
return await wikiAvatarViewModelTaskSource.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> InitializeForWikiWeaponViewModelAsync()
|
public async ValueTask<bool> InitializeForWikiWeaponViewModelAsync()
|
||||||
{
|
{
|
||||||
if (wikiWeaponViewModelTaskSource != null)
|
if (wikiWeaponViewModelTaskSource is not null)
|
||||||
{
|
{
|
||||||
return await wikiWeaponViewModelTaskSource.Task.ConfigureAwait(false);
|
return await wikiWeaponViewModelTaskSource.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
|||||||
|
|
||||||
private async ValueTask<Dictionary<AvatarId, Avatar>> GetIdAvatarMapExtendedAsync()
|
private async ValueTask<Dictionary<AvatarId, Avatar>> GetIdAvatarMapExtendedAsync()
|
||||||
{
|
{
|
||||||
if (idAvatarExtendedMap == null)
|
if (idAvatarExtendedMap is null)
|
||||||
{
|
{
|
||||||
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||||
idAvatarExtendedMap = AvatarIds.WithPlayers(idAvatarMap);
|
idAvatarExtendedMap = AvatarIds.WithPlayers(idAvatarMap);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ internal sealed partial class HutaoService : IHutaoService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (data != null)
|
if (data is not null)
|
||||||
{
|
{
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (fallbackDataFolder == null)
|
if (fallbackDataFolder is null)
|
||||||
{
|
{
|
||||||
fallbackDataFolder = Path.Combine(hutaoOptions.DataFolder, "Metadata", "CHS");
|
fallbackDataFolder = Path.Combine(hutaoOptions.DataFolder, "Metadata", "CHS");
|
||||||
Directory.CreateDirectory(fallbackDataFolder);
|
Directory.CreateDirectory(fallbackDataFolder);
|
||||||
@@ -47,7 +47,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (localizedDataFolder == null)
|
if (localizedDataFolder is null)
|
||||||
{
|
{
|
||||||
localizedDataFolder = Path.Combine(hutaoOptions.DataFolder, "Metadata", LocaleName);
|
localizedDataFolder = Path.Combine(hutaoOptions.DataFolder, "Metadata", LocaleName);
|
||||||
Directory.CreateDirectory(localizedDataFolder);
|
Directory.CreateDirectory(localizedDataFolder);
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ internal sealed class NavigationService : INavigationService, INavigationInitial
|
|||||||
: NavHelper.GetNavigateTo(selected);
|
: NavHelper.GetNavigateTo(selected);
|
||||||
|
|
||||||
// ignore item that doesn't have nav type specified
|
// ignore item that doesn't have nav type specified
|
||||||
if (targetType != null)
|
if (targetType is not null)
|
||||||
{
|
{
|
||||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(NavHelper.GetExtraData(selected));
|
INavigationAwaiter navigationAwaiter = new NavigationExtra(NavHelper.GetExtraData(selected));
|
||||||
Navigate(targetType, navigationAwaiter, false);
|
Navigate(targetType, navigationAwaiter, false);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
|
|||||||
{
|
{
|
||||||
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
|
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
|
||||||
|
|
||||||
if (spiralAbysses!.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId) is SpiralAbyssEntry existEntry)
|
if (spiralAbysses!.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId) is { } existEntry)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
|
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ internal sealed partial class UserService : IUserService
|
|||||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
if (userCollection == null)
|
if (userCollection is null)
|
||||||
{
|
{
|
||||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
||||||
List<BindingUser> users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false);
|
List<BindingUser> users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false);
|
||||||
@@ -83,7 +83,7 @@ internal sealed partial class UserService : IUserService
|
|||||||
public async ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
|
public async ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
if (userAndUidCollection == null)
|
if (userAndUidCollection is null)
|
||||||
{
|
{
|
||||||
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
|
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
|
||||||
userAndUidCollection = users
|
userAndUidCollection = users
|
||||||
@@ -186,16 +186,16 @@ internal sealed partial class UserService : IUserService
|
|||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false);
|
BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false);
|
||||||
|
|
||||||
if (newUser != null)
|
if (newUser is not null)
|
||||||
{
|
{
|
||||||
// Sync cache
|
// Sync cache
|
||||||
if (userCollection != null)
|
if (userCollection is not null)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
{
|
{
|
||||||
userCollection!.Add(newUser);
|
userCollection.Add(newUser);
|
||||||
|
|
||||||
if (userAndUidCollection != null)
|
if (userAndUidCollection is not null)
|
||||||
{
|
{
|
||||||
foreach (UserGameRole role in newUser.UserGameRoles)
|
foreach (UserGameRole role in newUser.UserGameRoles)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.View.Control;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 胡桃云祈愿统计卡片
|
/// 胡桃云祈愿统计卡片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class HutaoStatisticsCard : UserControl
|
internal sealed partial class HutaoStatisticsCard : UserControl
|
||||||
{
|
{
|
||||||
public HutaoStatisticsCard()
|
public HutaoStatisticsCard()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Abstraction;
|
namespace Snap.Hutao.ViewModel.Abstraction;
|
||||||
|
|
||||||
@@ -27,19 +28,31 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsViewDisposed { get; set; }
|
public bool IsViewDisposed { get; set; }
|
||||||
|
|
||||||
|
protected TaskCompletionSource<bool> Initialization { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步初始化UI
|
/// 异步初始化UI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
[Command("OpenUICommand")]
|
[Command("OpenUICommand")]
|
||||||
protected abstract Task OpenUIAsync();
|
protected virtual async Task OpenUIAsync()
|
||||||
|
{
|
||||||
|
// Set value on UI thread
|
||||||
|
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
|
||||||
|
Initialization.TrySetResult(IsInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual ValueTask<bool> InitializeUIAsync()
|
||||||
|
{
|
||||||
|
return ValueTask.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保证 using scope 内的代码运行完成
|
/// 保证 using scope 内的代码运行完成
|
||||||
/// 防止 视图资源被回收
|
/// 防止 视图资源被回收
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>解除执行限制</returns>
|
/// <returns>解除执行限制</returns>
|
||||||
protected async Task<IDisposable> EnterCriticalExecutionAsync()
|
protected async ValueTask<IDisposable> EnterCriticalExecutionAsync()
|
||||||
{
|
{
|
||||||
ThrowIfViewDisposed();
|
ThrowIfViewDisposed();
|
||||||
IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false);
|
IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false);
|
||||||
@@ -55,7 +68,7 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
|||||||
{
|
{
|
||||||
if (IsViewDisposed)
|
if (IsViewDisposed)
|
||||||
{
|
{
|
||||||
throw new OperationCanceledException(SH.ViewModelViewDisposedOperationCancel);
|
ThrowHelper.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,19 +10,12 @@ namespace Snap.Hutao.ViewModel.Abstraction;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 简化的视图模型抽象类
|
/// 简化的视图模型抽象类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ConstructorGenerated]
|
||||||
internal abstract partial class ViewModelSlim : ObservableObject
|
internal abstract partial class ViewModelSlim : ObservableObject
|
||||||
{
|
{
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
private bool isInitialized;
|
private bool isInitialized;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的简化的视图模型抽象类
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
public ViewModelSlim(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
ServiceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否初始化完成
|
/// 是否初始化完成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -31,7 +24,7 @@ internal abstract partial class ViewModelSlim : ObservableObject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 服务提供器
|
/// 服务提供器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected IServiceProvider ServiceProvider { get; }
|
protected IServiceProvider ServiceProvider { get => serviceProvider; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打开界面执行
|
/// 打开界面执行
|
||||||
@@ -48,19 +41,11 @@ internal abstract partial class ViewModelSlim : ObservableObject
|
|||||||
/// 简化的视图模型抽象类
|
/// 简化的视图模型抽象类
|
||||||
/// 可导航
|
/// 可导航
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TPage">页面类型</typeparam>
|
/// <typeparam name="TPage">要导航到的页面类型</typeparam>
|
||||||
|
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||||
internal abstract partial class ViewModelSlim<TPage> : ViewModelSlim
|
internal abstract partial class ViewModelSlim<TPage> : ViewModelSlim
|
||||||
where TPage : Page
|
where TPage : Page
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的简化的视图模型抽象类
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
public ViewModelSlim(IServiceProvider serviceProvider)
|
|
||||||
: base(serviceProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导航到指定的页面类型
|
/// 导航到指定的页面类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -22,28 +22,37 @@ internal static class AchievementFinishPercent
|
|||||||
int totalFinished = 0;
|
int totalFinished = 0;
|
||||||
int totalCount = 0;
|
int totalCount = 0;
|
||||||
|
|
||||||
if (viewModel.Achievements is AdvancedCollectionView achievements)
|
if (viewModel.Achievements is { } achievements)
|
||||||
{
|
{
|
||||||
if (viewModel.AchievementGoals is List<AchievementGoalView> achievementGoals)
|
if (viewModel.AchievementGoals is { } achievementGoals)
|
||||||
{
|
{
|
||||||
Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.Create);
|
Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.From);
|
||||||
foreach (AchievementView achievement in achievements.SourceCollection.Cast<AchievementView>())
|
|
||||||
{
|
|
||||||
// Make the state update as fast as possible
|
|
||||||
ref AchievementGoalStatistics stat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
|
|
||||||
|
|
||||||
stat.TotalCount += 1;
|
// Fast path
|
||||||
totalCount += 1;
|
if (achievements.SourceCollection is List<AchievementView> list)
|
||||||
if (achievement.IsChecked)
|
{
|
||||||
|
foreach (ref readonly AchievementView achievement in CollectionsMarshal.AsSpan(list))
|
||||||
{
|
{
|
||||||
stat.Finished += 1;
|
// Make the state update as fast as possible
|
||||||
totalFinished += 1;
|
ref AchievementGoalStatistics stat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
|
||||||
|
|
||||||
|
stat.TotalCount += 1;
|
||||||
|
totalCount += 1;
|
||||||
|
if (achievement.IsChecked)
|
||||||
|
{
|
||||||
|
stat.Finished += 1;
|
||||||
|
totalFinished += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Must.NeverHappen("AchievementViewModel.Achievements.SourceCollection 应为 List<AchievementView>");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (AchievementGoalStatistics statistics in counter.Values)
|
foreach (AchievementGoalStatistics statistics in counter.Values)
|
||||||
{
|
{
|
||||||
statistics.AchievementGoal.UpdateFinishPercent(statistics);
|
statistics.AchievementGoal.UpdateFinishDescriptionAndPercent(statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.FinishDescription = AchievementStatistics.Format(totalFinished, totalCount, out _);
|
viewModel.FinishDescription = AchievementStatistics.Format(totalFinished, totalCount, out _);
|
||||||
|
|||||||
@@ -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 BindingAchievementGoal = Snap.Hutao.ViewModel.Achievement.AchievementGoalView;
|
using BindingAchievementGoal = Snap.Hutao.ViewModel.Achievement.AchievementGoalView;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Achievement;
|
namespace Snap.Hutao.ViewModel.Achievement;
|
||||||
@@ -9,7 +10,7 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
|||||||
/// 成就分类统计
|
/// 成就分类统计
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal struct AchievementGoalStatistics
|
internal struct AchievementGoalStatistics : IMappingFrom<AchievementGoalStatistics, BindingAchievementGoal>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成就分类
|
/// 成就分类
|
||||||
@@ -40,7 +41,7 @@ internal struct AchievementGoalStatistics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="goal">分类</param>
|
/// <param name="goal">分类</param>
|
||||||
/// <returns>新的成就分类统计</returns>
|
/// <returns>新的成就分类统计</returns>
|
||||||
public static AchievementGoalStatistics Create(BindingAchievementGoal goal)
|
public static AchievementGoalStatistics From(BindingAchievementGoal goal)
|
||||||
{
|
{
|
||||||
return new(goal);
|
return new(goal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Snap.Hutao.Core.Abstraction;
|
||||||
using Snap.Hutao.Model;
|
using Snap.Hutao.Model;
|
||||||
using Snap.Hutao.Model.Metadata.Achievement;
|
using Snap.Hutao.Model.Metadata.Achievement;
|
||||||
using Snap.Hutao.Model.Metadata.Converter;
|
using Snap.Hutao.Model.Metadata.Converter;
|
||||||
@@ -13,7 +14,7 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
|||||||
/// 绑定成就分类
|
/// 绑定成就分类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
internal sealed class AchievementGoalView : ObservableObject, INameIcon, IMappingFrom<AchievementGoalView, AchievementGoal>
|
||||||
{
|
{
|
||||||
private double finishPercent;
|
private double finishPercent;
|
||||||
private string? finishDescription;
|
private string? finishDescription;
|
||||||
@@ -22,7 +23,7 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
|||||||
/// 构造一个新的成就分类
|
/// 构造一个新的成就分类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="goal">分类</param>
|
/// <param name="goal">分类</param>
|
||||||
public AchievementGoalView(AchievementGoal goal)
|
private AchievementGoalView(AchievementGoal goal)
|
||||||
{
|
{
|
||||||
Id = goal.Id;
|
Id = goal.Id;
|
||||||
Order = goal.Order;
|
Order = goal.Order;
|
||||||
@@ -60,26 +61,18 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? FinishDescription { get => finishDescription; set => SetProperty(ref finishDescription, value); }
|
public string? FinishDescription { get => finishDescription; set => SetProperty(ref finishDescription, value); }
|
||||||
|
|
||||||
/// <summary>
|
public static AchievementGoalView From(AchievementGoal source)
|
||||||
/// 创建新的列表
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="goals">目标</param>
|
|
||||||
/// <returns>列表</returns>
|
|
||||||
public static List<AchievementGoalView> List(List<AchievementGoal> goals)
|
|
||||||
{
|
{
|
||||||
return goals
|
return new(source);
|
||||||
.OrderBy(goal => goal.Order)
|
|
||||||
.Select(goal => new AchievementGoalView(goal))
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新进度
|
/// 更新进度
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="statistics">统计</param>
|
/// <param name="statistics">统计</param>
|
||||||
public void UpdateFinishPercent(AchievementGoalStatistics statistics)
|
public void UpdateFinishDescriptionAndPercent(AchievementGoalStatistics statistics)
|
||||||
{
|
{
|
||||||
UpdateFinishPercent(statistics.Finished, statistics.TotalCount);
|
UpdateFinishDescriptionAndPercent(statistics.Finished, statistics.TotalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,7 +80,7 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="finished">完成项</param>
|
/// <param name="finished">完成项</param>
|
||||||
/// <param name="count">总项</param>
|
/// <param name="count">总项</param>
|
||||||
private void UpdateFinishPercent(int finished, int count)
|
private void UpdateFinishDescriptionAndPercent(int finished, int count)
|
||||||
{
|
{
|
||||||
FinishDescription = AchievementStatistics.Format(finished, count, out double finishPercent);
|
FinishDescription = AchievementStatistics.Format(finished, count, out double finishPercent);
|
||||||
FinishPercent = finishPercent;
|
FinishPercent = finishPercent;
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ internal sealed partial class AchievementImporter
|
|||||||
/// 从剪贴板导入
|
/// 从剪贴板导入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>是否导入成功</returns>
|
/// <returns>是否导入成功</returns>
|
||||||
public async Task<bool> FromClipboardAsync()
|
public async ValueTask<bool> FromClipboardAsync()
|
||||||
{
|
{
|
||||||
if (achievementService.CurrentArchive is EntityAchievementArchive archive)
|
if (achievementService.CurrentArchive is { } archive)
|
||||||
{
|
{
|
||||||
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is UIAF uiaf)
|
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is { } uiaf)
|
||||||
{
|
{
|
||||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -58,9 +58,9 @@ internal sealed partial class AchievementImporter
|
|||||||
/// 从文件导入
|
/// 从文件导入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>是否导入成功</returns>
|
/// <returns>是否导入成功</returns>
|
||||||
public async Task<bool> FromFileAsync()
|
public async ValueTask<bool> FromFileAsync()
|
||||||
{
|
{
|
||||||
if (achievementService.CurrentArchive is EntityAchievementArchive archive)
|
if (achievementService.CurrentArchive is { } archive)
|
||||||
{
|
{
|
||||||
ValueResult<bool, ValueFile> pickerResult = await serviceProvider
|
ValueResult<bool, ValueFile> pickerResult = await serviceProvider
|
||||||
.GetRequiredService<IPickerFactory>()
|
.GetRequiredService<IPickerFactory>()
|
||||||
@@ -90,7 +90,7 @@ internal sealed partial class AchievementImporter
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -106,28 +106,30 @@ internal sealed partial class AchievementImporter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf)
|
private async ValueTask<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf)
|
||||||
{
|
{
|
||||||
if (uiaf.IsCurrentVersionSupported())
|
if (uiaf.IsCurrentVersionSupported())
|
||||||
{
|
{
|
||||||
// ContentDialog must be created by main thread.
|
// ContentDialog must be created by main thread.
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
AchievementImportDialog importDialog = serviceProvider.CreateInstance<AchievementImportDialog>(uiaf);
|
AchievementImportDialog importDialog = serviceProvider.CreateInstance<AchievementImportDialog>(uiaf);
|
||||||
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(true);
|
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
if (isOk)
|
if (isOk)
|
||||||
{
|
{
|
||||||
ImportResult result;
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
ContentDialog dialog = await serviceProvider.GetRequiredService<IContentDialogFactory>()
|
ContentDialog dialog = await serviceProvider
|
||||||
|
.GetRequiredService<IContentDialogFactory>()
|
||||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
ImportResult result;
|
||||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
infoBarService.Success(result.ToString());
|
infoBarService.Success($"{result}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ internal sealed class AchievementStatistics
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格式化完成进度
|
/// 格式化完成进度
|
||||||
/// "xxx/yyy - zz.zz%"
|
/// "xxx/yyy - z.zz%"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="finished">完成的成就个数</param>
|
/// <param name="finished">完成的成就个数</param>
|
||||||
/// <param name="totalCount">总个数</param>
|
/// <param name="totalCount">总个数</param>
|
||||||
|
|||||||
@@ -53,19 +53,11 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata<Mo
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref isChecked, value))
|
if (SetProperty(ref isChecked, value))
|
||||||
{
|
{
|
||||||
// Only update state when checked
|
(Entity.Status, Entity.Time) = value
|
||||||
if (value)
|
? (AchievementStatus.STATUS_REWARD_TAKEN, DateTimeOffset.Now)
|
||||||
{
|
: (AchievementStatus.STATUS_FINISHED, default);
|
||||||
Entity.Status = AchievementStatus.STATUS_REWARD_TAKEN;
|
|
||||||
Entity.Time = DateTimeOffset.Now;
|
OnPropertyChanged(nameof(Time));
|
||||||
OnPropertyChanged(nameof(Time));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Entity.Status = AchievementStatus.STATUS_UNFINISHED;
|
|
||||||
Entity.Time = default;
|
|
||||||
OnPropertyChanged(nameof(Time));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
private static readonly SortDescription UncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending);
|
private static readonly SortDescription UncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending);
|
||||||
private static readonly SortDescription CompletionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
|
private static readonly SortDescription CompletionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
|
||||||
|
|
||||||
private readonly TaskCompletionSource<bool> openUITaskCompletionSource;
|
|
||||||
private readonly IContentDialogFactory contentDialogFactory;
|
private readonly IContentDialogFactory contentDialogFactory;
|
||||||
private readonly AchievementImporter achievementImporter;
|
private readonly AchievementImporter achievementImporter;
|
||||||
private readonly IAchievementService achievementService;
|
private readonly IAchievementService achievementService;
|
||||||
@@ -101,9 +100,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
get => selectedAchievementGoal;
|
get => selectedAchievementGoal;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
SetProperty(ref selectedAchievementGoal, value);
|
if (SetProperty(ref selectedAchievementGoal, value))
|
||||||
SearchText = string.Empty;
|
{
|
||||||
UpdateAchievementsFilterByGoal(value);
|
SearchText = string.Empty;
|
||||||
|
UpdateAchievementsFilterByGoal(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> ReceiveAsync(INavigationData data)
|
public async ValueTask<bool> ReceiveAsync(INavigationData data)
|
||||||
{
|
{
|
||||||
if (await openUITaskCompletionSource.Task.ConfigureAwait(false))
|
if (await Initialization.Task.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
if (data.Data is Activation.ImportUIAFFromClipboard)
|
if (data.Data is Activation.ImportUIAFFromClipboard)
|
||||||
{
|
{
|
||||||
@@ -149,12 +150,9 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
protected override async ValueTask<bool> InitializeUIAsync()
|
||||||
protected override async Task OpenUIAsync()
|
|
||||||
{
|
{
|
||||||
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
|
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||||
|
|
||||||
if (metaInitialized)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -167,33 +165,29 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
.GetAchievementGoalsAsync(CancellationToken)
|
.GetAchievementGoalsAsync(CancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
sortedGoals = AchievementGoalView.List(goals);
|
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||||
archives = achievementService.ArchiveCollection;
|
archives = achievementService.ArchiveCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
|
||||||
AchievementGoals = sortedGoals;
|
AchievementGoals = sortedGoals;
|
||||||
Archives = archives;
|
Archives = archives;
|
||||||
SelectedArchive = achievementService.CurrentArchive;
|
SelectedArchive = achievementService.CurrentArchive;
|
||||||
|
return true;
|
||||||
IsInitialized = true;
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// User canceled the loading operation,
|
|
||||||
// Indicate initialization not succeed.
|
|
||||||
openUITaskCompletionSource.TrySetResult(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openUITaskCompletionSource.TrySetResult(metaInitialized);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("AddArchiveCommand")]
|
[Command("AddArchiveCommand")]
|
||||||
private async Task AddArchiveAsync()
|
private async Task AddArchiveAsync()
|
||||||
{
|
{
|
||||||
if (Archives != null)
|
if (Archives is null)
|
||||||
{
|
{
|
||||||
// ContentDialog must be created by main thread.
|
// ContentDialog must be created by main thread.
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
@@ -212,10 +206,10 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
infoBarService.Success(string.Format(SH.ViewModelAchievementArchiveAdded, name));
|
infoBarService.Success(string.Format(SH.ViewModelAchievementArchiveAdded, name));
|
||||||
break;
|
break;
|
||||||
case ArchiveAddResult.InvalidName:
|
case ArchiveAddResult.InvalidName:
|
||||||
infoBarService.Information(SH.ViewModelAchievementArchiveInvalidName);
|
infoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||||
break;
|
break;
|
||||||
case ArchiveAddResult.AlreadyExists:
|
case ArchiveAddResult.AlreadyExists:
|
||||||
infoBarService.Information(string.Format(SH.ViewModelAchievementArchiveAlreadyExists, name));
|
infoBarService.Warning(string.Format(SH.ViewModelAchievementArchiveAlreadyExists, name));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw Must.NeverHappen();
|
throw Must.NeverHappen();
|
||||||
@@ -227,12 +221,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
[Command("RemoveArchiveCommand")]
|
[Command("RemoveArchiveCommand")]
|
||||||
private async Task RemoveArchiveAsync()
|
private async Task RemoveArchiveAsync()
|
||||||
{
|
{
|
||||||
if (Archives != null && SelectedArchive != null)
|
if (Archives is not null && SelectedArchive is not null)
|
||||||
{
|
{
|
||||||
|
string title = string.Format(SH.ViewModelAchievementRemoveArchiveTitle, SelectedArchive.Name);
|
||||||
|
string content = SH.ViewModelAchievementRemoveArchiveContent;
|
||||||
ContentDialogResult result = await contentDialogFactory
|
ContentDialogResult result = await contentDialogFactory
|
||||||
.CreateForConfirmCancelAsync(
|
.CreateForConfirmCancelAsync(title, content)
|
||||||
string.Format(SH.ViewModelAchievementRemoveArchiveTitle, SelectedArchive.Name),
|
|
||||||
SH.ViewModelAchievementRemoveArchiveContent)
|
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == ContentDialogResult.Primary)
|
if (result == ContentDialogResult.Primary)
|
||||||
@@ -258,8 +252,9 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
[Command("ExportAsUIAFToFileCommand")]
|
[Command("ExportAsUIAFToFileCommand")]
|
||||||
private async Task ExportAsUIAFToFileAsync()
|
private async Task ExportAsUIAFToFileAsync()
|
||||||
{
|
{
|
||||||
if (SelectedArchive != null && Achievements != null)
|
if (SelectedArchive is not null && Achievements is not null)
|
||||||
{
|
{
|
||||||
|
string fileName = $"{achievementService.CurrentArchive?.Name}.json";
|
||||||
Dictionary<string, IList<string>> fileTypes = new()
|
Dictionary<string, IList<string>> fileTypes = new()
|
||||||
{
|
{
|
||||||
[SH.ViewModelAchievementExportFileType] = ".json".Enumerate().ToList(),
|
[SH.ViewModelAchievementExportFileType] = ".json".Enumerate().ToList(),
|
||||||
@@ -267,11 +262,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
|
|
||||||
FileSavePicker picker = serviceProvider
|
FileSavePicker picker = serviceProvider
|
||||||
.GetRequiredService<IPickerFactory>()
|
.GetRequiredService<IPickerFactory>()
|
||||||
.GetFileSavePicker(
|
.GetFileSavePicker(PickerLocationId.Desktop, fileName, SH.FilePickerExportCommit, fileTypes);
|
||||||
PickerLocationId.Desktop,
|
|
||||||
$"{achievementService.CurrentArchive?.Name}.json",
|
|
||||||
SH.FilePickerExportCommit,
|
|
||||||
fileTypes);
|
|
||||||
|
|
||||||
(bool isPickerOk, ValueFile file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
|
(bool isPickerOk, ValueFile file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
|
||||||
if (isPickerOk)
|
if (isPickerOk)
|
||||||
@@ -294,7 +285,8 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
{
|
{
|
||||||
if (await achievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
if (await achievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await UpdateAchievementsAsync(achievementService.CurrentArchive!).ConfigureAwait(false);
|
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive);
|
||||||
|
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,13 +295,15 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
{
|
{
|
||||||
if (await achievementImporter.FromFileAsync().ConfigureAwait(false))
|
if (await achievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await UpdateAchievementsAsync(achievementService.CurrentArchive!).ConfigureAwait(false);
|
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive);
|
||||||
|
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateAchievementsAsync(EntityAchievementArchive? archive)
|
private async ValueTask UpdateAchievementsAsync(EntityAchievementArchive? archive)
|
||||||
{
|
{
|
||||||
if (archive == null)
|
// TODO: immediately clear values
|
||||||
|
if (archive is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -319,8 +313,8 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
Achievements = new(combined, true); // Assemble achievements on the UI thread.
|
|
||||||
|
|
||||||
|
Achievements = new(combined, true);
|
||||||
UpdateAchievementsFinishPercent();
|
UpdateAchievementsFinishPercent();
|
||||||
UpdateAchievementsFilterByGoal(SelectedAchievementGoal);
|
UpdateAchievementsFilterByGoal(SelectedAchievementGoal);
|
||||||
UpdateAchievementsSort();
|
UpdateAchievementsSort();
|
||||||
@@ -331,13 +325,13 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
combined = achievementService.GetAchievementViews(archive, achievements);
|
combined = achievementService.GetAchievementViewList(archive, achievements);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||||
{
|
{
|
||||||
combined = default;
|
|
||||||
infoBarService.Error(ex);
|
infoBarService.Error(ex);
|
||||||
|
combined = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +339,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
[Command("SortUncompletedSwitchCommand")]
|
[Command("SortUncompletedSwitchCommand")]
|
||||||
private void UpdateAchievementsSort()
|
private void UpdateAchievementsSort()
|
||||||
{
|
{
|
||||||
if (Achievements != null)
|
if (Achievements is not null)
|
||||||
{
|
{
|
||||||
if (IsUncompletedItemsFirst)
|
if (IsUncompletedItemsFirst)
|
||||||
{
|
{
|
||||||
@@ -361,15 +355,16 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
|
|
||||||
private void UpdateAchievementsFilterByGoal(AchievementGoalView? goal)
|
private void UpdateAchievementsFilterByGoal(AchievementGoalView? goal)
|
||||||
{
|
{
|
||||||
if (Achievements != null)
|
if (Achievements is not null)
|
||||||
{
|
{
|
||||||
if (goal == null)
|
if (goal is null)
|
||||||
{
|
{
|
||||||
Achievements.Filter = null;
|
Achievements.Filter = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Achievements.Filter = (object o) => o is AchievementView view && view.Inner.Goal == goal.Id;
|
Model.Primitive.AchievementGoalId goalId = goal.Id;
|
||||||
|
Achievements.Filter = (object o) => o is AchievementView view && view.Inner.Goal == goalId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,15 +372,15 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
[Command("SearchAchievementCommand")]
|
[Command("SearchAchievementCommand")]
|
||||||
private void UpdateAchievementsFilterBySearch(string? search)
|
private void UpdateAchievementsFilterBySearch(string? search)
|
||||||
{
|
{
|
||||||
if (Achievements != null)
|
if (Achievements is not null)
|
||||||
{
|
{
|
||||||
SetProperty(ref selectedAchievementGoal, null);
|
SetProperty(ref selectedAchievementGoal, null);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(search))
|
if (!string.IsNullOrEmpty(search))
|
||||||
{
|
{
|
||||||
if (search.Length == 5 && int.TryParse(search, out int achievementId))
|
if (uint.TryParse(search, out uint achievementId))
|
||||||
{
|
{
|
||||||
Achievements.Filter = obj => ((AchievementView)obj).Inner.Id.Value == achievementId;
|
Achievements.Filter = obj => ((AchievementView)obj).Inner.Id == achievementId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -408,7 +403,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
|||||||
[Command("SaveAchievementCommand")]
|
[Command("SaveAchievementCommand")]
|
||||||
private void SaveAchievement(AchievementView? achievement)
|
private void SaveAchievement(AchievementView? achievement)
|
||||||
{
|
{
|
||||||
if (achievement != null)
|
if (achievement is not null)
|
||||||
{
|
{
|
||||||
achievementService.SaveAchievement(achievement);
|
achievementService.SaveAchievement(achievement);
|
||||||
UpdateAchievementsFinishPercent();
|
UpdateAchievementsFinishPercent();
|
||||||
|
|||||||
@@ -10,20 +10,12 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 简化的成就视图模型
|
/// 简化的成就视图模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||||
[Injection(InjectAs.Transient)]
|
[Injection(InjectAs.Transient)]
|
||||||
internal sealed class AchievementViewModelSlim : Abstraction.ViewModelSlim<View.Page.AchievementPage>
|
internal sealed partial class AchievementViewModelSlim : Abstraction.ViewModelSlim<View.Page.AchievementPage>
|
||||||
{
|
{
|
||||||
private List<AchievementStatistics>? statisticsList;
|
private List<AchievementStatistics>? statisticsList;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的简化的成就视图模型
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
public AchievementViewModelSlim(IServiceProvider serviceProvider)
|
|
||||||
: base(serviceProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 统计列表
|
/// 统计列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal sealed class AvatarProperty : INameIcon
|
internal sealed class AvatarProperty : INameIcon
|
||||||
{
|
{
|
||||||
|
// TODO: use FrozenDictionary
|
||||||
private static readonly ImmutableDictionary<FightProperty, Uri> PropertyIcons = new Dictionary<FightProperty, Uri>()
|
private static readonly ImmutableDictionary<FightProperty, Uri> PropertyIcons = new Dictionary<FightProperty, Uri>()
|
||||||
{
|
{
|
||||||
[FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CDReduce.png").ToUri(),
|
[FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CDReduce.png").ToUri(),
|
||||||
|
|||||||
@@ -22,17 +22,16 @@ using Snap.Hutao.Web.Response;
|
|||||||
using Windows.Graphics.Imaging;
|
using Windows.Graphics.Imaging;
|
||||||
using Windows.Storage.Streams;
|
using Windows.Storage.Streams;
|
||||||
using Windows.UI;
|
using Windows.UI;
|
||||||
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
using CalculatorAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
||||||
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
using CalculatorClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
||||||
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
using CalculatorConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
||||||
using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
|
using CalculatorItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
|
||||||
using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
|
using CalculatorItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.AvatarProperty;
|
namespace Snap.Hutao.ViewModel.AvatarProperty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 角色属性视图模型
|
/// 角色属性视图模型
|
||||||
/// TODO: support page unload as cancellation
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
@@ -65,51 +64,45 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
protected override async ValueTask<bool> InitializeUIAsync()
|
||||||
protected override Task OpenUIAsync()
|
|
||||||
{
|
{
|
||||||
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
||||||
{
|
{
|
||||||
return RefreshCoreAsync(userAndUid, RefreshOption.None, CancellationToken);
|
await RefreshCoreAsync(userAndUid, RefreshOption.None, CancellationToken).ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("RefreshFromEnkaApiCommand")]
|
[Command("RefreshFromEnkaApiCommand")]
|
||||||
private Task RefreshByEnkaApiAsync()
|
private async Task RefreshByEnkaApiAsync()
|
||||||
{
|
{
|
||||||
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
||||||
{
|
{
|
||||||
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken);
|
await RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("RefreshFromHoyolabGameRecordCommand")]
|
[Command("RefreshFromHoyolabGameRecordCommand")]
|
||||||
private Task RefreshByHoyolabGameRecordAsync()
|
private async Task RefreshByHoyolabGameRecordAsync()
|
||||||
{
|
{
|
||||||
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
||||||
{
|
{
|
||||||
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken);
|
await RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("RefreshFromHoyolabCalculateCommand")]
|
[Command("RefreshFromHoyolabCalculateCommand")]
|
||||||
private Task RefreshByHoyolabCalculateAsync()
|
private async Task RefreshByHoyolabCalculateAsync()
|
||||||
{
|
{
|
||||||
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
||||||
{
|
{
|
||||||
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken);
|
await RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshCoreAsync(UserAndUid userAndUid, RefreshOption option, CancellationToken token)
|
private async ValueTask RefreshCoreAsync(UserAndUid userAndUid, RefreshOption option, CancellationToken token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -163,11 +156,11 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
|||||||
[Command("CultivateCommand")]
|
[Command("CultivateCommand")]
|
||||||
private async Task CultivateAsync(AvatarView? avatar)
|
private async Task CultivateAsync(AvatarView? avatar)
|
||||||
{
|
{
|
||||||
if (avatar != null)
|
if (avatar is not null)
|
||||||
{
|
{
|
||||||
if (userService.Current != null)
|
if (userService.Current is not null)
|
||||||
{
|
{
|
||||||
if (avatar.Weapon == null)
|
if (avatar.Weapon is null)
|
||||||
{
|
{
|
||||||
infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull);
|
infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull);
|
||||||
return;
|
return;
|
||||||
@@ -177,21 +170,21 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
|||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable());
|
CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable());
|
||||||
CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance<CultivatePromotionDeltaDialog>(options);
|
CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance<CultivatePromotionDeltaDialog>(options);
|
||||||
(bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
|
(bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
if (isOk)
|
if (isOk)
|
||||||
{
|
{
|
||||||
Response<CalcConsumption> consumptionResponse = await serviceProvider
|
Response<CalculatorConsumption> consumptionResponse = await serviceProvider
|
||||||
.GetRequiredService<CalcClient>()
|
.GetRequiredService<CalculatorClient>()
|
||||||
.ComputeAsync(userService.Current.Entity, delta)
|
.ComputeAsync(userService.Current.Entity, delta)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (consumptionResponse.IsOk())
|
if (consumptionResponse.IsOk())
|
||||||
{
|
{
|
||||||
ICultivationService cultivationService = serviceProvider.GetRequiredService<ICultivationService>();
|
ICultivationService cultivationService = serviceProvider.GetRequiredService<ICultivationService>();
|
||||||
CalcConsumption consumption = consumptionResponse.Data;
|
CalculatorConsumption consumption = consumptionResponse.Data;
|
||||||
|
|
||||||
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
List<CalculatorItem> items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
||||||
bool avatarSaved = await cultivationService
|
bool avatarSaved = await cultivationService
|
||||||
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
|
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@@ -229,7 +222,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
|||||||
[Command("ExportAsImageCommand")]
|
[Command("ExportAsImageCommand")]
|
||||||
private async Task ExportAsImageAsync(FrameworkElement? element)
|
private async Task ExportAsImageAsync(FrameworkElement? element)
|
||||||
{
|
{
|
||||||
if (element != null && element.IsLoaded)
|
if (element is { IsLoaded: true })
|
||||||
{
|
{
|
||||||
RenderTargetBitmap bitmap = new();
|
RenderTargetBitmap bitmap = new();
|
||||||
await bitmap.RenderAsync(element);
|
await bitmap.RenderAsync(element);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最大等级
|
/// 最大等级
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal uint MaxLevel { get => ((int)Quality) >= 3 ? 90U : 70U; }
|
internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ICalculableWeapon ToCalculable()
|
public ICalculableWeapon ToCalculable()
|
||||||
|
|||||||
@@ -23,18 +23,19 @@ internal sealed class ReliquarySetView
|
|||||||
{
|
{
|
||||||
ReliquarySets sets = reliquarySetRate.Item;
|
ReliquarySets sets = reliquarySetRate.Item;
|
||||||
|
|
||||||
if (sets.Count >= 1)
|
if (!sets.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
StringBuilder nameBuilder = new();
|
StringBuilder nameBuilder = new();
|
||||||
List<Uri> icons = new(2);
|
List<Uri> icons = new(2);
|
||||||
|
|
||||||
foreach (ReliquarySet set in CollectionsMarshal.AsSpan(sets))
|
foreach (ReliquarySet set in CollectionsMarshal.AsSpan(sets))
|
||||||
{
|
{
|
||||||
|
// TODO use set EquipAffixSetIds
|
||||||
Model.Metadata.Reliquary.ReliquarySet metaSet = idReliquarySetMap[set.EquipAffixId / 10];
|
Model.Metadata.Reliquary.ReliquarySet metaSet = idReliquarySetMap[set.EquipAffixId / 10];
|
||||||
|
|
||||||
if (nameBuilder.Length != 0)
|
if (nameBuilder.Length > 0)
|
||||||
{
|
{
|
||||||
nameBuilder.Append(Environment.NewLine);
|
nameBuilder.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
nameBuilder.Append(set.Count).Append('×').Append(metaSet.Name);
|
nameBuilder.Append(set.Count).Append('×').Append(metaSet.Name);
|
||||||
|
|||||||
@@ -73,8 +73,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async Task OpenUIAsync()
|
protected override async Task OpenUIAsync()
|
||||||
{
|
{
|
||||||
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
|
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||||
if (metaInitialized)
|
|
||||||
{
|
{
|
||||||
ObservableCollection<CultivateProject> projects = cultivationService.ProjectCollection;
|
ObservableCollection<CultivateProject> projects = cultivationService.ProjectCollection;
|
||||||
CultivateProject? selected = cultivationService.Current;
|
CultivateProject? selected = cultivationService.Current;
|
||||||
@@ -82,9 +81,12 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
Projects = projects;
|
Projects = projects;
|
||||||
SelectedProject = selected;
|
SelectedProject = selected;
|
||||||
|
IsInitialized = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsInitialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsInitialized = metaInitialized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("AddProjectCommand")]
|
[Command("AddProjectCommand")]
|
||||||
@@ -122,7 +124,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
[Command("RemoveProjectCommand")]
|
[Command("RemoveProjectCommand")]
|
||||||
private async Task RemoveProjectAsync(CultivateProject? project)
|
private async Task RemoveProjectAsync(CultivateProject? project)
|
||||||
{
|
{
|
||||||
if (project != null)
|
if (project is not null)
|
||||||
{
|
{
|
||||||
await cultivationService.RemoveProjectAsync(project).ConfigureAwait(false);
|
await cultivationService.RemoveProjectAsync(project).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -133,7 +135,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
|
|
||||||
private async Task UpdateEntryCollectionAsync(CultivateProject? project)
|
private async Task UpdateEntryCollectionAsync(CultivateProject? project)
|
||||||
{
|
{
|
||||||
if (project != null)
|
if (project is not null)
|
||||||
{
|
{
|
||||||
List<Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
|
List<Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
|
||||||
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||||
@@ -154,7 +156,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
[Command("RemoveEntryCommand")]
|
[Command("RemoveEntryCommand")]
|
||||||
private async Task RemoveEntryAsync(CultivateEntryView? entry)
|
private async Task RemoveEntryAsync(CultivateEntryView? entry)
|
||||||
{
|
{
|
||||||
if (entry != null)
|
if (entry is not null)
|
||||||
{
|
{
|
||||||
CultivateEntries!.Remove(entry);
|
CultivateEntries!.Remove(entry);
|
||||||
await cultivationService.RemoveCultivateEntryAsync(entry.EntryId).ConfigureAwait(false);
|
await cultivationService.RemoveCultivateEntryAsync(entry.EntryId).ConfigureAwait(false);
|
||||||
@@ -165,7 +167,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
[Command("SaveInventoryItemCommand")]
|
[Command("SaveInventoryItemCommand")]
|
||||||
private void SaveInventoryItem(InventoryItemView? inventoryItem)
|
private void SaveInventoryItem(InventoryItemView? inventoryItem)
|
||||||
{
|
{
|
||||||
if (inventoryItem != null)
|
if (inventoryItem is not null)
|
||||||
{
|
{
|
||||||
cultivationService.SaveInventoryItem(inventoryItem);
|
cultivationService.SaveInventoryItem(inventoryItem);
|
||||||
UpdateStatisticsItemsAsync().SafeForget();
|
UpdateStatisticsItemsAsync().SafeForget();
|
||||||
@@ -175,7 +177,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
[Command("FinishStateCommand")]
|
[Command("FinishStateCommand")]
|
||||||
private void UpdateFinishedState(CultivateItemView? item)
|
private void UpdateFinishedState(CultivateItemView? item)
|
||||||
{
|
{
|
||||||
if (item != null)
|
if (item is not null)
|
||||||
{
|
{
|
||||||
item.IsFinished = !item.IsFinished;
|
item.IsFinished = !item.IsFinished;
|
||||||
cultivationService.SaveCultivateItem(item);
|
cultivationService.SaveCultivateItem(item);
|
||||||
@@ -185,7 +187,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
|
|
||||||
private async Task UpdateStatisticsItemsAsync()
|
private async Task UpdateStatisticsItemsAsync()
|
||||||
{
|
{
|
||||||
if (SelectedProject != null)
|
if (SelectedProject is not null)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
CancellationToken token = StatisticsCancellationTokenSource.Register();
|
CancellationToken token = StatisticsCancellationTokenSource.Register();
|
||||||
@@ -208,7 +210,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
[Command("NavigateToPageCommand")]
|
[Command("NavigateToPageCommand")]
|
||||||
private void NavigateToPage(string? typeString)
|
private void NavigateToPage(string? typeString)
|
||||||
{
|
{
|
||||||
if (typeString != null)
|
if (typeString is not null)
|
||||||
{
|
{
|
||||||
serviceProvider
|
serviceProvider
|
||||||
.GetRequiredService<INavigationService>()
|
.GetRequiredService<INavigationService>()
|
||||||
|
|||||||
@@ -17,17 +17,17 @@ internal static class ItemHelper
|
|||||||
/// <returns>合并且排序好的列表</returns>
|
/// <returns>合并且排序好的列表</returns>
|
||||||
public static List<Item> Merge(List<Item>? left, List<Item>? right)
|
public static List<Item> Merge(List<Item>? left, List<Item>? right)
|
||||||
{
|
{
|
||||||
if (left == null && right == null)
|
if (left.IsNullOrEmpty() && right.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return new(0);
|
return new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (right == null)
|
if (right.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return left!;
|
return left!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left == null)
|
if (left.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return right!;
|
return right!;
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ internal static class ItemHelper
|
|||||||
|
|
||||||
foreach (Item item in right)
|
foreach (Item item in right)
|
||||||
{
|
{
|
||||||
if (result.SingleOrDefault(i => i.Id == item.Id) is Item existed)
|
if (result.SingleOrDefault(i => i.Id == item.Id) is { } existed)
|
||||||
{
|
{
|
||||||
existed.Num += item.Num;
|
existed.Num += item.Num;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user