diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs index b5adf057..e1622a73 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Snap.Hutao.SourceGeneration.Primitive; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -58,41 +59,80 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator { foreach (AttributeData propertyInfo in context2.Attributes.Where(attr => attr.AttributeClass!.ToDisplayString() == AttributeName)) { + string owner = context2.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + Dictionary namedArguments = propertyInfo.NamedArguments.ToDictionary(); + bool isAttached = namedArguments.TryGetValue("IsAttached", out TypedConstant constant) && (bool)constant.Value!; + string register = isAttached ? "RegisterAttached" : "Register"; + ImmutableArray arguments = propertyInfo.ConstructorArguments; string propertyName = (string)arguments[0].Value!; - string propertyType = arguments[0].Type!.ToDisplayString(); - string type = arguments[1].Value!.ToString(); - string defaultValue = arguments.Length > 2 - ? GetLiteralString(arguments[2]) - : "default"; - string className = context2.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + string propertyType = arguments[1].Value!.ToString(); + string defaultValue = GetLiteralString(arguments.ElementAtOrDefault(2)) ?? "default"; + string propertyChangedCallback = arguments.ElementAtOrDefault(3) is { IsNull: false } arg3 ? $", {arg3.Value}" : string.Empty; - string code = $$""" - using Microsoft.UI.Xaml; + string code; + if (isAttached) + { + string objType = namedArguments.TryGetValue("AttachedType", out TypedConstant attachedType) + ? attachedType.Value!.ToString() + : "object"; - namespace {{context2.Symbol.ContainingNamespace}}; + code = $$""" + using Microsoft.UI.Xaml; - partial class {{className}} - { - private DependencyProperty {{propertyName}}Property = - DependencyProperty.Register(nameof({{propertyName}}), typeof({{type}}), typeof({{className}}), new PropertyMetadata(({{type}}){{defaultValue}})); + namespace {{context2.Symbol.ContainingNamespace}}; - public {{type}} {{propertyName}} + partial class {{owner}} { - get => ({{type}})GetValue({{propertyName}}Property); - set => SetValue({{propertyName}}Property, value); + private static readonly DependencyProperty {{propertyName}}Property = + DependencyProperty.RegisterAttached("{{propertyName}}", typeof({{propertyType}}), typeof({{owner}}), new PropertyMetadata(({{propertyType}}){{defaultValue}}{{propertyChangedCallback}})); + + public static {{propertyType}} Get{{propertyName}}({{objType}} obj) + { + return obj?.GetValue({{propertyName}}Property) as Type; + } + + public static void Set{{propertyName}}({{objType}} obj, {{propertyType}} value) + { + obj.SetValue({{propertyName}}Property, value); + } } - } - """; + """; + } + else + { + code = $$""" + using Microsoft.UI.Xaml; + + namespace {{context2.Symbol.ContainingNamespace}}; + + partial class {{owner}} + { + private readonly DependencyProperty {{propertyName}}Property = + DependencyProperty.Register(nameof({{propertyName}}), typeof({{propertyType}}), typeof({{owner}}), new PropertyMetadata(({{propertyType}}){{defaultValue}}{{propertyChangedCallback}})); + + public {{propertyType}} {{propertyName}} + { + get => ({{propertyType}})GetValue({{propertyName}}Property); + set => SetValue({{propertyName}}Property, value); + } + } + """; + } string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}'); production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code); } } - private static string GetLiteralString(TypedConstant typedConstant) + private static string? GetLiteralString(TypedConstant typedConstant) { + if (typedConstant.IsNull) + { + return default; + } + if (typedConstant.Value is bool boolValue) { return boolValue ? "true" : "false"; diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs index 753398c4..0a6931ed 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs @@ -24,6 +24,8 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator private const string UseDynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute"; private const string CRLF = "\r\n"; + private static readonly DiagnosticDescriptor injectionShouldOmitDescriptor = new("SH201", "Injection 特性可以省略", "HttpClient 特性已将 {0} 注册为 Transient 服务", "Quality", DiagnosticSeverity.Warning, true); + public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValueProvider> injectionClasses = context.SyntaxProvider @@ -91,6 +93,17 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString())) { + if (context.SingleOrDefaultAttribute(InjectionGenerator.AttributeName) is AttributeData injectionData) + { + if (injectionData.ConstructorArguments[0].ToCSharpString() == InjectionGenerator.InjectAsTransientName) + { + if (injectionData.ConstructorArguments.Length < 2) + { + production.ReportDiagnostic(Diagnostic.Create(injectionShouldOmitDescriptor, context.Context.Node.GetLocation(), context.Context.Node)); + } + } + } + lineBuilder.Clear().Append(CRLF); lineBuilder.Append(@" services.AddHttpClient<"); diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs index eebaaf45..fe42e6ea 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs @@ -16,11 +16,10 @@ namespace Snap.Hutao.SourceGeneration.DependencyInjection; [Generator(LanguageNames.CSharp)] internal sealed class InjectionGenerator : IIncrementalGenerator { - private const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute"; + public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute"; private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton"; - private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient"; + public const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient"; private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped"; - private const string CRLF = "\r\n"; private static readonly DiagnosticDescriptor invalidInjectionDescriptor = new("SH101", "无效的 InjectAs 枚举值", "尚未支持生成 {0} 配置", "Quality", DiagnosticSeverity.Error, true); @@ -87,7 +86,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString())) { - lineBuilder.Clear().Append(CRLF); + lineBuilder.Clear().AppendLine(); AttributeData injectionInfo = context.SingleAttribute(AttributeName); ImmutableArray arguments = injectionInfo.ConstructorArguments; diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs index ca8de434..381bdb41 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs @@ -14,7 +14,7 @@ internal class LocalizedEnumGenerator : IIncrementalGenerator { private const string AttributeName = "Snap.Hutao.Resource.Localization.LocalizationAttribute"; private const string LocalizationKeyName = "Snap.Hutao.Resource.Localization.LocalizationKeyAttribute"; - + public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider localizationEnums = context.SyntaxProvider diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs index db7e0391..05ee1f6a 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Snap.Hutao.SourceGeneration.Primitive; @@ -32,4 +33,9 @@ internal static class EnumerableExtension while (enumerator.MoveNext()); } } + + public static Dictionary ToDictionary(this IEnumerable> source) + { + return source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap - Backup.Hutao.SourceGeneration.csproj b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap - Backup.Hutao.SourceGeneration.csproj deleted file mode 100644 index 7b3ef00a..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap - Backup.Hutao.SourceGeneration.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - netstandard2.0 - false - latest - enable - x64 - true - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs index 49f37f45..d46f3fdf 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Snap.Hutao.SourceGeneration.Primitive; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -16,6 +17,7 @@ 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); public override ImmutableArray SupportedDiagnostics { @@ -25,6 +27,7 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer { typeInternalDescriptor, readOnlyStructRefDescriptor, + useValueTaskIfPossibleDescriptor, }.ToImmutableArray(); } } @@ -90,9 +93,22 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer private void HandleMethodDeclaration(SyntaxNodeAnalysisContext context) { MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node; - INamedTypeSymbol? returnTypeSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax.ReturnType) as INamedTypeSymbol; + IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!; + if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task")) + { + Location location = methodSyntax.ReturnType.GetLocation(); + Diagnostic diagnostic = Diagnostic.Create(useValueTaskIfPossibleDescriptor, location); + context.ReportDiagnostic(diagnostic); + return; + } + + if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.ValueTask")) + { + return; + } + // 跳过异步方法,因为异步方法无法使用 ref in out - if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.AsyncKeyword)) || IsTaskOrValueTask(returnTypeSymbol)) + if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.AsyncKeyword))) { return; } @@ -185,23 +201,4 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer _ => false, }; } - - private static bool IsTaskOrValueTask(INamedTypeSymbol? symbol) - { - if (symbol == null) - { - return false; - } - - string typeName = symbol.MetadataName; - if (typeName == "System.Threading.Tasks.Task" || - typeName == "System.Threading.Tasks.Task`1" || - typeName == "System.Threading.Tasks.ValueTask" || - typeName == "System.Threading.Tasks.ValueTask`1") - { - return true; - } - - return false; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs index 82b0bbb1..09fa627e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs @@ -22,10 +22,10 @@ namespace Snap.Hutao.Control.Image; /// 为其他图像类控件提供基类 /// [HighQuality] -[DependencyProperty("EnableLazyLoading", typeof(bool), true)] +[DependencyProperty("EnableLazyLoading", typeof(bool), true, nameof(OnSourceChanged))] +[DependencyProperty("Source", typeof(Uri), default!, nameof(OnSourceChanged))] internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Control { - private static readonly DependencyProperty SourceProperty = Property.Depend(nameof(Source), default(Uri), OnSourceChanged); private readonly ConcurrentCancellationTokenSource loadingTokenSource = new(); private readonly IServiceProvider serviceProvider; @@ -55,15 +55,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co loadedImageSourceLoadCompletedEventHandler = OnLoadImageSurfaceLoadCompleted; } - /// - /// 源 - /// - public Uri Source - { - get => (Uri)GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - /// /// 合成组合视觉 /// @@ -131,7 +122,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co } } - private async Task ApplyImageAsync(Uri? uri, CancellationToken token) + private async ValueTask ApplyImageAsync(Uri? uri, CancellationToken token) { await HideAsync(token).ConfigureAwait(true); @@ -170,7 +161,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co } } - private async Task LoadImageSurfaceAsync(string file, CancellationToken token) + private async ValueTask LoadImageSurfaceAsync(string file, CancellationToken token) { surfaceLoadTaskCompletionSource = new(); LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri()); @@ -180,7 +171,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co return surface; } - private async Task ShowAsync(CancellationToken token) + private async ValueTask ShowAsync(CancellationToken token) { if (!isShow) { @@ -201,7 +192,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co } } - private async Task HideAsync(CancellationToken token) + private async ValueTask HideAsync(CancellationToken token) { if (isShow) { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs index 2e435593..8e22aecd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs @@ -11,12 +11,11 @@ namespace Snap.Hutao.Control.Panel; /// 面板选择器 /// [HighQuality] +[DependencyProperty("Current", typeof(string), List, nameof(OnCurrentChanged))] internal sealed partial class PanelSelector : SplitButton { private const string List = nameof(List); - private static readonly DependencyProperty CurrentProperty = Property.Depend(nameof(Current), List, OnCurrentChanged); - private readonly RoutedEventHandler loadedEventHandler; private readonly RoutedEventHandler unloadedEventHandler; private readonly TypedEventHandler clickEventHandler; @@ -41,15 +40,6 @@ internal sealed partial class PanelSelector : SplitButton Unloaded += unloadedEventHandler; } - /// - /// 当前选择 - /// - public string Current - { - get => (string)GetValue(CurrentProperty); - set => SetValue(CurrentProperty, value); - } - private static void InitializeItems(PanelSelector selector) { MenuFlyout menuFlyout = (MenuFlyout)selector.Flyout; diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Property.cs b/src/Snap.Hutao/Snap.Hutao/Control/Property.cs deleted file mode 100644 index fca8a789..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Control/Property.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.UI.Xaml; - -namespace Snap.Hutao.Control; - -/// -/// 快速创建 -/// -/// 所有者的类型 -[HighQuality] -[Obsolete("Use DependencyPropertyAttribute whenever possible")] -internal static class Property -{ - /// - /// 注册依赖属性 - /// - /// 属性的类型 - /// 属性名称 - /// 注册的依赖属性 - public static DependencyProperty Depend(string name) - { - return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(default(TProperty))); - } - - /// - /// 注册依赖属性 - /// - /// 属性的类型 - /// 属性名称 - /// 默认值 - /// 注册的依赖属性 - public static DependencyProperty Depend(string name, TProperty defaultValue) - { - return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue)); - } - - /// - /// 注册依赖属性 - /// - /// 属性的类型 - /// 属性名称 - /// 封装的默认值 - /// 注册的依赖属性 - public static DependencyProperty DependBoxed(string name, object defaultValue) - { - return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue)); - } - - /// - /// 注册依赖属性 - /// - /// 属性的类型 - /// 属性名称 - /// 默认值 - /// 属性更改回调 - /// 注册的依赖属性 - public static DependencyProperty Depend( - string name, - TProperty defaultValue, - Action callback) - { - return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue, new(callback))); - } - - /// - /// 注册附加属性 - /// - /// 属性的类型 - /// 属性名称 - /// 注册的附加属性 - public static DependencyProperty Attach(string name) - { - return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(default(TProperty))); - } - - /// - /// 注册附加属性 - /// - /// 属性的类型 - /// 属性名称 - /// 默认值 - /// 注册的附加属性 - public static DependencyProperty Attach(string name, TProperty defaultValue) - { - return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(defaultValue)); - } - - /// - /// 注册附加属性 - /// - /// 属性的类型 - /// 属性名称 - /// 默认值 - /// 属性更改回调 - /// 注册的附加属性 - public static DependencyProperty Attach( - string name, - TProperty defaultValue, - Action callback) - { - return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(defaultValue, new(callback))); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs index 852cf008..0ffcc074 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs @@ -21,10 +21,9 @@ namespace Snap.Hutao.Control.Text; /// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs /// [HighQuality] -internal sealed class DescriptionTextBlock : ContentControl +[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))] +internal sealed partial class DescriptionTextBlock : ContentControl { - private static readonly DependencyProperty DescriptionProperty = Property.Depend(nameof(Description), string.Empty, OnDescriptionChanged); - private static readonly int ColorTagFullLength = "".Length; private static readonly int ColorTagLeftLength = "".Length; @@ -49,15 +48,6 @@ internal sealed class DescriptionTextBlock : ContentControl ActualThemeChanged += actualThemeChangedEventHandler; } - /// - /// 可绑定的描述文本 - /// - public string Description - { - get => (string)GetValue(DescriptionProperty); - set => SetValue(DescriptionProperty, value); - } - private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Annotation/DependencyPropertyAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/Annotation/DependencyPropertyAttribute.cs index 702c01e6..03f26cdd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Annotation/DependencyPropertyAttribute.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Annotation/DependencyPropertyAttribute.cs @@ -13,4 +13,12 @@ internal sealed class DependencyPropertyAttribute : Attribute public DependencyPropertyAttribute(string name, Type type, object defaultValue) { } + + public DependencyPropertyAttribute(string name, Type type, object defaultValue, string valueChangedCallbackName) + { + } + + public bool IsAttached { get; set; } + + public Type AttachedType { get; set; } = default!; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/INamedService.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/INamedService.cs index 556588e1..1fc7b03d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/INamedService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/INamedService.cs @@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction; /// 有名称的对象 /// 指示该对象可通过名称区分 /// +[Obsolete("无意义的接口")] [HighQuality] internal interface INamedService { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/IOverseaSupport.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/IOverseaSupport.cs index 24acafa9..35145531 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/IOverseaSupport.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/IOverseaSupport.cs @@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction; /// /// 海外服/HoYoLAB 可区分 /// +[Obsolete("Use IOverseaSupportFactory instead")] internal interface IOverseaSupport { /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs index 77c4614b..44ddf98d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs @@ -11,20 +11,6 @@ namespace Snap.Hutao.Core.DependencyInjection; /// internal static class EnumerableServiceExtension { - /// - /// 选择对应的服务 - /// - /// 服务类型 - /// 服务集合 - /// 名称 - /// 对应的服务 - [Obsolete("该方法会导致不必要的服务实例化")] - public static TService Pick(this IEnumerable services, string name) - where TService : INamedService - { - return services.Single(s => s.Name == name); - } - /// /// 选择对应的服务 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs index df564cb7..183e37c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs @@ -32,7 +32,8 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter EnumerateNumbers(string source) { - foreach (StringSegment id in new StringTokenizer(source, new[] { Comma })) // TODO: Use CL + // TODO: Use Collection Literals + foreach (StringSegment id in new StringTokenizer(source, new[] { Comma })) { yield return int.Parse(id.AsSpan()); } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs index f05dab8d..8cbb61c2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs @@ -30,7 +30,7 @@ internal class AsyncBarrier // Allocate the stack so no resizing is necessary. // We don't need space for the last participant, since we never have to store it. - waiters = new Stack(participants - 1); + waiters = new Queue(participants - 1); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtension.cs index dc2af2bf..03aaf348 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtension.cs @@ -110,4 +110,102 @@ internal static class TaskExtension onException?.Invoke(e); } } + + /// + /// 安全的触发任务 + /// + /// 任务 + public static async void SafeForget(this ValueTask task) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Do nothing + } +#if DEBUG + catch (Exception ex) + { + if (System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debug.WriteLine(ExceptionFormat.Format(ex)); + System.Diagnostics.Debugger.Break(); + } + } +#else + catch + { + } +#endif + } + + /// + /// 安全的触发任务 + /// + /// 任务 + /// 日志器 + public static async void SafeForget(this ValueTask task, ILogger logger) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Do nothing + } + catch (Exception e) + { + logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), ExceptionFormat.Format(e.GetBaseException())); + } + } + + /// + /// 安全的触发任务 + /// + /// 任务 + /// 日志器 + /// 发生异常时调用 + public static async void SafeForget(this ValueTask task, ILogger logger, Action onException) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Do nothing + } + catch (Exception e) + { + logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), ExceptionFormat.Format(e.GetBaseException())); + onException?.Invoke(e); + } + } + + /// + /// 安全的触发任务 + /// + /// 任务 + /// 日志器 + /// 任务取消时调用 + /// 发生异常时调用 + public static async void SafeForget(this ValueTask task, ILogger logger, Action onCanceled, Action? onException = null) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + onCanceled?.Invoke(); + } + catch (Exception e) + { + logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), ExceptionFormat.Format(e.GetBaseException())); + onException?.Invoke(e); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs index ed543fe5..40fba6fc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs @@ -22,7 +22,6 @@ internal sealed class FlyoutStateChangedMessage public static FlyoutStateChangedMessage Close { get; } = new(false); - /// /// 是否为开启状态 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/Material.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/Material.cs index 45c6f69a..f818af7f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/Material.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/Material.cs @@ -2,11 +2,9 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Primitive; -using Snap.Hutao.ViewModel.Cultivation; -using System.Collections.Immutable; -using System.Text.RegularExpressions; using Snap.Hutao.Model.Intrinsic.Immutable; +using Snap.Hutao.ViewModel.Cultivation; +using System.Text.RegularExpressions; namespace Snap.Hutao.Model.Metadata.Item; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchiveOperation.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchiveOperation.cs index 77e52f71..c3176f31 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchiveOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchiveOperation.cs @@ -1,9 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Database; using System.Collections.ObjectModel; namespace Snap.Hutao.Service.GachaLog; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs new file mode 100644 index 00000000..36b40924 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs @@ -0,0 +1,205 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Model.Entity.Database; +using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; +using System.Collections.ObjectModel; + +namespace Snap.Hutao.Service.GachaLog; + +[ConstructorGenerated] +[Injection(InjectAs.Scoped, typeof(IGachaLogDbService))] +internal sealed partial class GachaLogDbService : IGachaLogDbService +{ + private readonly IServiceProvider serviceProvider; + + public ObservableCollection GetGachaArchiveCollection() + { + try + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + return appDbContext.GachaArchives.AsNoTracking().ToObservableCollection(); + } + } + catch (SqliteException ex) + { + string message = string.Format(SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage, ex.Message); + throw ThrowHelper.UserdataCorrupted(message, ex); + } + } + + public List GetGachaItemListByArchiveId(Guid archiveId) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + return appDbContext.GachaItems + .AsNoTracking() + .Where(i => i.ArchiveId == archiveId) + .OrderBy(i => i.Id) + .ToList(); + } + } + + public async ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.GachaArchives + .ExecuteDeleteWhereAsync(a => a.InnerId == archiveId) + .ConfigureAwait(false); + } + } + + public async ValueTask GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token) + { + GachaItem? item = null; + + try + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + + // TODO: replace with MaxBy + // https://github.com/dotnet/efcore/issues/25566 + // .MaxBy(i => i.Id); + item = await appDbContext.GachaItems + .AsNoTracking() + .Where(i => i.ArchiveId == archiveId) + .Where(i => i.QueryType == queryType) + .OrderByDescending(i => i.Id) + .FirstOrDefaultAsync(token) + .ConfigureAwait(false); + } + } + catch (SqliteException ex) + { + ThrowHelper.UserdataCorrupted(SH.ServiceGachaLogEndIdUserdataCorruptedMessage, ex); + } + + return item?.Id ?? 0L; + } + + public long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType) + { + GachaItem? item = null; + + try + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + + // TODO: replace with MaxBy + // https://github.com/dotnet/efcore/issues/25566 + // .MaxBy(i => i.Id); + item = appDbContext.GachaItems + .AsNoTracking() + .Where(i => i.ArchiveId == archiveId) + .Where(i => i.QueryType == queryType) + .OrderByDescending(i => i.Id) + .FirstOrDefault(); + } + } + catch (SqliteException ex) + { + ThrowHelper.UserdataCorrupted(SH.ServiceGachaLogEndIdUserdataCorruptedMessage, ex); + } + + return item?.Id ?? 0L; + } + + public long GetOldestGachaItemIdByArchiveId(Guid archiveId) + { + GachaItem? item = null; + + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + + // TODO: replace with MaxBy + // https://github.com/dotnet/efcore/issues/25566 + // .MaxBy(i => i.Id); + item = appDbContext.GachaItems + .AsNoTracking() + .Where(i => i.ArchiveId == archiveId) + .OrderBy(i => i.Id) + .FirstOrDefault(); + } + + return item?.Id ?? long.MaxValue; + } + + public async ValueTask AddGachaArchiveAsync(GachaArchive archive) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.GachaArchives.AddAndSaveAsync(archive); + } + } + + public void AddGachaArchive(GachaArchive archive) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + appDbContext.GachaArchives.AddAndSave(archive); + } + } + + public List GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + return appDbContext.GachaItems + .AsNoTracking() + .Where(i => i.ArchiveId == archiveId) + .Where(i => i.QueryType == queryType) + .OrderByDescending(i => i.Id) + .Where(i => i.Id > endId) + + // Keep this to make SQL generates correctly + .Select(i => new Web.Hutao.GachaLog.GachaItem() + { + GachaType = i.GachaType, + QueryType = i.QueryType, + ItemId = i.ItemId, + Time = i.Time, + Id = i.Id, + }) + .ToList(); + } + } + + public async ValueTask GetGachaArchiveByUidAsync(string uid, CancellationToken token) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + return await appDbContext.GachaArchives + .AsNoTracking() + .SingleOrDefaultAsync(a => a.Uid == uid, token) + .ConfigureAwait(false); + } + } + + public async ValueTask AddGachaItemsAsync(List items) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.GachaItems.AddRangeAndSaveAsync(items).ConfigureAwait(false); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs index 0ada850e..54312ce3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs @@ -102,7 +102,7 @@ internal struct GachaLogFetchContext GachaArchiveOperation.GetOrAdd(serviceProvider, item.Uid, archives, out TargetArchive); } - DbEndId ??= gachaLogDbService.GetLastGachaItemIdByArchiveIdAndQueryType(TargetArchive.InnerId, CurrentType); + DbEndId ??= gachaLogDbService.GetNewestGachaItemIdByArchiveIdAndQueryType(TargetArchive.InnerId, CurrentType); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs new file mode 100644 index 00000000..ba5267e6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs @@ -0,0 +1,134 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Database; +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Model.Entity.Database; +using Snap.Hutao.Model.Metadata; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; +using Snap.Hutao.Service.GachaLog.Factory; +using Snap.Hutao.Service.Metadata; +using Snap.Hutao.ViewModel.GachaLog; +using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; +using Snap.Hutao.Web.Hutao; +using Snap.Hutao.Web.Hutao.GachaLog; +using Snap.Hutao.Web.Response; + +namespace Snap.Hutao.Service.GachaLog; + +/// +/// 祈愿记录胡桃云服务 +/// +[ConstructorGenerated] +[Injection(InjectAs.Scoped, typeof(IGachaLogHutaoCloudService))] +internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudService +{ + private readonly HomaGachaLogClient homaGachaLogClient; + private readonly IGachaLogDbService gachaLogDbService; + private readonly IServiceProvider serviceProvider; + + /// + public ValueTask>> GetGachaEntriesAsync(CancellationToken token = default) + { + return homaGachaLogClient.GetGachaEntriesAsync(token); + } + + /// + public async ValueTask> UploadGachaItemsAsync(GachaArchive gachaArchive, CancellationToken token = default) + { + string uid = gachaArchive.Uid; + if (await GetEndIdsFromCloudAsync(uid, token).ConfigureAwait(false) is { } endIds) + { + List items = new(); + foreach ((GachaConfigType type, long endId) in endIds) + { + List part = gachaLogDbService.GetHutaoGachaItemList(gachaArchive.InnerId, type, endId); + items.AddRange(part); + } + + return await homaGachaLogClient.UploadGachaItemsAsync(uid, items, token).ConfigureAwait(false); + } + + return new(false, SH.ServiceGachaLogHutaoCloudEndIdFetchFailed); + } + + /// + public async Task> RetrieveGachaItemsAsync(string uid, CancellationToken token = default) + { + GachaArchive? archive = await gachaLogDbService + .GetGachaArchiveByUidAsync(uid, token) + .ConfigureAwait(false); + + EndIds endIds = await CreateEndIdsAsync(archive, token).ConfigureAwait(false); + Response> resp = await homaGachaLogClient.RetrieveGachaItemsAsync(uid, endIds, token).ConfigureAwait(false); + + if (!resp.IsOk()) + { + return new(false, null); + } + + if (archive == null) + { + archive = GachaArchive.From(uid); + await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false); + } + + List gachaItems = resp.Data.SelectList(i => Model.Entity.GachaItem.From(archive.InnerId, i)); + await gachaLogDbService.AddGachaItemsAsync(gachaItems).ConfigureAwait(false); + return new(true, archive); + } + + /// + public async Task> DeleteGachaItemsAsync(string uid, CancellationToken token = default) + { + return await homaGachaLogClient.DeleteGachaItemsAsync(uid, token).ConfigureAwait(false); + } + + /// + public async Task> GetCurrentEventStatisticsAsync(CancellationToken token = default) + { + Response response = await homaGachaLogClient.GetGachaEventStatisticsAsync(token).ConfigureAwait(false); + if (response.IsOk()) + { + IMetadataService metadataService = serviceProvider.GetRequiredService(); + if (await metadataService.InitializeAsync().ConfigureAwait(false)) + { + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); + List gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false); + HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents); + + GachaEventStatistics raw = response.Data; + HutaoStatisticsFactory factory = new(context); + HutaoStatistics statistics = factory.Create(raw); + return new(true, statistics); + } + } + + return new(false, default!); + } + + private async Task GetEndIdsFromCloudAsync(string uid, CancellationToken token = default) + { + Response resp = await homaGachaLogClient.GetEndIdsAsync(uid, token).ConfigureAwait(false); + return resp.IsOk() ? resp.Data : default; + } + + private async ValueTask CreateEndIdsAsync(GachaArchive? archive, CancellationToken token) + { + EndIds endIds = new(); + foreach (GachaConfigType type in GachaLog.QueryTypes) + { + if (archive != null) + { + endIds[type] = await gachaLogDbService + .GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(archive.InnerId, type, token) + .ConfigureAwait(false); + } + } + + return endIds; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index de4e35db..6e515b73 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -1,13 +1,9 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Diagnostics; -using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.InterChange.GachaLog; using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.GachaLog.Factory; @@ -123,11 +119,12 @@ internal sealed partial class GachaLogService : IGachaLogService /// public async ValueTask ImportFromUIGFAsync(UIGF uigf) { - CurrentArchive = await gachaLogImportService.ImportAsync(context, uigf).ConfigureAwait(false); + ArgumentNullException.ThrowIfNull(ArchiveCollection); + CurrentArchive = await gachaLogImportService.ImportAsync(context, uigf, ArchiveCollection).ConfigureAwait(false); } /// - public async Task RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress progress, CancellationToken token) + public async ValueTask RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress progress, CancellationToken token) { bool isLazy = strategy switch { @@ -147,7 +144,7 @@ internal sealed partial class GachaLogService : IGachaLogService } /// - public async Task RemoveArchiveAsync(GachaArchive archive) + public async ValueTask RemoveArchiveAsync(GachaArchive archive) { ArgumentNullException.ThrowIfNull(archiveCollection); @@ -160,7 +157,7 @@ internal sealed partial class GachaLogService : IGachaLogService await gachaLogDbService.DeleteGachaArchiveByIdAsync(archive.InnerId).ConfigureAwait(false); } - private async Task> FetchGachaLogsAsync(GachaLogQuery query, bool isLazy, IProgress progress, CancellationToken token) + private async ValueTask> FetchGachaLogsAsync(GachaLogQuery query, bool isLazy, IProgress progress, CancellationToken token) { ArgumentNullException.ThrowIfNull(ArchiveCollection); GachaLogFetchContext fetchContext = new(serviceProvider, context, isLazy); @@ -225,103 +222,4 @@ internal sealed partial class GachaLogService : IGachaLogService return new(!fetchContext.FetchStatus.AuthKeyTimeout, fetchContext.TargetArchive); } -} - -[ConstructorGenerated] -[Injection(InjectAs.Scoped, typeof(IGachaLogDbService))] -internal sealed partial class GachaLogDbService : IGachaLogDbService -{ - private readonly IServiceProvider serviceProvider; - - public ObservableCollection GetGachaArchiveCollection() - { - try - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.GachaArchives.AsNoTracking().ToObservableCollection(); - } - } - catch (SqliteException ex) - { - string message = string.Format(SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage, ex.Message); - throw ThrowHelper.UserdataCorrupted(message, ex); - } - } - - public List GetGachaItemListByArchiveId(Guid archiveId) - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.GachaItems - .AsNoTracking() - .Where(i => i.ArchiveId == archiveId) - .OrderBy(i => i.Id) - .ToList(); - } - } - - public async ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId) - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.GachaArchives - .ExecuteDeleteWhereAsync(a => a.InnerId == archiveId) - .ConfigureAwait(false); - } - } - - public long GetLastGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType) - { - GachaItem? item = null; - - try - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - - // TODO: replace with MaxBy - // https://github.com/dotnet/efcore/issues/25566 - // .MaxBy(i => i.Id); - item = appDbContext.GachaItems - .AsNoTracking() - .Where(i => i.ArchiveId == archiveId) - .Where(i => i.QueryType == queryType) - .OrderByDescending(i => i.Id) - .FirstOrDefault(); - } - } - catch (SqliteException ex) - { - ThrowHelper.UserdataCorrupted(SH.ServiceGachaLogEndIdUserdataCorruptedMessage, ex); - } - - return item?.Id ?? 0L; - } - - public void AddGachaArchive(GachaArchive archive) - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - appDbContext.GachaArchives.AddAndSave(archive); - } - } -} - -internal interface IGachaLogDbService -{ - void AddGachaArchive(GachaArchive archive); - - ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId); - - ObservableCollection GetGachaArchiveCollection(); - - List GetGachaItemListByArchiveId(Guid archiveId); - - long GetLastGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs index 5d62d8d1..e56e7cd4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs @@ -42,12 +42,6 @@ internal readonly struct GachaLogServiceMetadataContext /// public readonly Dictionary NameWeaponMap; - /// - /// 存档集合 - /// - [Obsolete] - public readonly ObservableCollection ArchiveCollection; - /// /// 是否初始化完成 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs deleted file mode 100644 index a71ae389..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.EntityFrameworkCore; -using Snap.Hutao.Core.Database; -using Snap.Hutao.Model.Entity.Database; -using Snap.Hutao.Model.Metadata; -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Weapon; -using Snap.Hutao.Model.Primitive; -using Snap.Hutao.Service.GachaLog.Factory; -using Snap.Hutao.Service.Metadata; -using Snap.Hutao.ViewModel.GachaLog; -using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; -using Snap.Hutao.Web.Hutao; -using Snap.Hutao.Web.Hutao.GachaLog; -using Snap.Hutao.Web.Response; -using System.Runtime.CompilerServices; - -namespace Snap.Hutao.Service.GachaLog; - -/// -/// 胡桃云服务 -/// -[ConstructorGenerated] -[Injection(InjectAs.Scoped, typeof(IHutaoCloudService))] -internal sealed partial class HutaoCloudService : IHutaoCloudService -{ - private readonly HomaGachaLogClient homaGachaLogClient; - private readonly IServiceProvider serviceProvider; - - /// - public Task>> GetUidsAsync(CancellationToken token = default) - { - return homaGachaLogClient.GetUidsAsync(token); - } - - /// - public async Task> UploadGachaItemsAsync(Model.Entity.GachaArchive gachaArchive, CancellationToken token = default) - { - string uid = gachaArchive.Uid; - EndIds? endIds = await GetEndIdsFromCloudAsync(uid, token).ConfigureAwait(false); - if (endIds != null) - { - List items = new(); - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - foreach ((GachaConfigType type, long endId) in endIds) - { - IEnumerable part = QueryArchiveGachaItemsByTypeAndEndId(appDbContext, gachaArchive, type, endId); - items.AddRange(part); - } - } - - return await homaGachaLogClient.UploadGachaItemsAsync(uid, items, token).ConfigureAwait(false); - } - - return new(false, SH.ServiceGachaLogHutaoCloudEndIdFetchFailed); - } - - /// - public async Task> RetrieveGachaItemsAsync(string uid, CancellationToken token = default) - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - - Model.Entity.GachaArchive? archive = await appDbContext.GachaArchives - .SingleOrDefaultAsync(a => a.Uid == uid, token) - .ConfigureAwait(false); - - EndIds endIds = await EndIds.CreateAsync(appDbContext, archive, token).ConfigureAwait(false); - Response> resp = await homaGachaLogClient.RetrieveGachaItemsAsync(uid, endIds, token).ConfigureAwait(false); - - if (resp.IsOk()) - { - if (archive == null) - { - archive = Model.Entity.GachaArchive.From(uid); - await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false); - } - - List gachaItems = resp.Data.SelectList(i => Model.Entity.GachaItem.From(archive.InnerId, i)); - await appDbContext.GachaItems.AddRangeAndSaveAsync(gachaItems).ConfigureAwait(false); - return new(true, archive); - } - } - - return new(false, null); - } - - /// - public async Task> DeleteGachaItemsAsync(string uid, CancellationToken token = default) - { - return await homaGachaLogClient.DeleteGachaItemsAsync(uid, token).ConfigureAwait(false); - } - - /// - public async Task> GetCurrentEventStatisticsAsync(CancellationToken token = default) - { - IMetadataService metadataService = serviceProvider.GetRequiredService(); - - Response response = await homaGachaLogClient.GetGachaEventStatisticsAsync(token).ConfigureAwait(false); - if (response.IsOk()) - { - if (await metadataService.InitializeAsync().ConfigureAwait(false)) - { - Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); - Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); - List gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false); - HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents); - - GachaEventStatistics raw = response.Data; - HutaoStatisticsFactory factory = new(context); - HutaoStatistics statistics = factory.Create(raw); - return new(true, statistics); - } - } - - return new(false, default!); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IEnumerable QueryArchiveGachaItemsByTypeAndEndId(AppDbContext appDbContext, Model.Entity.GachaArchive gachaArchive, GachaConfigType type, long endId) - { - return appDbContext.GachaItems - .Where(i => i.ArchiveId == gachaArchive.InnerId) - .Where(i => i.QueryType == type) - .OrderByDescending(i => i.Id) - .Where(i => i.Id > endId) - - // Keep this to make SQL generates correctly - .Select(i => new GachaItem() - { - GachaType = i.GachaType, - QueryType = i.QueryType, - ItemId = i.ItemId, - Time = i.Time, - Id = i.Id, - }); - } - - private async Task GetEndIdsFromCloudAsync(string uid, CancellationToken token = default) - { - Response resp = await homaGachaLogClient.GetEndIdsAsync(uid, token).ConfigureAwait(false); - _ = resp.IsOk(); - return resp.Data; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogDbService.cs new file mode 100644 index 00000000..61790b3b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogDbService.cs @@ -0,0 +1,33 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; +using System.Collections.ObjectModel; + +namespace Snap.Hutao.Service.GachaLog; + +internal interface IGachaLogDbService +{ + void AddGachaArchive(GachaArchive archive); + + ValueTask AddGachaArchiveAsync(GachaArchive archive); + + ValueTask AddGachaItemsAsync(List items); + + ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId); + + ValueTask GetGachaArchiveByUidAsync(string uid, CancellationToken token); + + ObservableCollection GetGachaArchiveCollection(); + + List GetGachaItemListByArchiveId(Guid archiveId); + + List GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId); + + long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType); + + ValueTask GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token); + + long GetOldestGachaItemIdByArchiveId(Guid archiveId); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IHutaoCloudService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogHutaoCloudService.cs similarity index 73% rename from src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IHutaoCloudService.cs rename to src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogHutaoCloudService.cs index e9374e37..3a0a8da6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IHutaoCloudService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogHutaoCloudService.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.ViewModel.GachaLog; +using Snap.Hutao.Web.Hutao.GachaLog; using Snap.Hutao.Web.Response; namespace Snap.Hutao.Service.GachaLog; @@ -10,7 +11,7 @@ namespace Snap.Hutao.Service.GachaLog; /// /// 胡桃云服务 /// -internal interface IHutaoCloudService +internal interface IGachaLogHutaoCloudService { /// /// 异步删除服务器上的祈愿记录 @@ -25,14 +26,9 @@ internal interface IHutaoCloudService /// /// 取消令牌 /// 祈愿统计信息 - Task> GetCurrentEventStatisticsAsync(CancellationToken token = default(CancellationToken)); + Task> GetCurrentEventStatisticsAsync(CancellationToken token = default); - /// - /// 异步获取服务器上的 Uid 列表 - /// - /// 取消令牌 - /// 服务器上的 Uid 列表 - Task>> GetUidsAsync(CancellationToken token = default); + ValueTask>> GetGachaEntriesAsync(CancellationToken token = default); /// /// 异步获取祈愿记录 @@ -48,5 +44,5 @@ internal interface IHutaoCloudService /// 祈愿档案 /// 取消令牌 /// 是否上传成功 - Task> UploadGachaItemsAsync(GachaArchive gachaArchive, CancellationToken token = default); + ValueTask> UploadGachaItemsAsync(GachaArchive gachaArchive, CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs index dc827b01..974e8a4c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs @@ -69,12 +69,12 @@ internal interface IGachaLogService /// 进度 /// 取消令牌 /// 验证密钥是否有效 - Task RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress progress, CancellationToken token); + ValueTask RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress progress, CancellationToken token); /// /// 删除存档 /// /// 存档 /// 任务 - Task RemoveArchiveAsync(GachaArchive archive); + ValueTask RemoveArchiveAsync(GachaArchive archive); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IUIGFImportService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IUIGFImportService.cs index 3e409e71..e1e75e0e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IUIGFImportService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IUIGFImportService.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.InterChange.GachaLog; +using System.Collections.ObjectModel; namespace Snap.Hutao.Service.GachaLog; @@ -16,6 +17,7 @@ internal interface IUIGFImportService /// /// 祈愿记录服务上下文 /// 数据 + /// 存档集合 /// 存档 - Task ImportAsync(GachaLogServiceMetadataContext context, UIGF uigf); + ValueTask ImportAsync(GachaLogServiceMetadataContext context, UIGF uigf, ObservableCollection archives); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs index 064e52ab..e7824990 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs @@ -20,10 +20,7 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP private readonly ITaskContext taskContext; /// - public string Name { get => nameof(GachaLogQueryManualInputProvider); } - - /// - public async Task> GetQueryAsync() + public async ValueTask> GetQueryAsync() { // ContentDialog must be created by main thread. await taskContext.SwitchToMainThreadAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs index 1a89ddda..0f76f308 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs @@ -23,10 +23,7 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid private readonly IUserService userService; /// - public string Name { get => nameof(GachaLogQuerySTokenProvider); } - - /// - public async Task> GetQueryAsync() + public async ValueTask> GetQueryAsync() { if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs index f8464653..3466fad4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs @@ -23,9 +23,6 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv private readonly IGameService gameService; private readonly MetadataOptions metadataOptions; - /// - public string Name { get => nameof(GachaLogQueryWebCacheProvider); } - /// /// 获取缓存文件路径 /// @@ -50,7 +47,7 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv } /// - public async Task> GetQueryAsync() + public async ValueTask> GetQueryAsync() { (bool isOk, string path) = await gameService.GetGamePathAsync().ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs index 89f0a8f7..dff8f991 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs @@ -1,20 +1,18 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.DependencyInjection.Abstraction; - namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// /// 祈愿记录Url提供器 /// [HighQuality] -internal interface IGachaLogQueryProvider : INamedService +internal interface IGachaLogQueryProvider { /// /// 异步获取包含验证密钥的查询语句 /// 查询语句可以仅包含?后的内容 /// /// 包含验证密钥的查询语句 - Task> GetQueryAsync(); + ValueTask> GetQueryAsync(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFExportService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFExportService.cs index f94297b7..e2463449 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFExportService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFExportService.cs @@ -15,29 +15,24 @@ namespace Snap.Hutao.Service.GachaLog; internal sealed partial class UIGFExportService : IUIGFExportService { private readonly IServiceProvider serviceProvider; + private readonly IGachaLogDbService gachaLogDbService; private readonly ITaskContext taskContext; /// public async ValueTask ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive) { await taskContext.SwitchToBackgroundAsync(); - using (IServiceScope scope = serviceProvider.CreateScope()) + + List list = gachaLogDbService + .GetGachaItemListByArchiveId(archive.InnerId) + .SelectList(i => UIGFItem.From(i, context.GetNameQualityByItemId(i.ItemId))); + + UIGF uigf = new() { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + Info = UIGFInfo.From(serviceProvider, archive.Uid), + List = list, + }; - List list = appDbContext.GachaItems - .Where(i => i.ArchiveId == archive.InnerId) - .AsEnumerable() - .Select(i => UIGFItem.From(i, context.GetNameQualityByItemId(i.ItemId))) - .ToList(); - - UIGF uigf = new() - { - Info = UIGFInfo.From(serviceProvider, archive.Uid), - List = list, - }; - - return uigf; - } + return uigf; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs index 05f3f8f5..2fb79ecd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs @@ -1,15 +1,14 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.InterChange.GachaLog; +using System.Collections.ObjectModel; namespace Snap.Hutao.Service.GachaLog; /// -/// v2.1 v2.2 祈愿记录导入服务 +/// 祈愿记录导入服务 /// [ConstructorGenerated] [Injection(InjectAs.Scoped, typeof(IUIGFImportService))] @@ -17,43 +16,39 @@ internal sealed partial class UIGFImportService : IUIGFImportService { private readonly ILogger logger; private readonly IServiceProvider serviceProvider; + private readonly IGachaLogDbService gachaLogDbService; private readonly ITaskContext taskContext; /// - public async Task ImportAsync(GachaLogServiceMetadataContext context, UIGF uigf) + public async ValueTask ImportAsync(GachaLogServiceMetadataContext context, UIGF uigf, ObservableCollection archives) { await taskContext.SwitchToBackgroundAsync(); - using (IServiceScope scope = serviceProvider.CreateScope()) + + GachaArchiveOperation.GetOrAdd(serviceProvider, uigf.Info.Uid, archives, out GachaArchive? archive); + Guid archiveId = archive.InnerId; + + long trimId = gachaLogDbService.GetOldestGachaItemIdByArchiveId(archiveId); + + logger.LogInformation("Last Id to trim with: [{Id}]", trimId); + + _ = uigf.IsCurrentVersionSupported(out UIGFVersion version); + + List toAdd = version switch { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + UIGFVersion.Major2Minor3OrHigher => uigf.List + .OrderByDescending(i => i.Id) + .Where(i => i.Id < trimId) + .Select(i => GachaItem.From(archiveId, i)) + .ToList(), + UIGFVersion.Major2Minor2OrLower => uigf.List + .OrderByDescending(i => i.Id) + .Where(i => i.Id < trimId) + .Select(i => GachaItem.From(archiveId, i, context.GetItemId(i))) + .ToList(), + _ => new(), + }; - GachaArchiveOperation.GetOrAdd(serviceProvider, uigf.Info.Uid, context.ArchiveCollection, out GachaArchive? archive); - Guid archiveId = archive.InnerId; - - long trimId = appDbContext.GachaItems - .Where(i => i.ArchiveId == archiveId) - .OrderBy(i => i.Id) - .FirstOrDefault()?.Id ?? long.MaxValue; - - logger.LogInformation("Last Id to trim with: [{Id}]", trimId); - - _ = uigf.IsCurrentVersionSupported(out UIGFVersion version); - - IEnumerable toAdd = version switch - { - UIGFVersion.Major2Minor3OrHigher => uigf.List - .OrderByDescending(i => i.Id) - .Where(i => i.Id < trimId) - .Select(i => GachaItem.From(archiveId, i)), - UIGFVersion.Major2Minor2OrLower => uigf.List - .OrderByDescending(i => i.Id) - .Where(i => i.Id < trimId) - .Select(i => GachaItem.From(archiveId, i, context.GetItemId(i))), - _ => Enumerable.Empty(), - }; - - await appDbContext.GachaItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false); - return archive; - } + await gachaLogDbService.AddGachaItemsAsync(toAdd).ConfigureAwait(false); + return archive; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 0a856dee..55059004 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -65,19 +65,19 @@ internal sealed partial class GameService : IGameService // Cannot find in setting if (string.IsNullOrEmpty(appOptions.GamePath)) { - IEnumerable gameLocators = scope.ServiceProvider.GetRequiredService>(); + IGameLocatorFactory locatorFactory = scope.ServiceProvider.GetRequiredService(); // Try locate by unity log - ValueResult result = await gameLocators - .Pick(nameof(UnityLogGameLocator)) + ValueResult result = await locatorFactory + .Create(GameLocationSource.UnityLog) .LocateGamePathAsync() .ConfigureAwait(false); if (!result.IsOk) { // Try locate by registry - result = await gameLocators - .Pick(nameof(RegistryLauncherLocator)) + result = await locatorFactory + .Create(GameLocationSource.Registry) .LocateGamePathAsync() .ConfigureAwait(false); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSource.cs new file mode 100644 index 00000000..44e82b14 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game.Locator; + +internal enum GameLocationSource +{ + Registry, + UnityLog, + Manual, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocatorFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocatorFactory.cs new file mode 100644 index 00000000..bcd7ef91 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocatorFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game.Locator; + +[ConstructorGenerated] +[Injection(InjectAs.Transient, typeof(IGameLocatorFactory))] +internal sealed partial class GameLocatorFactory : IGameLocatorFactory +{ + private readonly IServiceProvider serviceProvider; + + public IGameLocator Create(GameLocationSource source) + { + return source switch + { + GameLocationSource.Registry => serviceProvider.GetRequiredService(), + GameLocationSource.UnityLog => serviceProvider.GetRequiredService(), + GameLocationSource.Manual => serviceProvider.GetRequiredService(), + _ => throw Must.NeverHappen(), + }; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs index a5d9a95b..a3b5ed9f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs @@ -9,12 +9,12 @@ namespace Snap.Hutao.Service.Game.Locator; /// 游戏位置定位器 /// [HighQuality] -internal interface IGameLocator : INamedService +internal interface IGameLocator { /// /// 异步获取游戏位置 /// 路径应当包含游戏文件名称 /// /// 游戏位置 - Task> LocateGamePathAsync(); + ValueTask> LocateGamePathAsync(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocatorFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocatorFactory.cs new file mode 100644 index 00000000..0095f816 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocatorFactory.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game.Locator; + +internal interface IGameLocatorFactory +{ + IGameLocator Create(GameLocationSource source); +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs index f944f9a2..d685bee8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs @@ -12,17 +12,14 @@ namespace Snap.Hutao.Service.Game.Locator; /// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGameLocator))] +[Injection(InjectAs.Transient)] internal sealed partial class ManualGameLocator : IGameLocator { private readonly ITaskContext taskContext; private readonly IPickerFactory pickerFactory; /// - public string Name { get => nameof(ManualGameLocator); } - - /// - public async Task> LocateGamePathAsync() + public async ValueTask> LocateGamePathAsync() { await taskContext.SwitchToMainThreadAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs index 65688bc4..5f9da921 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs @@ -13,16 +13,13 @@ namespace Snap.Hutao.Service.Game.Locator; /// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGameLocator))] +[Injection(InjectAs.Transient)] internal sealed partial class RegistryLauncherLocator : IGameLocator { private readonly ITaskContext taskContext; /// - public string Name { get => nameof(RegistryLauncherLocator); } - - /// - public async Task> LocateGamePathAsync() + public async ValueTask> LocateGamePathAsync() { await taskContext.SwitchToBackgroundAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs index 01eef79a..38866257 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs @@ -12,16 +12,13 @@ namespace Snap.Hutao.Service.Game.Locator; /// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGameLocator))] +[Injection(InjectAs.Transient)] internal sealed partial class UnityLogGameLocator : IGameLocator { private readonly ITaskContext taskContext; /// - public string Name { get => nameof(UnityLogGameLocator); } - - /// - public async Task> LocateGamePathAsync() + public async ValueTask> LocateGamePathAsync() { await taskContext.SwitchToBackgroundAsync(); @@ -32,11 +29,11 @@ internal sealed partial class UnityLogGameLocator : IGameLocator // Fallback to the CN server. string logFilePathFinal = File.Exists(logFilePathOversea) ? logFilePathOversea : logFilePathChinese; - using (TempFile? tempFile = TempFile.CopyFrom(logFilePathFinal)) + if (TempFile.CopyFrom(logFilePathFinal) is TempFile file) { - if (tempFile != null) + using (file) { - string content = await File.ReadAllTextAsync(tempFile.Path).ConfigureAwait(false); + string content = await File.ReadAllTextAsync(file.Path).ConfigureAwait(false); Match matchResult = WarmupFileLine().Match(content); if (!matchResult.Success) @@ -44,17 +41,17 @@ internal sealed partial class UnityLogGameLocator : IGameLocator return new(false, SH.ServiceGameLocatorUnityLogGamePathNotFound); } - string entryName = matchResult.Groups[0].Value.Replace("_Data", ".exe"); + string entryName = $"{matchResult.Value}.exe"; string fullPath = Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName)); return new(true, fullPath); } - else - { - return new(false, SH.ServiceGameLocatorUnityLogFileNotFound); - } + } + else + { + return new(false, SH.ServiceGameLocatorUnityLogFileNotFound); } } - [GeneratedRegex(@"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)")] + [GeneratedRegex(@".:/.+(?:GenshinImpact|YuanShen)(?=_Data)")] private static partial Regex WarmupFileLine(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs index aec1f517..05abdb5f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs @@ -19,7 +19,6 @@ namespace Snap.Hutao.Service.Game.Package; /// [HighQuality] [ConstructorGenerated(ResolveHttpClient = true)] -[Injection(InjectAs.Transient)] [HttpClient(HttpClientConfiguration.Default)] internal sealed partial class PackageConverter { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/LocaleNames.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/LocaleNames.cs new file mode 100644 index 00000000..03077deb --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/LocaleNames.cs @@ -0,0 +1,71 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Extensions.Options; +using Snap.Hutao.Core; +using System.Collections.Immutable; +using System.Globalization; +using System.IO; + +namespace Snap.Hutao.Service.Metadata; + +/// +/// 本地化名称 +/// +internal static class LocaleNames +{ + public const string DE = "DE"; // German + public const string EN = "EN"; // English + public const string ES = "ES"; // Spanish + public const string FR = "FR"; // French + public const string ID = "ID"; // Indonesian + public const string IT = "IT"; // Italian + public const string JP = "JP"; // Japanese + public const string KR = "KR"; // Korean + public const string PT = "PT"; // Portuguese + public const string RU = "RU"; // Russian + public const string TH = "TH"; // Thai + public const string TR = "TR"; // Turkish + public const string VI = "VI"; // Vietnamese + public const string CHS = "CHS"; // Chinese (Simplified) + public const string CHT = "CHT"; // Chinese (Traditional) + + public static readonly ImmutableDictionary LanguageNameLocaleNameMap = new Dictionary() + { + ["de"] = DE, + ["en"] = EN, + ["es"] = ES, + ["fr"] = FR, + ["id"] = ID, + ["it"] = IT, + ["ja"] = JP, + ["ko"] = KR, + ["pt"] = PT, + ["ru"] = RU, + ["th"] = TH, + ["tr"] = TR, + ["vi"] = VI, + ["zh-Hans"] = CHS, + ["zh-Hant"] = CHT, + [string.Empty] = CHS, // Fallback to Chinese. + }.ToImmutableDictionary(); + + public static readonly ImmutableDictionary LocaleNameLanguageCodeMap = new Dictionary() + { + [DE] = "de-de", + [EN] = "en-us", + [ES] = "es-es", + [FR] = "fr-fr", + [ID] = "id-id", + [IT] = "it-it", + [JP] = "ja-jp", + [KR] = "ko-kr", + [PT] = "pt-pt", + [RU] = "ru-ru", + [TH] = "th-th", + [TR] = "tr-tr", + [VI] = "vi-vn", + [CHS] = "zh-cn", + [CHT] = "zh-tw", + }.ToImmutableDictionary(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs index 24ad480f..b4acaf62 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs @@ -16,8 +16,6 @@ namespace Snap.Hutao.Service.Metadata; [Injection(InjectAs.Singleton)] internal sealed partial class MetadataOptions : IOptions { - - private readonly AppOptions appOptions; private readonly RuntimeOptions hutaoOptions; @@ -132,65 +130,4 @@ internal sealed partial class MetadataOptions : IOptions return Web.HutaoEndpoints.HutaoMetadata2File(LocaleName, fileNameWithExtension); #endif } -} - -/// -/// 本地化名称 -/// -internal static class LocaleNames -{ - public const string DE = "DE"; // German - public const string EN = "EN"; // English - public const string ES = "ES"; // Spanish - public const string FR = "FR"; // French - public const string ID = "ID"; // Indonesian - public const string IT = "IT"; // Italian - public const string JP = "JP"; // Japanese - public const string KR = "KR"; // Korean - public const string PT = "PT"; // Portuguese - public const string RU = "RU"; // Russian - public const string TH = "TH"; // Thai - public const string TR = "TR"; // Turkish - public const string VI = "VI"; // Vietnamese - public const string CHS = "CHS"; // Chinese (Simplified) - public const string CHT = "CHT"; // Chinese (Traditional) - - public static readonly ImmutableDictionary LanguageNameLocaleNameMap = new Dictionary() - { - ["de"] = DE, - ["en"] = EN, - ["es"] = ES, - ["fr"] = FR, - ["id"] = ID, - ["it"] = IT, - ["ja"] = JP, - ["ko"] = KR, - ["pt"] = PT, - ["ru"] = RU, - ["th"] = TH, - ["tr"] = TR, - ["vi"] = VI, - ["zh-Hans"] = CHS, - ["zh-Hant"] = CHT, - [string.Empty] = CHS, // Fallback to Chinese. - }.ToImmutableDictionary(); - - public static readonly ImmutableDictionary LocaleNameLanguageCodeMap = new Dictionary() - { - [DE] = "de-de", - [EN] = "en-us", - [ES] = "es-es", - [FR] = "fr-fr", - [ID] = "id-id", - [IT] = "it-it", - [JP] = "ja-jp", - [KR] = "ko-kr", - [PT] = "pt-pt", - [RU] = "ru-ru", - [TH] = "th-th", - [TR] = "tr-tr", - [VI] = "vi-vn", - [CHS] = "zh-cn", - [CHT] = "zh-tw", - }.ToImmutableDictionary(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs b/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs index 85bd442a..1c6ba960 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.ViewModel.User; @@ -63,7 +64,8 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi private async Task RefreshSpiralAbyssCoreAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule) { Response response = await serviceProvider - .PickRequiredService(userAndUid.User.IsOversea) + .GetRequiredService>() + .Create(userAndUid.User.IsOversea) .GetSpiralAbyssAsync(userAndUid, schedule) .ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index d4e7344d..4df98dc6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Messaging; using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Message; using Snap.Hutao.Model.Entity.Database; @@ -220,7 +221,8 @@ internal sealed partial class UserService : IUserService using (IServiceScope scope = serviceProvider.CreateScope()) { Response cookieTokenResponse = await scope.ServiceProvider - .PickRequiredService(user.Entity.IsOversea) + .GetRequiredService>() + .Create(user.Entity.IsOversea) .GetCookieAccountInfoBySTokenAsync(user.Entity) .ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs index 607b198b..0ff748e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs @@ -14,6 +14,7 @@ namespace Snap.Hutao.View.Control; /// 公告内容页面 /// [HighQuality] +[DependencyProperty("Announcement", typeof(Announcement))] internal sealed partial class AnnouncementContentViewer : Microsoft.UI.Xaml.Controls.UserControl { // apply in dark mode, Dark theme @@ -42,8 +43,6 @@ internal sealed partial class AnnouncementContentViewer : Microsoft.UI.Xaml.Cont } """; - private static readonly DependencyProperty AnnouncementProperty = Property.Depend(nameof(Announcement)); - /// /// 构造一个新的公告窗体 /// @@ -52,15 +51,6 @@ internal sealed partial class AnnouncementContentViewer : Microsoft.UI.Xaml.Cont InitializeComponent(); } - /// - /// 目标公告 - /// - public Announcement Announcement - { - get => (Announcement)GetValue(AnnouncementProperty); - set => SetValue(AnnouncementProperty, value); - } - private static string? GenerateHtml(Announcement? announcement, ElementTheme theme) { if (announcement == null) diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/BaseValueSlider.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/BaseValueSlider.xaml.cs index ff017a29..14c49b1b 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/BaseValueSlider.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/BaseValueSlider.xaml.cs @@ -11,11 +11,10 @@ namespace Snap.Hutao.View.Control; /// /// 基础数值滑动条 /// +[DependencyProperty("BaseValueInfo", typeof(BaseValueInfo))] +[DependencyProperty("IsPromoteVisible", typeof(bool), true)] internal sealed partial class BaseValueSlider : UserControl { - private static readonly DependencyProperty BaseValueInfoProperty = Property.Depend(nameof(BaseValueInfo)); - private static readonly DependencyProperty IsPromoteVisibleProperty = Property.DependBoxed(nameof(IsPromoteVisible), BoxedValues.True); - /// /// 构造一个新的基础数值滑动条 /// @@ -23,22 +22,4 @@ internal sealed partial class BaseValueSlider : UserControl { InitializeComponent(); } - - /// - /// 基础数值信息 - /// - public BaseValueInfo BaseValueInfo - { - get => (BaseValueInfo)GetValue(BaseValueInfoProperty); - set => SetValue(BaseValueInfoProperty, value); - } - - /// - /// 提升按钮是否可见 - /// - public bool IsPromoteVisible - { - get => (bool)GetValue(IsPromoteVisibleProperty); - set => SetValue(IsPromoteVisibleProperty, value); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/BottomTextControl.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/BottomTextControl.xaml.cs index 26e54046..0e965507 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/BottomTextControl.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/BottomTextControl.xaml.cs @@ -14,12 +14,11 @@ namespace Snap.Hutao.View.Control; /// [HighQuality] [ContentProperty(Name = nameof(TopContent))] +[DependencyProperty("Text", typeof(string), "", nameof(OnTextChanged))] +[DependencyProperty("TopContent", typeof(UIElement), default!, nameof(OnContentChanged))] +[DependencyProperty("Fill", typeof(Brush), default!, nameof(OnFillChanged))] internal sealed partial class BottomTextControl : ContentControl { - private static readonly DependencyProperty TextProperty = Property.Depend(nameof(Text), string.Empty, OnTextChanged); - private static readonly DependencyProperty TopContentProperty = Property.Depend(nameof(TopContent), default!, OnContentChanged); - private static readonly DependencyProperty FillProperty = Property.Depend(nameof(Fill), default(Brush), OnFillChanged); - /// /// 构造一个新的底部带有文本的控件 /// @@ -28,33 +27,6 @@ internal sealed partial class BottomTextControl : ContentControl InitializeComponent(); } - /// - /// 顶部内容 - /// - public UIElement TopContent - { - get => (UIElement)GetValue(TopContentProperty); - set => SetValue(TopContentProperty, value); - } - - /// - /// 文本 - /// - public string Text - { - get => (string)GetValue(TextProperty); - set => SetValue(TextProperty, value); - } - - /// - /// 填充 - /// - public Brush Fill - { - get => (Brush)GetValue(FillProperty); - set => SetValue(FillProperty, value); - } - private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) { ((BottomTextControl)sender).TextHost.Text = (string)args.NewValue; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs index 6842a84d..cfee7ed0 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs @@ -12,14 +12,10 @@ namespace Snap.Hutao.View.Control; /// 描述参数组合框 /// [HighQuality] +[DependencyProperty("Source", typeof(List>), default!, nameof(OnSourceChanged))] +[DependencyProperty("PreferredSelectedIndex", typeof(int), 0)] internal sealed partial class DescParamComboBox : UserControl { - private static readonly DependencyProperty SourceProperty = Property - .Depend>>(nameof(Source), default!, OnSourceChanged); - - private static readonly DependencyProperty PreferredSelectedIndexProperty = Property - .DependBoxed(nameof(PreferredSelectedIndex), BoxedValues.Int32Zero); - /// /// 构造一个新的描述参数组合框 /// @@ -28,31 +24,13 @@ internal sealed partial class DescParamComboBox : UserControl InitializeComponent(); } - /// - /// 技能列表 - /// - public List> Source - { - get => (List>)GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - - /// - /// 期望的选中索引 - /// - public int PreferredSelectedIndex - { - get => (int)GetValue(PreferredSelectedIndexProperty); - set => SetValue(PreferredSelectedIndexProperty, value); - } - private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) { // Some of the {x:Bind} feature is not working properly, // so we use this simple code behind approach to achieve selection function if (sender is DescParamComboBox descParamComboBox) { - if (args.NewValue != args.OldValue && args.NewValue is IList> list) + if (args.NewValue != args.OldValue && args.NewValue is List> list) { descParamComboBox.ItemHost.ItemsSource = list; descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count - 1); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml.cs index 41ee5d44..021d3406 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml.cs @@ -12,12 +12,11 @@ namespace Snap.Hutao.View.Control; /// 物品图标 /// [HighQuality] +[DependencyProperty("Quality", typeof(QualityType), QualityType.QUALITY_NONE)] +[DependencyProperty("Icon", typeof(Uri))] +[DependencyProperty("Badge", typeof(Uri))] internal sealed partial class ItemIcon : UserControl { - private static readonly DependencyProperty QualityProperty = Property.Depend(nameof(Quality), QualityType.QUALITY_NONE); - private static readonly DependencyProperty IconProperty = Property.Depend(nameof(Icon)); - private static readonly DependencyProperty BadgeProperty = Property.Depend(nameof(Badge)); - /// /// 构造一个新的物品图标 /// @@ -25,31 +24,4 @@ internal sealed partial class ItemIcon : UserControl { InitializeComponent(); } - - /// - /// 等阶 - /// - public QualityType Quality - { - get => (QualityType)GetValue(QualityProperty); - set => SetValue(QualityProperty, value); - } - - /// - /// 图标 - /// - public Uri Icon - { - get => (Uri)GetValue(IconProperty); - set => SetValue(IconProperty, value); - } - - /// - /// 角标 - /// - public Uri Badge - { - get => (Uri)GetValue(BadgeProperty); - set => SetValue(BadgeProperty, value); - } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs index a0b1f24d..01fc79e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs @@ -12,12 +12,11 @@ namespace Snap.Hutao.View.Control; /// 技能展柜 /// [HighQuality] +[DependencyProperty("Skills", typeof(IList))] +[DependencyProperty("Selected", typeof(object))] +[DependencyProperty("ItemTemplate", typeof(DataTemplate))] internal sealed partial class SkillPivot : UserControl { - private static readonly DependencyProperty SkillsProperty = Property.Depend(nameof(Skills)); - private static readonly DependencyProperty SelectedProperty = Property.Depend(nameof(Selected)); - private static readonly DependencyProperty ItemTemplateProperty = Property.Depend(nameof(ItemTemplate)); - /// /// 创建一个新的技能展柜 /// @@ -25,31 +24,4 @@ internal sealed partial class SkillPivot : UserControl { InitializeComponent(); } - - /// - /// 技能列表 - /// - public IList Skills - { - get => (IList)GetValue(SkillsProperty); - set => SetValue(SkillsProperty, value); - } - - /// - /// 选中的项 - /// - public object Selected - { - get => GetValue(SelectedProperty); - set => SetValue(SelectedProperty, value); - } - - /// - /// 项目模板 - /// - public DataTemplate ItemTemplate - { - get => (DataTemplate)GetValue(ItemTemplateProperty); - set => SetValue(ItemTemplateProperty, value); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml.cs index 4400ec27..a7ee521a 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml.cs @@ -11,10 +11,9 @@ namespace Snap.Hutao.View.Control; /// 统计卡片 /// [HighQuality] +[DependencyProperty("ShowUpPull", typeof(bool), true)] internal sealed partial class StatisticsCard : UserControl { - private static readonly DependencyProperty ShowUpPullProperty = Property.DependBoxed(nameof(ShowUpPull), BoxedValues.True); - /// /// 构造一个新的统计卡片 /// @@ -22,13 +21,4 @@ internal sealed partial class StatisticsCard : UserControl { InitializeComponent(); } - - /// - /// 显示Up抽数 - /// - public bool ShowUpPull - { - get => (bool)GetValue(ShowUpPullProperty); - set => SetValue(ShowUpPullProperty, value); - } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToGradientColorConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToGradientColorConverter.cs index c819d795..34f95291 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToGradientColorConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToGradientColorConverter.cs @@ -13,53 +13,21 @@ namespace Snap.Hutao.View.Converter; /// /// Int32 转 色阶颜色 /// -internal sealed class Int32ToGradientColorConverter : DependencyObject, IValueConverter +[DependencyProperty("MaximumValue", typeof(int), 90)] +[DependencyProperty("MinimumValue", typeof(int), 1)] +[DependencyProperty("Maximum", typeof(Color))] +[DependencyProperty("Minimum", typeof(Color))] +internal sealed partial class Int32ToGradientColorConverter : DependencyValueConverter { - private static readonly DependencyProperty MaximumProperty = Property.Depend(nameof(Maximum), StructMarshal.Color(0xFFFF4949)); - private static readonly DependencyProperty MinimumProperty = Property.Depend(nameof(Minimum), StructMarshal.Color(0xFF48FF7A)); - private static readonly DependencyProperty MaximumValueProperty = Property.Depend(nameof(MaximumValue), 90); - private static readonly DependencyProperty MinimumValueProperty = Property.Depend(nameof(MinimumValue), 1); - - /// - /// 最小颜色 - /// - public Color Minimum + public Int32ToGradientColorConverter() { - get => (Color)GetValue(MinimumProperty); - set => SetValue(MinimumProperty, value); + Minimum = StructMarshal.Color(0xFFFF4949); + Maximum = StructMarshal.Color(0xFF48FF7A); } - /// - /// 最大颜色 - /// - public Color Maximum + public override Color Convert(int from) { - get => (Color)GetValue(MaximumProperty); - set => SetValue(MaximumProperty, value); - } - - /// - /// 最小值 - /// - public int MinimumValue - { - get => (int)GetValue(MinimumValueProperty); - set => SetValue(MinimumValueProperty, value); - } - - /// - /// 最大值 - /// - public int MaximumValue - { - get => (int)GetValue(MaximumValueProperty); - set => SetValue(MaximumValueProperty, value); - } - - /// - public object Convert(object value, Type targetType, object parameter, string language) - { - double n = (value != null ? (int)value : MinimumValue) - MinimumValue; + double n = Math.Clamp(from, MinimumValue, MaximumValue) - MinimumValue; int step = MaximumValue - MinimumValue; double a = Minimum.A + ((Maximum.A - Minimum.A) * n / step); double r = Minimum.R + ((Maximum.R - Minimum.R) * n / step); @@ -73,10 +41,4 @@ internal sealed class Int32ToGradientColorConverter : DependencyObject, IValueCo color.B = (byte)b; return color; } - - /// - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/PanelSelectorModeConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/PanelSelectorModeConverter.cs index d4bc0629..2224920c 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Converter/PanelSelectorModeConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/PanelSelectorModeConverter.cs @@ -9,29 +9,10 @@ namespace Snap.Hutao.View.Converter; /// /// 条件转换器 /// -internal sealed class PanelSelectorModeConverter : DependencyValueConverter +[DependencyProperty("ListValue", typeof(object))] +[DependencyProperty("GridValue", typeof(object))] +internal sealed partial class PanelSelectorModeConverter : DependencyValueConverter { - private static readonly DependencyProperty ListValueProperty = Property.Depend(nameof(ListValue)); - private static readonly DependencyProperty GridValueProperty = Property.Depend(nameof(GridValue)); - - /// - /// 列表值 - /// - public object ListValue - { - get => GetValue(ListValueProperty); - set => SetValue(ListValueProperty, value); - } - - /// - /// 网格值 - /// - public object GridValue - { - get => GetValue(GridValueProperty); - set => SetValue(GridValueProperty, value); - } - /// public override object Convert(string from) { diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs index 811a8963..a77788f4 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs @@ -13,10 +13,9 @@ namespace Snap.Hutao.View.Dialog; /// 成就对话框 /// [HighQuality] +[DependencyProperty("UIAF", typeof(UIAF))] internal sealed partial class AchievementImportDialog : ContentDialog { - private static readonly DependencyProperty UIAFProperty = Property.Depend(nameof(UIAF), default(UIAF)); - private readonly ITaskContext taskContext; /// @@ -33,15 +32,6 @@ internal sealed partial class AchievementImportDialog : ContentDialog UIAF = uiaf; } - /// - /// UIAF数据 - /// - public UIAF UIAF - { - get => (UIAF)GetValue(UIAFProperty); - set => SetValue(UIAFProperty, value); - } - /// /// 异步获取导入选项 /// diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs index 6e046917..8d23ef04 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs @@ -13,11 +13,10 @@ namespace Snap.Hutao.View.Dialog; /// 养成计算对话框 /// [HighQuality] +[DependencyProperty("Avatar", typeof(ICalculableAvatar))] +[DependencyProperty("Weapon", typeof(ICalculableWeapon))] internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog { - private static readonly DependencyProperty AvatarProperty = Property.Depend(nameof(Avatar)); - private static readonly DependencyProperty WeaponProperty = Property.Depend(nameof(Weapon)); - private readonly ITaskContext taskContext; /// @@ -37,24 +36,6 @@ internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog DataContext = this; } - /// - /// 角色 - /// - public ICalculableAvatar? Avatar - { - get => (ICalculableAvatar?)GetValue(AvatarProperty); - set => SetValue(AvatarProperty, value); - } - - /// - /// 武器 - /// - public ICalculableWeapon? Weapon - { - get => (ICalculableWeapon?)GetValue(WeaponProperty); - set => SetValue(WeaponProperty, value); - } - /// /// 异步获取提升差异 /// diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs index 5892220a..f6a6f619 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs @@ -12,10 +12,9 @@ namespace Snap.Hutao.View.Dialog; /// 祈愿记录导入对话框 /// [HighQuality] +[DependencyProperty("UIGF", typeof(UIGF))] internal sealed partial class GachaLogImportDialog : ContentDialog { - private static readonly DependencyProperty UIGFProperty = Property.Depend(nameof(UIGF), default(UIGF)); - private readonly ITaskContext taskContext; /// @@ -32,15 +31,6 @@ internal sealed partial class GachaLogImportDialog : ContentDialog UIGF = uigf; } - /// - /// UIAF数据 - /// - public UIGF UIGF - { - get => (UIGF)GetValue(UIGFProperty); - set => SetValue(UIGFProperty, value); - } - /// /// 异步获取导入选项 /// diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml.cs index 748adf12..5117b29e 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml.cs @@ -14,10 +14,9 @@ namespace Snap.Hutao.View.Dialog; /// 祈愿记录刷新进度对话框 /// [HighQuality] +[DependencyProperty("Status", typeof(GachaLogFetchStatus))] internal sealed partial class GachaLogRefreshProgressDialog : ContentDialog { - private static readonly DependencyProperty StatusProperty = Property.Depend(nameof(Status)); - /// /// 构造一个新的对话框 /// @@ -28,15 +27,6 @@ internal sealed partial class GachaLogRefreshProgressDialog : ContentDialog XamlRoot = serviceProvider.GetRequiredService().Content.XamlRoot; } - /// - /// 刷新状态 - /// - public GachaLogFetchStatus Status - { - get => (GachaLogFetchStatus)GetValue(StatusProperty); - set => SetValue(StatusProperty, value); - } - /// /// 接收进度更新 /// diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs index d92ea5e4..642120bd 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs @@ -12,10 +12,9 @@ namespace Snap.Hutao.View.Dialog; /// 启动游戏客户端转换对话框 /// [HighQuality] +[DependencyProperty("State", typeof(PackageReplaceStatus))] internal sealed partial class LaunchGamePackageConvertDialog : ContentDialog { - private static readonly DependencyProperty StateProperty = Property.Depend(nameof(State)); - /// /// 构造一个新的启动游戏客户端转换对话框 /// @@ -27,13 +26,4 @@ internal sealed partial class LaunchGamePackageConvertDialog : ContentDialog DataContext = this; } - - /// - /// 描述 - /// - public PackageReplaceStatus State - { - get => (PackageReplaceStatus)GetValue(StateProperty); - set => SetValue(StateProperty, value); - } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Helper/NavHelper.cs b/src/Snap.Hutao/Snap.Hutao/View/Helper/NavHelper.cs index 1bd078de..77e5ecaa 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Helper/NavHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Helper/NavHelper.cs @@ -13,48 +13,8 @@ namespace Snap.Hutao.View.Helper; /// [HighQuality] [SuppressMessage("", "SH001")] -public sealed class NavHelper +[DependencyProperty("NavigateTo", typeof(Type), IsAttached = true, AttachedType = typeof(NavigationViewItem))] +[DependencyProperty("ExtraData", typeof(object), IsAttached = true, AttachedType = typeof(NavigationViewItem))] +public sealed partial class NavHelper { - private static readonly DependencyProperty NavigateToProperty = Property.Attach("NavigateTo"); - private static readonly DependencyProperty ExtraDataProperty = Property.Attach("ExtraData"); - - /// - /// 获取导航项的目标页面类型 - /// - /// 待获取的导航项 - /// 目标页面类型 - public static Type? GetNavigateTo(NavigationViewItem? item) - { - return item?.GetValue(NavigateToProperty) as Type; - } - - /// - /// 设置导航项的目标页面类型 - /// - /// 待设置的导航项 - /// 新的目标页面类型 - public static void SetNavigateTo(NavigationViewItem item, Type value) - { - item.SetValue(NavigateToProperty, value); - } - - /// - /// 获取导航项的目标页面的额外数据 - /// - /// 待获取的导航项 - /// 目标页面类型的额外数据 - public static object? GetExtraData(NavigationViewItem? item) - { - return item?.GetValue(ExtraDataProperty); - } - - /// - /// 设置导航项的目标页面类型 - /// - /// 待设置的导航项 - /// 新的目标页面类型 - public static void SetExtraData(NavigationViewItem item, object value) - { - item.SetValue(ExtraDataProperty, value); - } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/InfoBarView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/InfoBarView.xaml.cs index ab548e62..abc04977 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/InfoBarView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/InfoBarView.xaml.cs @@ -14,9 +14,9 @@ namespace Snap.Hutao.View; /// /// 信息条视图 /// +[DependencyProperty("InfoBars", typeof(ObservableCollection))] internal sealed partial class InfoBarView : UserControl { - private static readonly DependencyProperty InfoBarsProperty = Property.Depend>(nameof(InfoBars)); private readonly IInfoBarService infoBarService; /// @@ -32,15 +32,6 @@ internal sealed partial class InfoBarView : UserControl VisibilityButton.IsChecked = LocalSetting.Get(SettingKeys.IsInfoBarToggleChecked, true); } - /// - /// 信息条 - /// - public ObservableCollection InfoBars - { - get => (ObservableCollection)GetValue(InfoBarsProperty); - set => SetValue(InfoBarsProperty, value); - } - private void OnVisibilityButtonCheckedChanged(object sender, RoutedEventArgs e) { LocalSetting.Set(SettingKeys.IsInfoBarToggleChecked, ((ToggleButton)sender).IsChecked ?? false); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs index 9bcb5d66..c234411b 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs @@ -71,7 +71,7 @@ internal sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.P Cookie stokenV1 = Cookie.FromSToken(loginTicketCookie[Cookie.LOGIN_UID], multiTokenMap[Cookie.STOKEN]); Response loginResultResponse = await Ioc.Default - .GetRequiredService() + .GetRequiredService() .LoginBySTokenAsync(stokenV1) .ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs index 5431503d..39de7e50 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs @@ -99,6 +99,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel { if (await gachaLogService.InitializeAsync(CancellationToken).ConfigureAwait(false)) { + ArgumentNullException.ThrowIfNull(gachaLogService.ArchiveCollection); ObservableCollection archives = gachaLogService.ArchiveCollection; await taskContext.SwitchToMainThreadAsync(); Archives = archives; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudUidOperationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudEntryOperationViewModel.cs similarity index 59% rename from src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudUidOperationViewModel.cs rename to src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudEntryOperationViewModel.cs index 5caaf99f..fc6c5ce6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudUidOperationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudEntryOperationViewModel.cs @@ -1,28 +1,33 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Web.Hutao.GachaLog; + namespace Snap.Hutao.ViewModel.GachaLog; /// -/// 胡桃云Uid操作视图模型 +/// 胡桃云记录操作视图模型 /// -internal sealed class HutaoCloudUidOperationViewModel +internal sealed class HutaoCloudEntryOperationViewModel { /// - /// 构造一个新的 胡桃云Uid操作视图模型 + /// 构造一个新的胡桃云记录操作视图模型 /// - /// Uid + /// 记录 /// 获取记录 /// 删除记录 - public HutaoCloudUidOperationViewModel(string uid, ICommand retrieve, ICommand delete) + public HutaoCloudEntryOperationViewModel(GachaEntry entry, ICommand retrieve, ICommand delete) { - Uid = uid; + Uid = entry.Uid; + ItemCount = entry.ItemCount; RetrieveCommand = retrieve; DeleteCommand = delete; } public string Uid { get; } + public int ItemCount { get; } + /// /// 获取云端数据 /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudStatisticsViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudStatisticsViewModel.cs index 6efbd713..c9f96c07 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudStatisticsViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudStatisticsViewModel.cs @@ -32,7 +32,7 @@ internal sealed class HutaoCloudStatisticsViewModel : Abstraction.ViewModelSlim { ITaskContext taskContext = ServiceProvider.GetRequiredService(); await taskContext.SwitchToBackgroundAsync(); - IHutaoCloudService hutaoCloudService = ServiceProvider.GetRequiredService(); + IGachaLogHutaoCloudService hutaoCloudService = ServiceProvider.GetRequiredService(); (bool isOk, HutaoStatistics statistics) = await hutaoCloudService.GetCurrentEventStatisticsAsync().ConfigureAwait(false); if (isOk) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudViewModel.cs index 9e3ddfae..0d67d12c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/HutaoCloudViewModel.cs @@ -9,6 +9,7 @@ using Snap.Hutao.Service.GachaLog; using Snap.Hutao.Service.Hutao; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification; +using Snap.Hutao.Web.Hutao.GachaLog; using Snap.Hutao.Web.Response; using System.Collections.ObjectModel; @@ -22,18 +23,18 @@ namespace Snap.Hutao.ViewModel.GachaLog; internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel { private readonly IContentDialogFactory contentDialogFactory; - private readonly IHutaoCloudService hutaoCloudService; + private readonly IGachaLogHutaoCloudService hutaoCloudService; private readonly IServiceProvider serviceProvider; private readonly IInfoBarService infoBarService; private readonly ITaskContext taskContext; private readonly HutaoUserOptions options; - private ObservableCollection? uidOperations; + private ObservableCollection? uidOperations; /// /// Uid集合 /// - public ObservableCollection? UidOperations { get => uidOperations; set => SetProperty(ref uidOperations, value); } + public ObservableCollection? UidOperations { get => uidOperations; set => SetProperty(ref uidOperations, value); } /// /// 选项 @@ -71,6 +72,12 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel IsInitialized = true; } + [Command("NavigateToAfdianSkuCommand")] + private static async Task NavigateToAfdianSkuAsync() + { + await Windows.System.Launcher.LaunchUriAsync("https://afdian.net/item/80d3b9decf9011edb5f452540025c377".ToUri()); + } + [Command("UploadCommand")] private async Task UploadAsync(GachaArchive? gachaArchive) { @@ -127,23 +134,17 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel .Navigate(INavigationAwaiter.Default); } - [Command("NavigateToAfdianSkuCommand")] - private async Task NavigateToAfdianSkuAsync() - { - await Windows.System.Launcher.LaunchUriAsync("https://afdian.net/item/80d3b9decf9011edb5f452540025c377".ToUri()); - } - private async Task RefreshUidCollectionAsync() { if (Options.IsCloudServiceAllowed) { - Response> resp = await hutaoCloudService.GetUidsAsync().ConfigureAwait(false); + Response> resp = await hutaoCloudService.GetGachaEntriesAsync().ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync(); if (resp.IsOk()) { UidOperations = resp.Data! - .SelectList(uid => new HutaoCloudUidOperationViewModel(uid, RetrieveCommand, DeleteCommand)) + .SelectList(entry => new HutaoCloudEntryOperationViewModel(entry, RetrieveCommand, DeleteCommand)) .ToObservableCollection(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs index faf5a048..3fc8d36a 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -109,7 +109,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel [Command("SetGamePathCommand")] private async Task SetGamePathAsync() { - IGameLocator locator = serviceProvider.GetRequiredService>().Pick(nameof(ManualGameLocator)); + IGameLocator locator = serviceProvider.GetRequiredService().Create(GameLocationSource.Manual); (bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false); if (isOk) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs index add502d1..9cabdcd6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Passport; @@ -195,7 +196,8 @@ internal sealed class User : ObservableObject } Response lTokenResponse = await provider - .PickRequiredService(Entity.IsOversea) + .GetRequiredService>() + .Create(Entity.IsOversea) .GetLTokenBySTokenAsync(Entity, token) .ConfigureAwait(false); @@ -218,7 +220,8 @@ internal sealed class User : ObservableObject } Response cookieTokenResponse = await provider - .PickRequiredService(Entity.IsOversea) + .GetRequiredService>() + .Create(Entity.IsOversea) .GetCookieAccountInfoBySTokenAsync(Entity, token) .ConfigureAwait(false); @@ -236,7 +239,8 @@ internal sealed class User : ObservableObject private async Task TrySetUserInfoAsync(IServiceProvider provider, CancellationToken token) { Response response = await provider - .PickRequiredService(Entity.IsOversea) + .GetRequiredService>() + .Create(Entity.IsOversea) .GetUserFullInfoAsync(Entity, token) .ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/IUserClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/IUserClient.cs index 768dd9b9..d3c8abb6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/IUserClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/IUserClient.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Web.Response; namespace Snap.Hutao.Web.Hoyolab.Bbs.User; @@ -9,7 +8,7 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User; /// /// 用户信息客户端 /// -internal interface IUserClient : IOverseaSupport +internal interface IUserClient { /// /// 获取当前用户详细信息 @@ -18,4 +17,4 @@ internal interface IUserClient : IOverseaSupport /// 取消令牌 /// 详细信息 Task> GetUserFullInfoAsync(Model.Entity.User user, CancellationToken token = default); -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs index 100d3aca..968616c9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs @@ -16,19 +16,12 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User; [UseDynamicSecret] [ConstructorGenerated] [HttpClient(HttpClientConfiguration.XRpc)] -[Injection(InjectAs.Transient, typeof(IUserClient))] internal sealed partial class UserClient : IUserClient { private readonly JsonSerializerOptions options; private readonly ILogger logger; private readonly HttpClient httpClient; - /// - public bool IsOversea - { - get => false; - } - /// /// 获取当前用户详细信息 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientFactory.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientFactory.cs new file mode 100644 index 00000000..731319ba --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.DependencyInjection.Abstraction; + +namespace Snap.Hutao.Web.Hoyolab.Bbs.User; + +[Injection(InjectAs.Transient)] +[ConstructorGenerated(CallBaseConstructor = true)] +internal sealed partial class UserClientFactory : OverseaSupportFactory +{ +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs index e7d6b778..fd1a5f1a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs @@ -15,19 +15,12 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User; [UseDynamicSecret] [ConstructorGenerated] [HttpClient(HttpClientConfiguration.Default)] -[Injection(InjectAs.Transient, typeof(IUserClient))] internal sealed partial class UserClientOversea : IUserClient { private readonly HttpClient httpClient; private readonly JsonSerializerOptions options; private readonly ILogger logger; - /// - public bool IsOversea - { - get => true; - } - /// /// 获取当前用户详细信息,使用 LToken /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IPassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IPassportClient.cs index 5b32fd78..84b673c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IPassportClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IPassportClient.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Response; @@ -10,7 +9,7 @@ namespace Snap.Hutao.Web.Hoyolab.Passport; /// /// 通行证客户端 /// -internal interface IPassportClient : IOverseaSupport +internal interface IPassportClient { /// /// 异步获取 CookieToken diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs index 7bae8430..29d98dd1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs @@ -7,65 +7,55 @@ using Snap.Hutao.Web.Hoyolab.Annotation; using Snap.Hutao.Web.Hoyolab.DynamicSecret; using Snap.Hutao.Web.Response; using System.Net.Http; -using System.Net.Http.Json; namespace Snap.Hutao.Web.Hoyolab.Passport; /// -/// 通行证客户端 +/// 通行证客户端 XRPC 版 /// [HighQuality] [UseDynamicSecret] -[ConstructorGenerated(ResolveHttpClient = true)] +[ConstructorGenerated] [HttpClient(HttpClientConfiguration.XRpc2)] -internal sealed partial class PassportClient +internal sealed partial class PassportClient : IPassportClient { - private readonly ILogger logger; + private readonly ILogger logger; private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; /// - /// 异步验证 LToken + /// 异步获取 CookieToken /// /// 用户 /// 取消令牌 - /// 验证信息 - [ApiInformation(Cookie = CookieType.LToken)] - public async Task> VerifyLtokenAsync(User user, CancellationToken token) + /// cookie token + [ApiInformation(Cookie = CookieType.SToken, Salt = SaltType.PROD)] + public async Task> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token = default) { - Response? response = await httpClient - .SetUser(user, CookieType.LToken) - .TryCatchPostAsJsonAsync>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token) - .ConfigureAwait(false); - - return Response.Response.DefaultIfNull(response); - } - - /// - /// V1 SToken 登录 - /// - /// v1 SToken - /// 取消令牌 - /// 登录数据 - [ApiInformation(Salt = SaltType.PROD)] - public async Task> LoginBySTokenAsync(Cookie stokenV1, CancellationToken token = default) - { - HttpResponseMessage message = await httpClient - .SetHeader("Cookie", stokenV1.ToString()) + Response? resp = await httpClient + .SetUser(user, CookieType.SToken) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.PROD, true) - .PostAsync(ApiEndpoints.AccountGetSTokenByOldToken, null, token) - .ConfigureAwait(false); - - Response? resp = await message.Content - .ReadFromJsonAsync>(options, token) + .TryCatchGetFromJsonAsync>(ApiEndpoints.AccountGetCookieTokenBySToken, options, logger, token) .ConfigureAwait(false); return Response.Response.DefaultIfNull(resp); } - private class Timestamp + /// + /// 异步获取 LToken + /// + /// 用户 + /// 取消令牌 + /// uid 与 cookie token + [ApiInformation(Cookie = CookieType.SToken, Salt = SaltType.PROD)] + public async Task> GetLTokenBySTokenAsync(User user, CancellationToken token = default) { - [JsonPropertyName("t")] - public long Time { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + Response? resp = await httpClient + .SetUser(user, CookieType.SToken) + .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.PROD, true) + .TryCatchGetFromJsonAsync>(ApiEndpoints.AccountGetLTokenBySToken, options, logger, token) + .ConfigureAwait(false); + + return Response.Response.DefaultIfNull(resp); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index 395e0214..5858151c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -7,62 +7,65 @@ using Snap.Hutao.Web.Hoyolab.Annotation; using Snap.Hutao.Web.Hoyolab.DynamicSecret; using Snap.Hutao.Web.Response; using System.Net.Http; +using System.Net.Http.Json; namespace Snap.Hutao.Web.Hoyolab.Passport; /// -/// 通行证客户端 XRPC 版 +/// 通行证客户端 /// [HighQuality] [UseDynamicSecret] -[ConstructorGenerated] +[ConstructorGenerated(ResolveHttpClient = true)] [HttpClient(HttpClientConfiguration.XRpc2)] -[Injection(InjectAs.Transient, typeof(IPassportClient))] -internal sealed partial class PassportClient2 : IPassportClient +internal sealed partial class PassportClient2 { - private readonly ILogger logger; + private readonly ILogger logger; private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; - /// - public bool IsOversea - { - get => false; - } - /// - /// 异步获取 CookieToken + /// 异步验证 LToken /// /// 用户 /// 取消令牌 - /// cookie token - [ApiInformation(Cookie = CookieType.SToken, Salt = SaltType.PROD)] - public async Task> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token = default) + /// 验证信息 + [ApiInformation(Cookie = CookieType.LToken)] + public async Task> VerifyLtokenAsync(User user, CancellationToken token) { - Response? resp = await httpClient - .SetUser(user, CookieType.SToken) + Response? response = await httpClient + .SetUser(user, CookieType.LToken) + .TryCatchPostAsJsonAsync>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token) + .ConfigureAwait(false); + + return Response.Response.DefaultIfNull(response); + } + + /// + /// V1 SToken 登录 + /// + /// v1 SToken + /// 取消令牌 + /// 登录数据 + [ApiInformation(Salt = SaltType.PROD)] + public async Task> LoginBySTokenAsync(Cookie stokenV1, CancellationToken token = default) + { + HttpResponseMessage message = await httpClient + .SetHeader("Cookie", stokenV1.ToString()) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.PROD, true) - .TryCatchGetFromJsonAsync>(ApiEndpoints.AccountGetCookieTokenBySToken, options, logger, token) + .PostAsync(ApiEndpoints.AccountGetSTokenByOldToken, null, token) + .ConfigureAwait(false); + + Response? resp = await message.Content + .ReadFromJsonAsync>(options, token) .ConfigureAwait(false); return Response.Response.DefaultIfNull(resp); } - /// - /// 异步获取 LToken - /// - /// 用户 - /// 取消令牌 - /// uid 与 cookie token - [ApiInformation(Cookie = CookieType.SToken, Salt = SaltType.PROD)] - public async Task> GetLTokenBySTokenAsync(User user, CancellationToken token = default) + private class Timestamp { - Response? resp = await httpClient - .SetUser(user, CookieType.SToken) - .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.PROD, true) - .TryCatchGetFromJsonAsync>(ApiEndpoints.AccountGetLTokenBySToken, options, logger, token) - .ConfigureAwait(false); - - return Response.Response.DefaultIfNull(resp); + [JsonPropertyName("t")] + public long Time { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientFactory.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientFactory.cs new file mode 100644 index 00000000..533efdad --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.DependencyInjection.Abstraction; + +namespace Snap.Hutao.Web.Hoyolab.Passport; + +[ConstructorGenerated(CallBaseConstructor = true)] +[Injection(InjectAs.Transient, typeof(IOverseaSupportFactory))] +internal sealed partial class PassportClientFactory : OverseaSupportFactory +{ +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs index 61f451b3..a3576826 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs @@ -14,19 +14,12 @@ namespace Snap.Hutao.Web.Hoyolab.Passport; /// [ConstructorGenerated] [HttpClient(HttpClientConfiguration.XRpc3)] -[Injection(InjectAs.Transient, typeof(IPassportClient))] internal sealed partial class PassportClientOversea : IPassportClient { private readonly ILogger logger; private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; - /// - public bool IsOversea - { - get => true; - } - /// /// 异步获取 CookieToken /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs index a2d2a29d..9b018fa2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs @@ -19,7 +19,6 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; [ConstructorGenerated] [HttpClient(HttpClientConfiguration.XRpc)] [PrimaryHttpMessageHandler(UseCookies = false)] -[Injection(InjectAs.Transient)] internal sealed partial class GameRecordClient : IGameRecordClient { private readonly IServiceProvider serviceProvider; @@ -27,12 +26,6 @@ internal sealed partial class GameRecordClient : IGameRecordClient private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; - /// - public bool IsOversea - { - get => false; - } - /// /// 异步获取实时便笺 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs index 12a043c7..08cf4108 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs @@ -18,19 +18,12 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; [ConstructorGenerated] [HttpClient(HttpClientConfiguration.XRpc3)] [PrimaryHttpMessageHandler(UseCookies = false)] -[Injection(InjectAs.Transient)] internal sealed partial class GameRecordClientOversea : IGameRecordClient { private readonly ILogger logger; private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; - /// - public bool IsOversea - { - get => true; - } - /// /// 异步获取实时便笺 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/IGameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/IGameRecordClient.cs index 5a8d80ba..d67b42e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/IGameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/IGameRecordClient.cs @@ -11,7 +11,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; /// /// 游戏记录提供器 /// -internal interface IGameRecordClient : IOverseaSupport +internal interface IGameRecordClient { /// /// 获取玩家角色详细信息 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/EndIds.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/EndIds.cs index 4a380daa..95180270 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/EndIds.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/EndIds.cs @@ -76,37 +76,6 @@ internal sealed class EndIds } } - /// - /// 异步创建一个新的 End Id集合 - /// - /// 数据库上下文 - /// 存档 - /// 取消令牌 - /// 新的 End Id集合 - public static async Task CreateAsync(AppDbContext appDbContext, GachaArchive? archive, CancellationToken token) - { - EndIds endIds = new(); - foreach (GachaConfigType type in Service.GachaLog.GachaLog.QueryTypes) - { - if (archive != null) - { - Snap.Hutao.Model.Entity.GachaItem? item = await appDbContext.GachaItems - .Where(i => i.ArchiveId == archive.InnerId) - .Where(i => i.QueryType == type) - .OrderBy(i => i.Id) - .FirstOrDefaultAsync(token) - .ConfigureAwait(false); - - if (item != null) - { - endIds[type] = item.Id; - } - } - } - - return endIds; - } - /// /// 获取枚举器 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/GachaEntry.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/GachaEntry.cs new file mode 100644 index 00000000..ab18eb88 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/GachaEntry.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hutao.GachaLog; + +internal sealed class GachaEntry +{ + /// + /// Uid + /// + public string Uid { get; set; } = default!; + + /// + /// 物品个数 + /// + public int ItemCount { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaGachaLogClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaGachaLogClient.cs index 67add4c9..f9180d08 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaGachaLogClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaGachaLogClient.cs @@ -69,6 +69,7 @@ internal sealed class HomaGachaLogClient /// /// 取消令牌 /// Uid 列表 + [Obsolete("Use GetGachaEntriesAsync instead")] public async Task>> GetUidsAsync(CancellationToken token = default) { Response>? resp = await httpClient @@ -78,6 +79,20 @@ internal sealed class HomaGachaLogClient return Response.Response.DefaultIfNull(resp); } + /// + /// 异步获取 Uid 列表 + /// + /// 取消令牌 + /// Uid 列表 + public async ValueTask>> GetGachaEntriesAsync(CancellationToken token = default) + { + Response>? resp = await httpClient + .TryCatchGetFromJsonAsync>>(HutaoEndpoints.GachaLogUids, options, logger, token) + .ConfigureAwait(false); + + return Response.Response.DefaultIfNull(resp); + } + /// /// 异步获取末尾 Id /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaSpiralAbyssClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaSpiralAbyssClient.cs index c1ad335f..032eef79 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaSpiralAbyssClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaSpiralAbyssClient.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Service.Hutao; using Snap.Hutao.ViewModel.User; @@ -173,7 +174,9 @@ internal sealed partial class HomaSpiralAbyssClient /// 玩家记录 public async Task GetPlayerRecordAsync(UserAndUid userAndUid, CancellationToken token = default) { - IGameRecordClient gameRecordClient = serviceProvider.PickRequiredService(userAndUid.User.IsOversea); + IGameRecordClient gameRecordClient = serviceProvider + .GetRequiredService>() + .Create(userAndUid.User.IsOversea); Response playerInfoResponse = await gameRecordClient .GetPlayerInfoAsync(userAndUid, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs index 2c16f0bf..a02cefae 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs @@ -45,6 +45,11 @@ internal static class HutaoEndpoints /// public const string GachaLogUids = $"{HomaSnapGenshinApi}/GachaLog/Uids"; + /// + /// 获取Uid列表 + /// + public const string GachaLogEntries = $"{HomaSnapGenshinApi}/GachaLog/Entries"; + /// /// 删除祈愿记录 ///