refactor viewmodels

This commit is contained in:
Lightczx
2023-08-07 17:28:39 +08:00
parent 4dca174019
commit 3cf0cd1c9a
68 changed files with 325 additions and 320 deletions

View File

@@ -79,7 +79,6 @@ internal sealed class CommandGenerator : IIncrementalGenerator
string className = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
// TODO: 支持嵌套类
string code = $$"""
using CommunityToolkit.Mvvm.Input;

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

@@ -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>()

View File

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

View File

@@ -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>

View File

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

View File

@@ -97,7 +97,7 @@ internal sealed class Achievement
/// <inheritdoc/>
public bool Equals(Achievement? other)
{
if (other == null)
if (other is null)
{
return false;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

@@ -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()

View File

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

View File

@@ -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++;

View File

@@ -27,7 +27,7 @@ internal sealed partial class AchievementService
{
get
{
if (archiveCollection == null)
if (archiveCollection is null)
{
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
CurrentArchive = archiveCollection.SelectedOrDefault();

View File

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

View File

@@ -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数据

View File

@@ -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>
/// 初始化前的语言

View File

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

View File

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

View File

@@ -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,

View File

@@ -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)
{

View File

@@ -27,7 +27,7 @@ internal sealed partial class CultivationService
{
get
{
if (projects == null)
if (projects is null)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{

View File

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

View File

@@ -44,7 +44,7 @@ internal sealed class DailyNoteNotificationOperation
/// <returns>任务</returns>
public async ValueTask SendAsync()
{
if (entry.DailyNote == null)
if (entry.DailyNote is null)
{
return;
}

View File

@@ -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))
{

View File

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

View File

@@ -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())

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
{

View File

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

View File

@@ -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..]}";

View File

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

View File

@@ -99,7 +99,7 @@ internal sealed partial class HutaoService : IHutaoService
try
{
if (data != null)
if (data is not null)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{

View File

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

View File

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

View File

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

View File

@@ -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)
{

View File

@@ -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()
{

View File

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

View File

@@ -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>

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -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>

View File

@@ -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(),

View File

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

View File

@@ -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()

View File

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

View File

@@ -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>()

View File

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