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);
|
||||
|
||||
// TODO: 支持嵌套类
|
||||
string code = $$"""
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -61,12 +61,6 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
||||
|
||||
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);
|
||||
|
||||
bool resolveHttpClient = constructorInfo.HasNamedArgumentWith<bool>("ResolveHttpClient", value => value);
|
||||
@@ -79,10 +73,9 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
|
||||
namespace {{context2.Symbol.ContainingNamespace}};
|
||||
|
||||
[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}})
|
||||
{{(options.CallBaseConstructor? ": base(serviceProvider)" : string.Empty)}}
|
||||
public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}}){{(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)
|
||||
|
||||
@@ -12,4 +12,10 @@ internal static class SymbolDisplayFormats
|
||||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
|
||||
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
|
||||
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.Diagnostics;
|
||||
using Snap.Hutao.SourceGeneration.Primitive;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
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 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 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>()
|
||||
{
|
||||
@@ -34,6 +39,9 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
typeInternalDescriptor,
|
||||
readOnlyStructRefDescriptor,
|
||||
useValueTaskIfPossibleDescriptor,
|
||||
useIsNotNullPatternMatchingDescriptor,
|
||||
useIsNullPatternMatchingDescriptor,
|
||||
useIsPatternRecursiveMatchingDescriptor
|
||||
}.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
@@ -48,18 +56,25 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static void CompilationStart(CompilationStartAnalysisContext context)
|
||||
{
|
||||
SyntaxKind[] types = new SyntaxKind[]
|
||||
SyntaxKind[] types =
|
||||
{
|
||||
SyntaxKind.ClassDeclaration,
|
||||
SyntaxKind.InterfaceDeclaration,
|
||||
SyntaxKind.StructDeclaration,
|
||||
SyntaxKind.EnumDeclaration
|
||||
SyntaxKind.EnumDeclaration,
|
||||
};
|
||||
|
||||
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
|
||||
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
|
||||
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
|
||||
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)
|
||||
@@ -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)
|
||||
{
|
||||
return symbol.SpecialType switch
|
||||
|
||||
@@ -17,7 +17,7 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
if (Command != null && Command.CanExecute(CommandParameter))
|
||||
if (Command is not null && Command.CanExecute(CommandParameter))
|
||||
{
|
||||
Command.Execute(CommandParameter);
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await HideAsync(token).ConfigureAwait(true);
|
||||
|
||||
if (uri != null)
|
||||
if (uri is not null)
|
||||
{
|
||||
LoadedImageSurface? imageSurface = null;
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
@@ -147,7 +147,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
imageCache.Remove(uri);
|
||||
}
|
||||
|
||||
if (imageSurface != null)
|
||||
if (imageSurface is not null)
|
||||
{
|
||||
using (imageSurface)
|
||||
{
|
||||
@@ -221,7 +221,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ internal sealed class MonoChrome : CompositionImage
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
{
|
||||
if (backgroundBrush != null)
|
||||
if (backgroundBrush is not null)
|
||||
{
|
||||
SetBackgroundColor(backgroundBrush);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ internal class ScopedPage : Page
|
||||
/// <returns>任务</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ internal sealed class CommandLineBuilder
|
||||
/// <returns>命令行建造器</returns>
|
||||
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
|
||||
{
|
||||
return AppendIf(name, value != null, value);
|
||||
return AppendIf(name, value is not null, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
{
|
||||
@@ -23,16 +24,6 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
|
||||
private TEntity? current;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public ScopedDbCurrent(IServiceProvider serviceProvider)
|
||||
{
|
||||
messenger = serviceProvider.GetRequiredService<IMessenger>();
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的项
|
||||
/// </summary>
|
||||
@@ -54,9 +45,9 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
|
||||
// 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;
|
||||
dbSet.UpdateAndSave(current);
|
||||
@@ -67,7 +58,7 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
|
||||
current = value;
|
||||
|
||||
if (current != null)
|
||||
if (current is not null)
|
||||
{
|
||||
current.IsSelected = true;
|
||||
dbSet.UpdateAndSave(current);
|
||||
@@ -79,8 +70,8 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
where TEntityOnly : class, IEntityOnly<TEntity>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntityOnly>, new()
|
||||
@@ -90,16 +81,6 @@ internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
|
||||
private TEntityOnly? current;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public ScopedDbCurrent(IServiceProvider serviceProvider)
|
||||
{
|
||||
messenger = serviceProvider.GetRequiredService<IMessenger>();
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的项
|
||||
/// </summary>
|
||||
@@ -121,9 +102,9 @@ internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
|
||||
// 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;
|
||||
dbSet.UpdateAndSave(current.Entity);
|
||||
@@ -134,7 +115,7 @@ internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
|
||||
current = value;
|
||||
|
||||
if (current != null)
|
||||
if (current is not null)
|
||||
{
|
||||
current.Entity.IsSelected = true;
|
||||
dbSet.UpdateAndSave(current.Entity);
|
||||
|
||||
@@ -27,7 +27,7 @@ internal static class PickerExtension
|
||||
file = null;
|
||||
}
|
||||
|
||||
if (file != null)
|
||||
if (file is not null)
|
||||
{
|
||||
return new(true, file.Path);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ internal static class PickerExtension
|
||||
file = null;
|
||||
}
|
||||
|
||||
if (file != null)
|
||||
if (file is not null)
|
||||
{
|
||||
return new(true, file.Path);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ internal static class PickerExtension
|
||||
folder = null;
|
||||
}
|
||||
|
||||
if (folder != null)
|
||||
if (folder is not null)
|
||||
{
|
||||
return new(true, folder.Path);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ internal static class PickerExtension
|
||||
|
||||
private static void InfoBarWaringPickerException(Exception? exception)
|
||||
{
|
||||
if (exception != null)
|
||||
if (exception is not null)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<IInfoBarService>()
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed class DebugLogger : ILogger
|
||||
|
||||
message = $"{logLevel}: {message}";
|
||||
|
||||
if (exception != null)
|
||||
if (exception is not null)
|
||||
{
|
||||
message += Environment.NewLine + Environment.NewLine + exception;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ internal static class TypeNameHelper
|
||||
[return: NotNullIfNotNull(nameof(item))]
|
||||
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>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -92,6 +93,7 @@ internal static partial class EnumerableExtension
|
||||
/// <param name="list">列表</param>
|
||||
/// <param name="selector">选择器</param>
|
||||
/// <returns>新类型的列表</returns>
|
||||
[Pure]
|
||||
public static List<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, TResult> selector)
|
||||
{
|
||||
Span<TSource> span = CollectionsMarshal.AsSpan(list);
|
||||
|
||||
@@ -97,7 +97,7 @@ internal sealed class Achievement
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Achievement? other)
|
||||
{
|
||||
if (other == null)
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ internal sealed class UIAF
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v1.1";
|
||||
|
||||
private static readonly List<string> SupportedVersion = new()
|
||||
// TODO use FrozenSet
|
||||
private static readonly HashSet<string> SupportedVersion = new()
|
||||
{
|
||||
CurrentVersion,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ internal sealed class UIGF
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed class SkillDepot
|
||||
{
|
||||
get
|
||||
{
|
||||
if (compositeSkills == null)
|
||||
if (compositeSkills is null)
|
||||
{
|
||||
compositeSkills = new(Skills.Count + 1 + Inherents.Count);
|
||||
compositeSkills.AddRange(Skills);
|
||||
@@ -60,7 +60,7 @@ internal sealed class SkillDepot
|
||||
/// <returns>天赋列表</returns>
|
||||
public List<ProudableSkill> CompositeSkillsNoInherents()
|
||||
{
|
||||
if (compositeSkillsNoInherents == null)
|
||||
if (compositeSkillsNoInherents is null)
|
||||
{
|
||||
compositeSkillsNoInherents = new(Skills.Count + 1);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ internal sealed class AvatarNameCardPicConverter : ValueConverter<Avatar.Avatar?
|
||||
/// <returns>名片</returns>
|
||||
public static Uri AvatarToUri(Avatar.Avatar? avatar)
|
||||
{
|
||||
if (avatar == null)
|
||||
if (avatar is null)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed class ParameterFormat : IFormatProvider, ICustomFormatter
|
||||
case 'P':
|
||||
return string.Format($"{{0:P0}}", arg);
|
||||
case 'I':
|
||||
return arg == null ? "0" : ((IConvertible)arg).ToInt32(default).ToString();
|
||||
return arg is null ? "0" : ((IConvertible)arg).ToInt32(default).ToString();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -21,6 +21,11 @@ internal sealed class ReliquarySet
|
||||
/// </summary>
|
||||
public EquipAffixId EquipAffixId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 装备被动的被动Id
|
||||
/// </summary>
|
||||
public HashSet<ExtendedEquipAffixId> EquipAffixIds { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 套装名称
|
||||
/// </summary>
|
||||
|
||||
@@ -32,7 +32,12 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
|
||||
/// <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/>
|
||||
public ICalculableWeapon ToCalculable()
|
||||
|
||||
@@ -61,7 +61,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
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;
|
||||
@@ -85,7 +85,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
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;
|
||||
@@ -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)
|
||||
where T : class
|
||||
{
|
||||
if (storage != null)
|
||||
if (storage is not null)
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
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;
|
||||
@@ -139,7 +139,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
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;
|
||||
|
||||
@@ -70,7 +70,7 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
else if (entity != null && uiaf == null)
|
||||
else if (entity is not null && uiaf is null)
|
||||
{
|
||||
// skip
|
||||
continue;
|
||||
@@ -157,7 +157,7 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
else if (oldEntity != null && newEntity == null)
|
||||
else if (oldEntity is not null && newEntity is null)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed partial class AchievementService
|
||||
{
|
||||
get
|
||||
{
|
||||
if (archiveCollection == null)
|
||||
if (archiveCollection is null)
|
||||
{
|
||||
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
|
||||
CurrentArchive = archiveCollection.SelectedOrDefault();
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed partial class AchievementService : IAchievementService
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <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);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ internal interface IAchievementService
|
||||
/// <param name="archive">用户</param>
|
||||
/// <param name="metadata">元数据</param>
|
||||
/// <returns>整合的成就</returns>
|
||||
List<AchievementView> GetAchievementViews(EntityArchive archive, List<MetadataAchievement> metadata);
|
||||
List<AchievementView> GetAchievementViewList(EntityArchive archive, List<MetadataAchievement> metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 异步导入UIAF数据
|
||||
|
||||
@@ -13,17 +13,18 @@ namespace Snap.Hutao.Service;
|
||||
/// 应用程序选项
|
||||
/// 存储服务相关的选项
|
||||
/// </summary>
|
||||
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
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("Mica", BackdropType.Mica),
|
||||
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-Hant")),
|
||||
@@ -38,15 +39,6 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
private CultureInfo? currentCulture;
|
||||
private bool? isAdvancedLaunchOptionsEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的应用程序选项
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public AppOptions(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
@@ -68,7 +60,7 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
/// <summary>
|
||||
/// 所有支持的背景样式
|
||||
/// </summary>
|
||||
public List<NameValue<BackdropType>> BackdropTypes { get => supportedBackdropTypes; }
|
||||
public List<NameValue<BackdropType>> BackdropTypes { get => SupportedBackdropTypesInner; }
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型 默认 Mica
|
||||
@@ -82,7 +74,7 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
/// <summary>
|
||||
/// 所有支持的语言
|
||||
/// </summary>
|
||||
public List<NameValue<string>> Cultures { get => supportedCultures; }
|
||||
public List<NameValue<string>> Cultures { get => SupportedCulturesInner; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化前的语言
|
||||
|
||||
@@ -178,7 +178,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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 };
|
||||
transformer.Transform(ref avatarInfo, source);
|
||||
@@ -197,7 +197,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AddOrUpdateAvatarInfo(ModelAvatarInfo? entity, string uid, AppDbContext appDbContext, EnkaAvatarInfo webInfo)
|
||||
{
|
||||
if (entity == null)
|
||||
if (entity is null)
|
||||
{
|
||||
entity = ModelAvatarInfo.From(uid, webInfo);
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
|
||||
@@ -41,7 +41,7 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
|
||||
{
|
||||
EnkaResponse? resp = await GetEnkaResponseAsync(userAndUid.Uid, token).ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (resp == null)
|
||||
if (resp is null)
|
||||
{
|
||||
return new(RefreshResult.APIUnavailable, default);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ internal sealed class SummaryAvatarFactory
|
||||
WeaponStat? subStat = equip.Flat.WeaponStats?.ElementAtOrDefault(1);
|
||||
|
||||
NameDescription subProperty;
|
||||
if (subStat == null)
|
||||
if (subStat is null)
|
||||
{
|
||||
subProperty = new(string.Empty, string.Empty);
|
||||
}
|
||||
@@ -148,7 +148,7 @@ internal sealed class SummaryAvatarFactory
|
||||
// EquipBase
|
||||
Level = $"Lv.{equip.Weapon.Level.Value}",
|
||||
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
|
||||
Id = weapon.Id,
|
||||
|
||||
@@ -53,7 +53,7 @@ internal static class SummaryHelper
|
||||
|
||||
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
|
||||
|
||||
if (proudSkillExtraLevelMap != null)
|
||||
if (proudSkillExtraLevelMap is not null)
|
||||
{
|
||||
foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed partial class CultivationService
|
||||
{
|
||||
get
|
||||
{
|
||||
if (projects == null)
|
||||
if (projects is null)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
|
||||
@@ -177,7 +177,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
.GetCultivateEntryByProjectIdAndItemIdAsync(Current.InnerId, itemId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (entry == null)
|
||||
if (entry is null)
|
||||
{
|
||||
entry = CultivateEntry.From(Current.InnerId, type, itemId);
|
||||
await cultivationDbService.InsertCultivateEntryAsync(entry).ConfigureAwait(false);
|
||||
|
||||
@@ -44,7 +44,7 @@ internal sealed class DailyNoteNotificationOperation
|
||||
/// <returns>任务</returns>
|
||||
public async ValueTask SendAsync()
|
||||
{
|
||||
if (entry.DailyNote == null)
|
||||
if (entry.DailyNote is null)
|
||||
{
|
||||
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]);
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
if (value is not null)
|
||||
{
|
||||
if (scheduleTaskInterop.RegisterForDailyNoteRefresh(value.Value))
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntryCollectionAsync()
|
||||
{
|
||||
if (entries == null)
|
||||
if (entries is null)
|
||||
{
|
||||
await RefreshDailyNotesAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ internal static class GachaArchiveOperation
|
||||
{
|
||||
archive = archives.SingleOrDefault(a => a.Uid == uid);
|
||||
|
||||
if (archive == null)
|
||||
if (archive is null)
|
||||
{
|
||||
GachaArchive created = GachaArchive.From(uid);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
|
||||
@@ -97,7 +97,7 @@ internal struct GachaLogFetchContext
|
||||
/// <param name="gachaLogDbService">祈愿记录数据库服务</param>
|
||||
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);
|
||||
}
|
||||
@@ -146,7 +146,7 @@ internal struct GachaLogFetchContext
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// 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);
|
||||
TargetArchive.SaveItems(saveContext);
|
||||
|
||||
@@ -69,7 +69,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
return new(false, null);
|
||||
}
|
||||
|
||||
if (archive == null)
|
||||
if (archive is null)
|
||||
{
|
||||
archive = GachaArchive.From(uid);
|
||||
await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false);
|
||||
@@ -121,7 +121,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
EndIds endIds = new();
|
||||
foreach (GachaConfigType type in GachaLog.QueryTypes)
|
||||
{
|
||||
if (archive != null)
|
||||
if (archive is not null)
|
||||
{
|
||||
endIds[type] = await gachaLogDbService
|
||||
.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);
|
||||
|
||||
if (result != null)
|
||||
if (result is not null)
|
||||
{
|
||||
CurrentArchive = result;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ internal sealed partial class GameService : IGameService
|
||||
ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex);
|
||||
}
|
||||
|
||||
if (account == null)
|
||||
if (account is null)
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
@@ -42,7 +42,7 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator
|
||||
.FirstOrDefault(p => p.Key == "game_install_path")?.Value;
|
||||
}
|
||||
|
||||
if (escapedPath != null)
|
||||
if (escapedPath is not null)
|
||||
{
|
||||
string gamePath = Path.Combine(Unescape(escapedPath), GameConstants.YuanShenFileName);
|
||||
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\原神"))
|
||||
{
|
||||
if (uninstallKey != null)
|
||||
if (uninstallKey is not null)
|
||||
{
|
||||
if (uninstallKey.GetValue(key) is string path)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Snap.Hutao.Service.Game.Package;
|
||||
internal sealed partial class PackageConverter
|
||||
{
|
||||
private const string PackageVersion = "pkg_version";
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly HttpClient httpClient;
|
||||
@@ -97,7 +96,7 @@ internal sealed partial class PackageConverter
|
||||
string sdkVersion = Path.Combine(gameFolder, "sdk_pkg_version");
|
||||
|
||||
// Only bilibili's sdk is not null
|
||||
if (resource.Sdk != null)
|
||||
if (resource.Sdk is not null)
|
||||
{
|
||||
// TODO: verify sdk md5
|
||||
if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup))
|
||||
@@ -132,7 +131,7 @@ internal sealed partial class PackageConverter
|
||||
FileOperation.Move(sdkVersion, sdkVersionBackup, true);
|
||||
}
|
||||
|
||||
if (resource.DeprecatedFiles != null)
|
||||
if (resource.DeprecatedFiles is not null)
|
||||
{
|
||||
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)(?=/)")]
|
||||
private static partial Regex DataFolderRegex();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal static class RegistryInterop
|
||||
/// <returns>账号是否设置</returns>
|
||||
public static bool Set(GameAccount? account)
|
||||
{
|
||||
if (account != null)
|
||||
if (account is not null)
|
||||
{
|
||||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(account.MihoyoSDK));
|
||||
string path = $"HKCU:{GenshinKey[@"HKEY_CURRENT_USER\".Length..]}";
|
||||
|
||||
@@ -53,7 +53,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
||||
{
|
||||
if (databaseViewModelTaskSource != null)
|
||||
if (databaseViewModelTaskSource is not null)
|
||||
{
|
||||
return await databaseViewModelTaskSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
|
||||
{
|
||||
if (wikiAvatarViewModelTaskSource != null)
|
||||
if (wikiAvatarViewModelTaskSource is not null)
|
||||
{
|
||||
return await wikiAvatarViewModelTaskSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -108,7 +108,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiWeaponViewModelAsync()
|
||||
{
|
||||
if (wikiWeaponViewModelTaskSource != null)
|
||||
if (wikiWeaponViewModelTaskSource is not null)
|
||||
{
|
||||
return await wikiWeaponViewModelTaskSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -129,7 +129,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
|
||||
private async ValueTask<Dictionary<AvatarId, Avatar>> GetIdAvatarMapExtendedAsync()
|
||||
{
|
||||
if (idAvatarExtendedMap == null)
|
||||
if (idAvatarExtendedMap is null)
|
||||
{
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
idAvatarExtendedMap = AvatarIds.WithPlayers(idAvatarMap);
|
||||
|
||||
@@ -99,7 +99,7 @@ internal sealed partial class HutaoService : IHutaoService
|
||||
|
||||
try
|
||||
{
|
||||
if (data != null)
|
||||
if (data is not null)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fallbackDataFolder == null)
|
||||
if (fallbackDataFolder is null)
|
||||
{
|
||||
fallbackDataFolder = Path.Combine(hutaoOptions.DataFolder, "Metadata", "CHS");
|
||||
Directory.CreateDirectory(fallbackDataFolder);
|
||||
@@ -47,7 +47,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
{
|
||||
get
|
||||
{
|
||||
if (localizedDataFolder == null)
|
||||
if (localizedDataFolder is null)
|
||||
{
|
||||
localizedDataFolder = Path.Combine(hutaoOptions.DataFolder, "Metadata", LocaleName);
|
||||
Directory.CreateDirectory(localizedDataFolder);
|
||||
|
||||
@@ -216,7 +216,7 @@ internal sealed class NavigationService : INavigationService, INavigationInitial
|
||||
: NavHelper.GetNavigateTo(selected);
|
||||
|
||||
// ignore item that doesn't have nav type specified
|
||||
if (targetType != null)
|
||||
if (targetType is not null)
|
||||
{
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(NavHelper.GetExtraData(selected));
|
||||
Navigate(targetType, navigationAwaiter, false);
|
||||
|
||||
@@ -60,7 +60,7 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
|
||||
{
|
||||
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();
|
||||
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
|
||||
|
||||
@@ -60,7 +60,7 @@ internal sealed partial class UserService : IUserService
|
||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
if (userCollection == null)
|
||||
if (userCollection is null)
|
||||
{
|
||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().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()
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
if (userAndUidCollection == null)
|
||||
if (userAndUidCollection is null)
|
||||
{
|
||||
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
|
||||
userAndUidCollection = users
|
||||
@@ -186,16 +186,16 @@ internal sealed partial class UserService : IUserService
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false);
|
||||
|
||||
if (newUser != null)
|
||||
if (newUser is not null)
|
||||
{
|
||||
// Sync cache
|
||||
if (userCollection != null)
|
||||
if (userCollection is not null)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
{
|
||||
userCollection!.Add(newUser);
|
||||
userCollection.Add(newUser);
|
||||
|
||||
if (userAndUidCollection != null)
|
||||
if (userAndUidCollection is not null)
|
||||
{
|
||||
foreach (UserGameRole role in newUser.UserGameRoles)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.View.Control;
|
||||
/// <summary>
|
||||
/// 胡桃云祈愿统计卡片
|
||||
/// </summary>
|
||||
public sealed partial class HutaoStatisticsCard : UserControl
|
||||
internal sealed partial class HutaoStatisticsCard : UserControl
|
||||
{
|
||||
public HutaoStatisticsCard()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Abstraction;
|
||||
|
||||
@@ -27,19 +28,31 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
/// <inheritdoc/>
|
||||
public bool IsViewDisposed { get; set; }
|
||||
|
||||
protected TaskCompletionSource<bool> Initialization { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化UI
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
[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>
|
||||
/// 保证 using scope 内的代码运行完成
|
||||
/// 防止 视图资源被回收
|
||||
/// </summary>
|
||||
/// <returns>解除执行限制</returns>
|
||||
protected async Task<IDisposable> EnterCriticalExecutionAsync()
|
||||
protected async ValueTask<IDisposable> EnterCriticalExecutionAsync()
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false);
|
||||
@@ -55,7 +68,7 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
throw new OperationCanceledException(SH.ViewModelViewDisposedOperationCancel);
|
||||
ThrowHelper.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,12 @@ namespace Snap.Hutao.ViewModel.Abstraction;
|
||||
/// <summary>
|
||||
/// 简化的视图模型抽象类
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
internal abstract partial class ViewModelSlim : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的简化的视图模型抽象类
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public ViewModelSlim(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否初始化完成
|
||||
/// </summary>
|
||||
@@ -31,7 +24,7 @@ internal abstract partial class ViewModelSlim : ObservableObject
|
||||
/// <summary>
|
||||
/// 服务提供器
|
||||
/// </summary>
|
||||
protected IServiceProvider ServiceProvider { get; }
|
||||
protected IServiceProvider ServiceProvider { get => serviceProvider; }
|
||||
|
||||
/// <summary>
|
||||
/// 打开界面执行
|
||||
@@ -48,19 +41,11 @@ internal abstract partial class ViewModelSlim : ObservableObject
|
||||
/// 简化的视图模型抽象类
|
||||
/// 可导航
|
||||
/// </summary>
|
||||
/// <typeparam name="TPage">页面类型</typeparam>
|
||||
/// <typeparam name="TPage">要导航到的页面类型</typeparam>
|
||||
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||
internal abstract partial class ViewModelSlim<TPage> : ViewModelSlim
|
||||
where TPage : Page
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的简化的视图模型抽象类
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public ViewModelSlim(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导航到指定的页面类型
|
||||
/// </summary>
|
||||
|
||||
@@ -22,28 +22,37 @@ internal static class AchievementFinishPercent
|
||||
int totalFinished = 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);
|
||||
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);
|
||||
Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.From);
|
||||
|
||||
stat.TotalCount += 1;
|
||||
totalCount += 1;
|
||||
if (achievement.IsChecked)
|
||||
// Fast path
|
||||
if (achievements.SourceCollection is List<AchievementView> list)
|
||||
{
|
||||
foreach (ref readonly AchievementView achievement in CollectionsMarshal.AsSpan(list))
|
||||
{
|
||||
stat.Finished += 1;
|
||||
totalFinished += 1;
|
||||
// Make the state update as fast as possible
|
||||
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)
|
||||
{
|
||||
statistics.AchievementGoal.UpdateFinishPercent(statistics);
|
||||
statistics.AchievementGoal.UpdateFinishDescriptionAndPercent(statistics);
|
||||
}
|
||||
|
||||
viewModel.FinishDescription = AchievementStatistics.Format(totalFinished, totalCount, out _);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using BindingAchievementGoal = Snap.Hutao.ViewModel.Achievement.AchievementGoalView;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
@@ -9,7 +10,7 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
||||
/// 成就分类统计
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal struct AchievementGoalStatistics
|
||||
internal struct AchievementGoalStatistics : IMappingFrom<AchievementGoalStatistics, BindingAchievementGoal>
|
||||
{
|
||||
/// <summary>
|
||||
/// 成就分类
|
||||
@@ -40,7 +41,7 @@ internal struct AchievementGoalStatistics
|
||||
/// </summary>
|
||||
/// <param name="goal">分类</param>
|
||||
/// <returns>新的成就分类统计</returns>
|
||||
public static AchievementGoalStatistics Create(BindingAchievementGoal goal)
|
||||
public static AchievementGoalStatistics From(BindingAchievementGoal goal)
|
||||
{
|
||||
return new(goal);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Metadata.Achievement;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
@@ -13,7 +14,7 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
||||
/// 绑定成就分类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
||||
internal sealed class AchievementGoalView : ObservableObject, INameIcon, IMappingFrom<AchievementGoalView, AchievementGoal>
|
||||
{
|
||||
private double finishPercent;
|
||||
private string? finishDescription;
|
||||
@@ -22,7 +23,7 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
||||
/// 构造一个新的成就分类
|
||||
/// </summary>
|
||||
/// <param name="goal">分类</param>
|
||||
public AchievementGoalView(AchievementGoal goal)
|
||||
private AchievementGoalView(AchievementGoal goal)
|
||||
{
|
||||
Id = goal.Id;
|
||||
Order = goal.Order;
|
||||
@@ -60,26 +61,18 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
||||
/// </summary>
|
||||
public string? FinishDescription { get => finishDescription; set => SetProperty(ref finishDescription, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 创建新的列表
|
||||
/// </summary>
|
||||
/// <param name="goals">目标</param>
|
||||
/// <returns>列表</returns>
|
||||
public static List<AchievementGoalView> List(List<AchievementGoal> goals)
|
||||
public static AchievementGoalView From(AchievementGoal source)
|
||||
{
|
||||
return goals
|
||||
.OrderBy(goal => goal.Order)
|
||||
.Select(goal => new AchievementGoalView(goal))
|
||||
.ToList();
|
||||
return new(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新进度
|
||||
/// </summary>
|
||||
/// <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>
|
||||
@@ -87,7 +80,7 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
|
||||
/// </summary>
|
||||
/// <param name="finished">完成项</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);
|
||||
FinishPercent = finishPercent;
|
||||
|
||||
@@ -33,11 +33,11 @@ internal sealed partial class AchievementImporter
|
||||
/// 从剪贴板导入
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
@@ -58,9 +58,9 @@ internal sealed partial class AchievementImporter
|
||||
/// 从文件导入
|
||||
/// </summary>
|
||||
/// <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
|
||||
.GetRequiredService<IPickerFactory>()
|
||||
@@ -90,7 +90,7 @@ internal sealed partial class AchievementImporter
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
||||
private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
||||
{
|
||||
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())
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
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)
|
||||
{
|
||||
ImportResult result;
|
||||
ContentDialog dialog = await serviceProvider.GetRequiredService<IContentDialogFactory>()
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await serviceProvider
|
||||
.GetRequiredService<IContentDialogFactory>()
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImportResult result;
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success(result.ToString());
|
||||
infoBarService.Success($"{result}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ internal sealed class AchievementStatistics
|
||||
|
||||
/// <summary>
|
||||
/// 格式化完成进度
|
||||
/// "xxx/yyy - zz.zz%"
|
||||
/// "xxx/yyy - z.zz%"
|
||||
/// </summary>
|
||||
/// <param name="finished">完成的成就个数</param>
|
||||
/// <param name="totalCount">总个数</param>
|
||||
|
||||
@@ -53,19 +53,11 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata<Mo
|
||||
{
|
||||
if (SetProperty(ref isChecked, value))
|
||||
{
|
||||
// Only update state when checked
|
||||
if (value)
|
||||
{
|
||||
Entity.Status = AchievementStatus.STATUS_REWARD_TAKEN;
|
||||
Entity.Time = DateTimeOffset.Now;
|
||||
OnPropertyChanged(nameof(Time));
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Status = AchievementStatus.STATUS_UNFINISHED;
|
||||
Entity.Time = default;
|
||||
OnPropertyChanged(nameof(Time));
|
||||
}
|
||||
(Entity.Status, Entity.Time) = value
|
||||
? (AchievementStatus.STATUS_REWARD_TAKEN, DateTimeOffset.Now)
|
||||
: (AchievementStatus.STATUS_FINISHED, 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 CompletionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
|
||||
|
||||
private readonly TaskCompletionSource<bool> openUITaskCompletionSource;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
private readonly IAchievementService achievementService;
|
||||
@@ -101,9 +100,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
get => selectedAchievementGoal;
|
||||
set
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, value);
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementsFilterByGoal(value);
|
||||
if (SetProperty(ref selectedAchievementGoal, value))
|
||||
{
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementsFilterByGoal(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +138,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
{
|
||||
@@ -149,12 +150,9 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
if (metaInitialized)
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -167,33 +165,29 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
.GetAchievementGoalsAsync(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
sortedGoals = AchievementGoalView.List(goals);
|
||||
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||
archives = achievementService.ArchiveCollection;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
AchievementGoals = sortedGoals;
|
||||
Archives = archives;
|
||||
SelectedArchive = achievementService.CurrentArchive;
|
||||
|
||||
IsInitialized = true;
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// User canceled the loading operation,
|
||||
// Indicate initialization not succeed.
|
||||
openUITaskCompletionSource.TrySetResult(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openUITaskCompletionSource.TrySetResult(metaInitialized);
|
||||
return false;
|
||||
}
|
||||
|
||||
[Command("AddArchiveCommand")]
|
||||
private async Task AddArchiveAsync()
|
||||
{
|
||||
if (Archives != null)
|
||||
if (Archives is null)
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
@@ -212,10 +206,10 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
infoBarService.Success(string.Format(SH.ViewModelAchievementArchiveAdded, name));
|
||||
break;
|
||||
case ArchiveAddResult.InvalidName:
|
||||
infoBarService.Information(SH.ViewModelAchievementArchiveInvalidName);
|
||||
infoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
break;
|
||||
case ArchiveAddResult.AlreadyExists:
|
||||
infoBarService.Information(string.Format(SH.ViewModelAchievementArchiveAlreadyExists, name));
|
||||
infoBarService.Warning(string.Format(SH.ViewModelAchievementArchiveAlreadyExists, name));
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
@@ -227,12 +221,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("RemoveArchiveCommand")]
|
||||
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
|
||||
.CreateForConfirmCancelAsync(
|
||||
string.Format(SH.ViewModelAchievementRemoveArchiveTitle, SelectedArchive.Name),
|
||||
SH.ViewModelAchievementRemoveArchiveContent)
|
||||
.CreateForConfirmCancelAsync(title, content)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
@@ -258,8 +252,9 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("ExportAsUIAFToFileCommand")]
|
||||
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()
|
||||
{
|
||||
[SH.ViewModelAchievementExportFileType] = ".json".Enumerate().ToList(),
|
||||
@@ -267,11 +262,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
|
||||
FileSavePicker picker = serviceProvider
|
||||
.GetRequiredService<IPickerFactory>()
|
||||
.GetFileSavePicker(
|
||||
PickerLocationId.Desktop,
|
||||
$"{achievementService.CurrentArchive?.Name}.json",
|
||||
SH.FilePickerExportCommit,
|
||||
fileTypes);
|
||||
.GetFileSavePicker(PickerLocationId.Desktop, fileName, SH.FilePickerExportCommit, fileTypes);
|
||||
|
||||
(bool isPickerOk, ValueFile file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
|
||||
if (isPickerOk)
|
||||
@@ -294,7 +285,8 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -319,8 +313,8 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Achievements = new(combined, true); // Assemble achievements on the UI thread.
|
||||
|
||||
Achievements = new(combined, true);
|
||||
UpdateAchievementsFinishPercent();
|
||||
UpdateAchievementsFilterByGoal(SelectedAchievementGoal);
|
||||
UpdateAchievementsSort();
|
||||
@@ -331,13 +325,13 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
try
|
||||
{
|
||||
combined = achievementService.GetAchievementViews(archive, achievements);
|
||||
combined = achievementService.GetAchievementViewList(archive, achievements);
|
||||
return true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
combined = default;
|
||||
infoBarService.Error(ex);
|
||||
combined = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -345,7 +339,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("SortUncompletedSwitchCommand")]
|
||||
private void UpdateAchievementsSort()
|
||||
{
|
||||
if (Achievements != null)
|
||||
if (Achievements is not null)
|
||||
{
|
||||
if (IsUncompletedItemsFirst)
|
||||
{
|
||||
@@ -361,15 +355,16 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
|
||||
private void UpdateAchievementsFilterByGoal(AchievementGoalView? goal)
|
||||
{
|
||||
if (Achievements != null)
|
||||
if (Achievements is not null)
|
||||
{
|
||||
if (goal == null)
|
||||
if (goal is null)
|
||||
{
|
||||
Achievements.Filter = null;
|
||||
}
|
||||
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")]
|
||||
private void UpdateAchievementsFilterBySearch(string? search)
|
||||
{
|
||||
if (Achievements != null)
|
||||
if (Achievements is not null)
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, null);
|
||||
|
||||
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
|
||||
{
|
||||
@@ -408,7 +403,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("SaveAchievementCommand")]
|
||||
private void SaveAchievement(AchievementView? achievement)
|
||||
{
|
||||
if (achievement != null)
|
||||
if (achievement is not null)
|
||||
{
|
||||
achievementService.SaveAchievement(achievement);
|
||||
UpdateAchievementsFinishPercent();
|
||||
|
||||
@@ -10,20 +10,12 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
||||
/// <summary>
|
||||
/// 简化的成就视图模型
|
||||
/// </summary>
|
||||
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的简化的成就视图模型
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public AchievementViewModelSlim(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计列表
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
|
||||
[HighQuality]
|
||||
internal sealed class AvatarProperty : INameIcon
|
||||
{
|
||||
// TODO: use FrozenDictionary
|
||||
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(),
|
||||
|
||||
@@ -22,17 +22,16 @@ using Snap.Hutao.Web.Response;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
||||
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
||||
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
||||
using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
|
||||
using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
|
||||
using CalculatorAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
||||
using CalculatorClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
||||
using CalculatorConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
||||
using CalculatorItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
|
||||
using CalculatorItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色属性视图模型
|
||||
/// TODO: support page unload as cancellation
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
@@ -65,51 +64,45 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OpenUIAsync()
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
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")]
|
||||
private Task RefreshByEnkaApiAsync()
|
||||
private async Task RefreshByEnkaApiAsync()
|
||||
{
|
||||
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")]
|
||||
private Task RefreshByHoyolabGameRecordAsync()
|
||||
private async Task RefreshByHoyolabGameRecordAsync()
|
||||
{
|
||||
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")]
|
||||
private Task RefreshByHoyolabCalculateAsync()
|
||||
private async Task RefreshByHoyolabCalculateAsync()
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -163,11 +156,11 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
[Command("CultivateCommand")]
|
||||
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);
|
||||
return;
|
||||
@@ -177,21 +170,21 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable());
|
||||
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)
|
||||
{
|
||||
Response<CalcConsumption> consumptionResponse = await serviceProvider
|
||||
.GetRequiredService<CalcClient>()
|
||||
Response<CalculatorConsumption> consumptionResponse = await serviceProvider
|
||||
.GetRequiredService<CalculatorClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (consumptionResponse.IsOk())
|
||||
{
|
||||
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
|
||||
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
|
||||
.ConfigureAwait(false);
|
||||
@@ -229,7 +222,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
[Command("ExportAsImageCommand")]
|
||||
private async Task ExportAsImageAsync(FrameworkElement? element)
|
||||
{
|
||||
if (element != null && element.IsLoaded)
|
||||
if (element is { IsLoaded: true })
|
||||
{
|
||||
RenderTargetBitmap bitmap = new();
|
||||
await bitmap.RenderAsync(element);
|
||||
|
||||
@@ -50,7 +50,7 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
|
||||
/// <summary>
|
||||
/// 最大等级
|
||||
/// </summary>
|
||||
internal uint MaxLevel { get => ((int)Quality) >= 3 ? 90U : 70U; }
|
||||
internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ICalculableWeapon ToCalculable()
|
||||
|
||||
@@ -23,18 +23,19 @@ internal sealed class ReliquarySetView
|
||||
{
|
||||
ReliquarySets sets = reliquarySetRate.Item;
|
||||
|
||||
if (sets.Count >= 1)
|
||||
if (!sets.IsNullOrEmpty())
|
||||
{
|
||||
StringBuilder nameBuilder = new();
|
||||
List<Uri> icons = new(2);
|
||||
|
||||
foreach (ReliquarySet set in CollectionsMarshal.AsSpan(sets))
|
||||
{
|
||||
// TODO use set EquipAffixSetIds
|
||||
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);
|
||||
|
||||
@@ -73,8 +73,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
|
||||
if (metaInitialized)
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
ObservableCollection<CultivateProject> projects = cultivationService.ProjectCollection;
|
||||
CultivateProject? selected = cultivationService.Current;
|
||||
@@ -82,9 +81,12 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Projects = projects;
|
||||
SelectedProject = selected;
|
||||
IsInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsInitialized = false;
|
||||
}
|
||||
|
||||
IsInitialized = metaInitialized;
|
||||
}
|
||||
|
||||
[Command("AddProjectCommand")]
|
||||
@@ -122,7 +124,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
[Command("RemoveProjectCommand")]
|
||||
private async Task RemoveProjectAsync(CultivateProject? project)
|
||||
{
|
||||
if (project != null)
|
||||
if (project is not null)
|
||||
{
|
||||
await cultivationService.RemoveProjectAsync(project).ConfigureAwait(false);
|
||||
|
||||
@@ -133,7 +135,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
|
||||
private async Task UpdateEntryCollectionAsync(CultivateProject? project)
|
||||
{
|
||||
if (project != null)
|
||||
if (project is not null)
|
||||
{
|
||||
List<Material> materials = await metadataService.GetMaterialsAsync().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")]
|
||||
private async Task RemoveEntryAsync(CultivateEntryView? entry)
|
||||
{
|
||||
if (entry != null)
|
||||
if (entry is not null)
|
||||
{
|
||||
CultivateEntries!.Remove(entry);
|
||||
await cultivationService.RemoveCultivateEntryAsync(entry.EntryId).ConfigureAwait(false);
|
||||
@@ -165,7 +167,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
[Command("SaveInventoryItemCommand")]
|
||||
private void SaveInventoryItem(InventoryItemView? inventoryItem)
|
||||
{
|
||||
if (inventoryItem != null)
|
||||
if (inventoryItem is not null)
|
||||
{
|
||||
cultivationService.SaveInventoryItem(inventoryItem);
|
||||
UpdateStatisticsItemsAsync().SafeForget();
|
||||
@@ -175,7 +177,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
[Command("FinishStateCommand")]
|
||||
private void UpdateFinishedState(CultivateItemView? item)
|
||||
{
|
||||
if (item != null)
|
||||
if (item is not null)
|
||||
{
|
||||
item.IsFinished = !item.IsFinished;
|
||||
cultivationService.SaveCultivateItem(item);
|
||||
@@ -185,7 +187,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
|
||||
private async Task UpdateStatisticsItemsAsync()
|
||||
{
|
||||
if (SelectedProject != null)
|
||||
if (SelectedProject is not null)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
CancellationToken token = StatisticsCancellationTokenSource.Register();
|
||||
@@ -208,7 +210,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
[Command("NavigateToPageCommand")]
|
||||
private void NavigateToPage(string? typeString)
|
||||
{
|
||||
if (typeString != null)
|
||||
if (typeString is not null)
|
||||
{
|
||||
serviceProvider
|
||||
.GetRequiredService<INavigationService>()
|
||||
|
||||
@@ -17,17 +17,17 @@ internal static class ItemHelper
|
||||
/// <returns>合并且排序好的列表</returns>
|
||||
public static List<Item> Merge(List<Item>? left, List<Item>? right)
|
||||
{
|
||||
if (left == null && right == null)
|
||||
if (left.IsNullOrEmpty() && right.IsNullOrEmpty())
|
||||
{
|
||||
return new(0);
|
||||
}
|
||||
|
||||
if (right == null)
|
||||
if (right.IsNullOrEmpty())
|
||||
{
|
||||
return left!;
|
||||
}
|
||||
|
||||
if (left == null)
|
||||
if (left.IsNullOrEmpty())
|
||||
{
|
||||
return right!;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ internal static class ItemHelper
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user