mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Generate Enum Localization
This commit is contained in:
@@ -33,7 +33,7 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
|
||||
{
|
||||
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> injectionClasses =
|
||||
context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
|
||||
.Where(GeneratorSyntaxContext2.NotNull)!
|
||||
.Where(GeneratorSyntaxContext2.NotNull)
|
||||
.Collect();
|
||||
|
||||
context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddHttpClientsImplementation);
|
||||
|
||||
@@ -26,9 +26,9 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> injectionClasses =
|
||||
context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
|
||||
.Where(GeneratorSyntaxContext2.NotNull)!
|
||||
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> injectionClasses = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass)
|
||||
.Where(GeneratorSyntaxContext2.NotNull)
|
||||
.Collect();
|
||||
|
||||
context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddInjectionsImplementation);
|
||||
@@ -96,13 +96,13 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
|
||||
switch (injectAsName)
|
||||
{
|
||||
case InjectAsSingletonName:
|
||||
lineBuilder.Append(@" services.AddSingleton<");
|
||||
lineBuilder.Append(" services.AddSingleton<");
|
||||
break;
|
||||
case InjectAsTransientName:
|
||||
lineBuilder.Append(@" services.AddTransient<");
|
||||
lineBuilder.Append(" services.AddTransient<");
|
||||
break;
|
||||
case InjectAsScopedName:
|
||||
lineBuilder.Append(@" services.AddScoped<");
|
||||
lineBuilder.Append(" services.AddScoped<");
|
||||
break;
|
||||
default:
|
||||
production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, null, injectAsName));
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Snap.Hutao.SourceGeneration.Primitive;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.Enum;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
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<GeneratorSyntaxContext2> localizationEnums = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(FilterAttributedEnums, LocalizationEnum)
|
||||
.Where(GeneratorSyntaxContext2.NotNull);
|
||||
|
||||
context.RegisterSourceOutput(localizationEnums, GenerateGetLocalizedDescriptionImplementation);
|
||||
}
|
||||
|
||||
private static bool FilterAttributedEnums(SyntaxNode node, CancellationToken token)
|
||||
{
|
||||
return node is EnumDeclarationSyntax enumDeclarationSyntax && enumDeclarationSyntax.AttributeLists.Count > 0;
|
||||
}
|
||||
|
||||
private static GeneratorSyntaxContext2 LocalizationEnum(GeneratorSyntaxContext context, CancellationToken token)
|
||||
{
|
||||
if (context.SemanticModel.GetDeclaredSymbol(context.Node, token) is INamedTypeSymbol enumSymbol)
|
||||
{
|
||||
ImmutableArray<AttributeData> attributes = enumSymbol.GetAttributes();
|
||||
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
|
||||
{
|
||||
return new(context, enumSymbol, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void GenerateGetLocalizedDescriptionImplementation(SourceProductionContext context, GeneratorSyntaxContext2 context2)
|
||||
{
|
||||
StringBuilder sourceBuilder = new StringBuilder().Append($$"""
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Resource.Localization;
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(LocalizedEnumGenerator)}}","1.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
internal static class {{context2.Symbol.Name}}Extension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取本地化的描述
|
||||
/// </summary>
|
||||
/// <param name="value">枚举值</param>
|
||||
/// <returns>本地化的描述</returns>
|
||||
public static string GetLocalizedDescription(this {{context2.Symbol}} value)
|
||||
{
|
||||
string key = value switch
|
||||
{
|
||||
|
||||
""");
|
||||
|
||||
FillUpWithSwitchBranches(sourceBuilder, context2);
|
||||
|
||||
sourceBuilder.Append($$"""
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return Enum.GetName(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return SH.ResourceManager.GetString(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化的描述
|
||||
/// </summary>
|
||||
/// <param name="value">枚举值</param>
|
||||
/// <returns>本地化的描述</returns>
|
||||
[return:MaybeNull]
|
||||
public static string GetLocalizedDescriptionOrDefault(this {{context2.Symbol}} value)
|
||||
{
|
||||
string key = value switch
|
||||
{
|
||||
|
||||
""");
|
||||
|
||||
FillUpWithSwitchBranches(sourceBuilder, context2);
|
||||
|
||||
sourceBuilder.Append($$"""
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
return SH.ResourceManager.GetString(key);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
context.AddSource($"{context2.Symbol.Name}Extension.g.cs", sourceBuilder.ToString());
|
||||
}
|
||||
|
||||
private static void FillUpWithSwitchBranches(StringBuilder sourceBuilder, GeneratorSyntaxContext2 context)
|
||||
{
|
||||
EnumDeclarationSyntax enumSyntax = (EnumDeclarationSyntax)context.Context.Node;
|
||||
|
||||
foreach(EnumMemberDeclarationSyntax enumMemberSyntax in enumSyntax.Members)
|
||||
{
|
||||
if (context.Context.SemanticModel.GetDeclaredSymbol(enumMemberSyntax) is IFieldSymbol fieldSymbol)
|
||||
{
|
||||
AttributeData? localizationKeyInfo = fieldSymbol.GetAttributes()
|
||||
.SingleOrDefault(data => data.AttributeClass!.ToDisplayString() == LocalizationKeyName);
|
||||
if (localizationKeyInfo != null)
|
||||
{
|
||||
sourceBuilder.Append(" ").Append(fieldSymbol).Append(" => \"").Append(localizationKeyInfo.ConstructorArguments[0].Value).AppendLine("\",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceBuilder.Append($"""
|
||||
|
||||
""");
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ public static class JsonParser
|
||||
|
||||
try
|
||||
{
|
||||
return Enum.Parse(type, json, false);
|
||||
return System.Enum.Parse(type, json, false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Test;
|
||||
|
||||
@@ -63,9 +64,27 @@ public class CSharpLanguageFeatureTest
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetTwiceOnPropertyResultsSame()
|
||||
public void GetTwiceOnPropertyResultsNotSame()
|
||||
{
|
||||
Assert.AreEqual(UUID, UUID);
|
||||
Assert.AreNotEqual(UUID, UUID);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()
|
||||
{
|
||||
List<string> strings = new()
|
||||
{
|
||||
"a", "b", "c"
|
||||
};
|
||||
|
||||
int count = 0;
|
||||
foreach (ReadOnlySpan<char> chars in strings)
|
||||
{
|
||||
Assert.IsTrue(chars.Length == 1);
|
||||
++count;
|
||||
}
|
||||
|
||||
Assert.AreEqual(3, count);
|
||||
}
|
||||
|
||||
public static Guid UUID { get => Guid.NewGuid(); }
|
||||
|
||||
@@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
|
||||
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:ParameterDescriptor x:Key="DescParamDescriptor"/>
|
||||
<shmmc:DescriptionsParametersDescriptor x:Key="DescParamDescriptor"/>
|
||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||
<shmmc:EmotionIconConverter x:Key="EmotionIconConverter"/>
|
||||
<shmmc:EquipIconConverter x:Key="EquipIconConverter"/>
|
||||
@@ -98,7 +98,7 @@
|
||||
<shmmc:GachaEquipIconConverter x:Key="GachaEquipIconConverter"/>
|
||||
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
|
||||
<shmmc:MonsterIconConverter x:Key="MonsterIconConverter"/>
|
||||
<shmmc:PropertyDescriptor x:Key="PropertyDescriptor"/>
|
||||
<shmmc:PropertiesParametersDescriptor x:Key="PropertyDescriptor"/>
|
||||
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
|
||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
|
||||
@@ -36,6 +36,7 @@ public sealed partial class App : Application
|
||||
|
||||
logger = serviceProvider.GetRequiredService<ILogger<App>>();
|
||||
serviceProvider.GetRequiredService<ExceptionRecorder>().Record(this);
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
[HighQuality]
|
||||
internal sealed class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetWidth), 320D);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetHeight), 1024D);
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
|
||||
|
||||
/// <summary>
|
||||
/// 目标宽度
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
|
||||
@@ -15,30 +15,36 @@ internal static class ContentDialogExtension
|
||||
/// 阻止用户交互
|
||||
/// </summary>
|
||||
/// <param name="contentDialog">对话框</param>
|
||||
/// <param name="taskContext">任务上下文</param>
|
||||
/// <returns>用于恢复用户交互</returns>
|
||||
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog)
|
||||
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog, ITaskContext taskContext)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
contentDialog.ShowAsync().AsTask().SafeForget();
|
||||
|
||||
// E_ASYNC_OPERATION_NOT_STARTED 0x80000019
|
||||
// Only a single ContentDialog can be open at any time.
|
||||
return new ContentDialogHider(contentDialog);
|
||||
return new ContentDialogHider(contentDialog, taskContext);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1201")]
|
||||
[SuppressMessage("", "SA1400")]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
file readonly struct ContentDialogHider : IDisposable
|
||||
{
|
||||
private readonly ContentDialog contentDialog;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public ContentDialogHider(ContentDialog contentDialog, ITaskContext taskContext)
|
||||
{
|
||||
this.contentDialog = contentDialog;
|
||||
this.taskContext = taskContext;
|
||||
}
|
||||
|
||||
private class ContentDialogHider : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
private readonly ContentDialog contentDialog;
|
||||
|
||||
public ContentDialogHider(ContentDialog contentDialog)
|
||||
{
|
||||
this.contentDialog = contentDialog;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Hide() must be called on main thread.
|
||||
ThreadHelper.InvokeOnMainThread(contentDialog.Hide);
|
||||
}
|
||||
// Hide() must be called on main thread.
|
||||
taskContext.InvokeOnMainThread(contentDialog.Hide);
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,7 @@ internal sealed class CachedImage : ImageEx
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
// We can only use Ioc to retrive IImageCache,
|
||||
// no IServiceProvider is available.
|
||||
// We can only use Ioc to retrieve IImageCache, no IServiceProvider is available.
|
||||
IImageCache imageCache = Ioc.Default.GetRequiredService<IImageCache>();
|
||||
|
||||
try
|
||||
@@ -47,7 +46,7 @@ internal sealed class CachedImage : ImageEx
|
||||
catch (COMException)
|
||||
{
|
||||
// The image is corrupted, remove it.
|
||||
imageCache.Remove(imageUri.Enumerate());
|
||||
imageCache.Remove(imageUri);
|
||||
return null;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -35,7 +35,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
/// </summary>
|
||||
public CompositionImage()
|
||||
{
|
||||
serviceProvider = Ioc.Default.GetRequiredService<IServiceProvider>();
|
||||
serviceProvider = Ioc.Default;
|
||||
|
||||
AllowFocusOnInteraction = false;
|
||||
IsDoubleTapEnabled = false;
|
||||
@@ -123,7 +123,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
|
||||
private static void OnApplyImageFailed(IServiceProvider serviceProvider, Uri? uri, Exception exception)
|
||||
{
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
|
||||
if (exception is HttpRequestException httpRequestException)
|
||||
{
|
||||
@@ -157,11 +157,11 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
imageCache.Remove(uri.Enumerate());
|
||||
imageCache.Remove(uri);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
imageCache.Remove(uri.Enumerate());
|
||||
imageCache.Remove(uri);
|
||||
}
|
||||
|
||||
if (imageSurface != null)
|
||||
@@ -183,7 +183,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
|
||||
if (EnableLazyLoading)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(1d, 0d).StartAsync(this, token).ConfigureAwait(true);
|
||||
await AnimationBuilder.Create().Opacity(from: 0D, to: 1D).StartAsync(this, token).ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -200,7 +200,7 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
|
||||
if (EnableLazyLoading)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(0d, 1d).StartAsync(this, token).ConfigureAwait(true);
|
||||
await AnimationBuilder.Create().Opacity(from: 1D, to: 0D).StartAsync(this, token).ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,17 +9,17 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <param name="Offset">便宜</param>
|
||||
/// <param name="Color">颜色</param>
|
||||
[HighQuality]
|
||||
internal struct GradientStop
|
||||
internal readonly struct GradientStop
|
||||
{
|
||||
/// <summary>
|
||||
/// 便宜
|
||||
/// 偏移
|
||||
/// </summary>
|
||||
public float Offset;
|
||||
public readonly float Offset;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色
|
||||
/// </summary>
|
||||
public Windows.UI.Color Color;
|
||||
public readonly Windows.UI.Color Color;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的渐变锚点
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Win32;
|
||||
@@ -27,15 +28,15 @@ internal static class SoftwareBitmapExtension
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out byte* data, out uint length);
|
||||
|
||||
for (int i = 0; i < length; i += 4)
|
||||
Span<Bgra8> bytes = new(data, unchecked((int)length / (sizeof(Bgra8) / sizeof(uint))));
|
||||
foreach (ref Bgra8 pixel in bytes)
|
||||
{
|
||||
Bgra8* pixel = (Bgra8*)(data + i);
|
||||
byte baseAlpha = pixel->A;
|
||||
pixel->B = (byte)(((pixel->B * baseAlpha) + (tint.B * (0xFF - baseAlpha))) / 0xFF);
|
||||
pixel->G = (byte)(((pixel->G * baseAlpha) + (tint.G * (0xFF - baseAlpha))) / 0xFF);
|
||||
pixel->R = (byte)(((pixel->R * baseAlpha) + (tint.R * (0xFF - baseAlpha))) / 0xFF);
|
||||
pixel->A = 0xFF;
|
||||
byte baseAlpha = pixel.A;
|
||||
int opposite = 0xFF - baseAlpha;
|
||||
pixel.B = (byte)(((pixel.B * baseAlpha) + (tint.B * opposite)) / 0xFF);
|
||||
pixel.G = (byte)(((pixel.G * baseAlpha) + (tint.G * opposite)) / 0xFF);
|
||||
pixel.R = (byte)(((pixel.R * baseAlpha) + (tint.R * opposite)) / 0xFF);
|
||||
pixel.A = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ internal sealed partial class PanelSelector : SplitButton
|
||||
set => SetValue(CurrentProperty, value);
|
||||
}
|
||||
|
||||
private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
OnCurrentChanged((PanelSelector)obj, (string)args.NewValue);
|
||||
}
|
||||
|
||||
private static void OnCurrentChanged(PanelSelector sender, string current)
|
||||
{
|
||||
MenuFlyout menuFlyout = (MenuFlyout)sender.RootSplitButton.Flyout;
|
||||
@@ -43,21 +48,16 @@ internal sealed partial class PanelSelector : SplitButton
|
||||
sender.IconPresenter.Glyph = ((FontIcon)targetItem.Icon).Glyph;
|
||||
}
|
||||
|
||||
private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
OnCurrentChanged((PanelSelector)obj, (string)args.NewValue);
|
||||
}
|
||||
|
||||
private void OnRootControlLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// because the GroupName shares in global
|
||||
// we have to impl a control scoped GroupName.
|
||||
// we have to implement a control scoped GroupName.
|
||||
PanelSelector selector = (PanelSelector)sender;
|
||||
MenuFlyout menuFlyout = (MenuFlyout)selector.RootSplitButton.Flyout;
|
||||
int hash = GetHashCode();
|
||||
foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast<RadioMenuFlyoutItem>())
|
||||
{
|
||||
item.GroupName = $"PanelSelector{hash}Group";
|
||||
item.GroupName = $"{nameof(PanelSelector)}GroupOf@{hash}";
|
||||
}
|
||||
|
||||
OnCurrentChanged(selector, Current);
|
||||
|
||||
@@ -153,6 +153,7 @@ internal sealed class DescriptionTextBlock : ContentControl
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
{
|
||||
// Simply re-apply texts
|
||||
ApplyDescription((TextBlock)Content, Description);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
|
||||
/// <summary>
|
||||
@@ -21,7 +23,13 @@ internal interface IImageCache : ICastableService
|
||||
/// Removed items based on uri list passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
||||
void Remove(IEnumerable<Uri> uriForCachedItems);
|
||||
void Remove(in ReadOnlySpan<Uri> uriForCachedItems);
|
||||
|
||||
/// <summary>
|
||||
/// Removed item based on uri passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItem">uri</param>
|
||||
void Remove(Uri uriForCachedItem);
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid cached files
|
||||
|
||||
@@ -36,6 +36,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
@@ -45,12 +46,15 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCache"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="httpClientFactory">http客户端工厂</param>
|
||||
public ImageCache(ILogger<ImageCache> logger, IHttpClientFactory httpClientFactory)
|
||||
public ImageCache(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.logger = logger;
|
||||
httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
logger = serviceProvider.GetRequiredService<ILogger<ImageCache>>();
|
||||
httpClient = serviceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(nameof(ImageCache));
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -73,9 +77,15 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(IEnumerable<Uri> uriForCachedItems)
|
||||
public void Remove(Uri uriForCachedItem)
|
||||
{
|
||||
if (uriForCachedItems == null || !uriForCachedItems.Any())
|
||||
Remove(new ReadOnlySpan<Uri>(uriForCachedItem));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
|
||||
{
|
||||
if (uriForCachedItems == null || uriForCachedItems.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -131,24 +141,10 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
/// <inheritdoc/>
|
||||
public string GetFilePathFromCategoryAndFileName(string category, string fileName)
|
||||
{
|
||||
Uri dummyUri = new(Web.HutaoEndpoints.StaticFile(category, fileName));
|
||||
Uri dummyUri = Web.HutaoEndpoints.StaticFile(category, fileName).ToUri();
|
||||
return Path.Combine(GetCacheFolder(), GetCacheFileName(dummyUri));
|
||||
}
|
||||
|
||||
private static void RemoveInternal(IEnumerable<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
@@ -164,11 +160,25 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
return treatNullFileAsInvalid;
|
||||
}
|
||||
|
||||
// Get extended properties.
|
||||
FileInfo fileInfo = new(file);
|
||||
return fileInfo.Length == 0;
|
||||
}
|
||||
|
||||
private void RemoveInternal(IEnumerable<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Remove Cache Image Failed:{file}", filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, string baseFile)
|
||||
{
|
||||
logger.LogInformation("Begin downloading for {uri}", uri);
|
||||
@@ -218,7 +228,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
{
|
||||
if (cacheFolder == null)
|
||||
{
|
||||
baseFolder ??= ApplicationData.Current.LocalCacheFolder.Path;
|
||||
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache;
|
||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||
cacheFolder = info.FullName;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
[HighQuality]
|
||||
[Obsolete("Use ScopedDbCurrent instead")]
|
||||
internal sealed class DbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
|
||||
@@ -24,10 +24,7 @@ internal static class DbSetExtension
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Add(entity);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = dbContext.SaveChanges();
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,14 +34,11 @@ internal static class DbSetExtension
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static async ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
public static ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Add(entity);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -58,10 +52,7 @@ internal static class DbSetExtension
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.AddRange(entities);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = dbSet.Context().SaveChanges();
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,14 +62,11 @@ internal static class DbSetExtension
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entities">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static async ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||
public static ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.AddRange(entities);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,10 +80,7 @@ internal static class DbSetExtension
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Remove(entity);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = dbContext.SaveChanges();
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,14 +90,11 @@ internal static class DbSetExtension
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static async ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
public static ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Remove(entity);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,10 +108,7 @@ internal static class DbSetExtension
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Update(entity);
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = dbContext.SaveChanges();
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -139,11 +118,31 @@ internal static class DbSetExtension
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static async ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
public static ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Update(entity);
|
||||
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int SaveChangesAndClearChangeTracker<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = dbContext.SaveChanges();
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static async ValueTask<int> SaveChangesAndClearChangeTrackerAsync<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// 可枚举扩展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class EnumerableExtension
|
||||
internal static class SelectableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取选中的值或默认值
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 可转换类型服务
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 有名称的对象
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 海外服/Hoyolab 可区分
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
|
||||
/// <summary>
|
||||
/// 添加 <see cref="HttpClient"/>
|
||||
/// 此方法将会自动生成
|
||||
/// </summary>
|
||||
/// <param name="services">集合</param>
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
|
||||
@@ -12,6 +12,7 @@ internal static partial class ServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 向容器注册服务
|
||||
/// 此方法将会自动生成
|
||||
/// </summary>
|
||||
/// <param name="services">容器</param>
|
||||
/// <returns>可继续操作的服务集合</returns>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// </summary>
|
||||
internal sealed class ExceptionFormat
|
||||
{
|
||||
private const string SectionSeparator = "----------------------------------------";
|
||||
private static readonly string SectionSeparator = new('-', 40);
|
||||
|
||||
/// <summary>
|
||||
/// 格式化异常
|
||||
|
||||
@@ -15,7 +15,7 @@ internal sealed class UserdataCorruptedException : Exception
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public UserdataCorruptedException(string message, Exception innerException)
|
||||
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, message), innerException)
|
||||
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, $"{message}\n{innerException.Message}"), innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.IO;
|
||||
@@ -17,6 +18,9 @@ namespace Snap.Hutao.Core;
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
{
|
||||
private readonly bool isWebView2Supported;
|
||||
private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃选项
|
||||
/// </summary>
|
||||
@@ -31,6 +35,7 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
UserAgent = $"Snap Hutao/{Version}";
|
||||
|
||||
DeviceId = GetUniqueUserId();
|
||||
DetectWebView2Environment(ref webView2Version, ref isWebView2Supported);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,6 +73,16 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
/// </summary>
|
||||
public string DeviceId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// WebView2 版本
|
||||
/// </summary>
|
||||
public string WebView2Version { get => webView2Version; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持 WebView2
|
||||
/// </summary>
|
||||
public bool IsWebView2Supported { get => isWebView2Supported; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public HutaoOptions Value { get => this; }
|
||||
|
||||
@@ -99,4 +114,18 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
|
||||
return Convert.ToMd5HexString($"{userName}{machineGuid}");
|
||||
}
|
||||
|
||||
private static void DetectWebView2Environment(ref string webView2Version, ref bool isWebView2Supported)
|
||||
{
|
||||
try
|
||||
{
|
||||
webView2Version = CoreWebView2Environment.GetAvailableBrowserVersionString();
|
||||
isWebView2Supported = true;
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
ILogger<WebView2Helper> logger = Ioc.Default.GetRequiredService<ILogger<WebView2Helper>>();
|
||||
logger.LogError(ex, "WebView2 Runtime not installed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,21 @@ internal static class Clipboard
|
||||
/// 从剪贴板文本中反序列化
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="options">Json序列化选项</param>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <returns>实例</returns>
|
||||
public static async Task<T?> DeserializeTextAsync<T>(JsonSerializerOptions options)
|
||||
public static async Task<T?> DeserializeTextAsync<T>(IServiceProvider serviceProvider)
|
||||
where T : class
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
|
||||
|
||||
if (view.Contains(StandardDataFormats.Text))
|
||||
{
|
||||
string json = await view.GetTextAsync();
|
||||
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
return JsonSerializer.Deserialize<T>(json, serviceProvider.GetRequiredService<JsonSerializerOptions>());
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Core.Json.Annotation;
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
internal class JsonEnumAttribute : Attribute
|
||||
{
|
||||
private static readonly Type ConfigurableEnumConverterType = typeof(ConfigurableEnumConverter<>);
|
||||
private static readonly Type UnsafeEnumConverterType = typeof(UnsafeEnumConverter<>);
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的Json枚举声明
|
||||
@@ -52,7 +52,7 @@ internal class JsonEnumAttribute : Attribute
|
||||
/// <returns>Json转换器</returns>
|
||||
internal JsonConverter CreateConverter(JsonPropertyInfo info)
|
||||
{
|
||||
Type converterType = ConfigurableEnumConverterType.MakeGenericType(info.PropertyType);
|
||||
Type converterType = UnsafeEnumConverterType.MakeGenericType(info.PropertyType);
|
||||
return (JsonConverter)Activator.CreateInstance(converterType, ReadAs, WriteAs)!;
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ namespace Snap.Hutao.Core.Json.Annotation;
|
||||
internal enum JsonSerializeType
|
||||
{
|
||||
/// <summary>
|
||||
/// Int32
|
||||
/// 数字
|
||||
/// </summary>
|
||||
Int32,
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// 字符串包裹的数字
|
||||
/// </summary>
|
||||
Int32AsString,
|
||||
NumberString,
|
||||
|
||||
/// <summary>
|
||||
/// 名称字符串
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExpressionService;
|
||||
using Snap.Hutao.Core.Json.Annotation;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举转换器
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
[HighQuality]
|
||||
internal sealed class ConfigurableEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
private readonly JsonSerializeType readAs;
|
||||
private readonly JsonSerializeType writeAs;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的枚举转换器
|
||||
/// </summary>
|
||||
/// <param name="readAs">读取</param>
|
||||
/// <param name="writeAs">写入</param>
|
||||
public ConfigurableEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
|
||||
{
|
||||
this.readAs = readAs;
|
||||
this.writeAs = writeAs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (readAs == JsonSerializeType.Int32)
|
||||
{
|
||||
return CastTo<TEnum>.From(reader.GetInt32());
|
||||
}
|
||||
|
||||
if (reader.GetString() is string str)
|
||||
{
|
||||
return Enum.Parse<TEnum>(str);
|
||||
}
|
||||
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
switch (writeAs)
|
||||
{
|
||||
case JsonSerializeType.Int32:
|
||||
writer.WriteNumberValue(CastTo<int>.From(value));
|
||||
break;
|
||||
case JsonSerializeType.Int32AsString:
|
||||
writer.WriteStringValue(value.ToString("D"));
|
||||
break;
|
||||
default:
|
||||
writer.WriteStringValue(value.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Json.Annotation;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举转换器
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
[HighQuality]
|
||||
internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
private readonly TypeCode enumTypeCode = Type.GetTypeCode(typeof(TEnum));
|
||||
|
||||
private readonly JsonSerializeType readAs;
|
||||
private readonly JsonSerializeType writeAs;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的枚举转换器
|
||||
/// </summary>
|
||||
/// <param name="readAs">读取</param>
|
||||
/// <param name="writeAs">写入</param>
|
||||
public UnsafeEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
|
||||
{
|
||||
this.readAs = readAs;
|
||||
this.writeAs = writeAs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConverTEnum, JsonSerializerOptions options)
|
||||
{
|
||||
if (readAs == JsonSerializeType.Number)
|
||||
{
|
||||
return GetEnum(ref reader, enumTypeCode);
|
||||
}
|
||||
|
||||
if (reader.GetString() is string str)
|
||||
{
|
||||
return Enum.Parse<TEnum>(str);
|
||||
}
|
||||
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
switch (writeAs)
|
||||
{
|
||||
case JsonSerializeType.Number:
|
||||
WriteEnumValue(writer, value, enumTypeCode);
|
||||
break;
|
||||
case JsonSerializeType.NumberString:
|
||||
writer.WriteStringValue(value.ToString("D"));
|
||||
break;
|
||||
default:
|
||||
writer.WriteStringValue(value.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static TEnum GetEnum(ref Utf8JsonReader reader, TypeCode typeCode)
|
||||
{
|
||||
switch (typeCode)
|
||||
{
|
||||
case TypeCode.Int32:
|
||||
if (reader.TryGetInt32(out int int32))
|
||||
{
|
||||
return Unsafe.As<int, TEnum>(ref int32);
|
||||
}
|
||||
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
if (reader.TryGetUInt32(out uint uint32))
|
||||
{
|
||||
return Unsafe.As<uint, TEnum>(ref uint32);
|
||||
}
|
||||
|
||||
break;
|
||||
case TypeCode.UInt64:
|
||||
if (reader.TryGetUInt64(out ulong uint64))
|
||||
{
|
||||
return Unsafe.As<ulong, TEnum>(ref uint64);
|
||||
}
|
||||
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
if (reader.TryGetInt64(out long int64))
|
||||
{
|
||||
return Unsafe.As<long, TEnum>(ref int64);
|
||||
}
|
||||
|
||||
break;
|
||||
case TypeCode.Byte:
|
||||
if (reader.TryGetByte(out byte byte8))
|
||||
{
|
||||
return Unsafe.As<byte, TEnum>(ref byte8);
|
||||
}
|
||||
|
||||
break;
|
||||
case TypeCode.Int16:
|
||||
if (reader.TryGetInt16(out short int16))
|
||||
{
|
||||
return Unsafe.As<short, TEnum>(ref int16);
|
||||
}
|
||||
|
||||
break;
|
||||
case TypeCode.UInt16:
|
||||
if (reader.TryGetUInt16(out ushort uint16))
|
||||
{
|
||||
return Unsafe.As<ushort, TEnum>(ref uint16);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
private static void WriteEnumValue(Utf8JsonWriter writer, TEnum value, TypeCode typeCode)
|
||||
{
|
||||
switch (typeCode)
|
||||
{
|
||||
case TypeCode.Int32:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, int>(ref value));
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, uint>(ref value));
|
||||
break;
|
||||
case TypeCode.UInt64:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, ulong>(ref value));
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, long>(ref value));
|
||||
break;
|
||||
case TypeCode.Int16:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, short>(ref value));
|
||||
break;
|
||||
case TypeCode.UInt16:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, ushort>(ref value));
|
||||
break;
|
||||
case TypeCode.Byte:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, byte>(ref value));
|
||||
break;
|
||||
case TypeCode.SByte:
|
||||
writer.WriteNumberValue(Unsafe.As<TEnum, sbyte>(ref value));
|
||||
break;
|
||||
default:
|
||||
throw new JsonException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ internal static class Activation
|
||||
private const string CategoryDailyNote = "dailynote";
|
||||
private const string UrlActionImport = "/import";
|
||||
private const string UrlActionRefresh = "/refresh";
|
||||
|
||||
private static readonly WeakReference<MainWindow> MainWindowReference = new(default!);
|
||||
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +74,7 @@ internal static class Activation
|
||||
/// <returns>任务</returns>
|
||||
public static async ValueTask RestartAsElevatedAsync()
|
||||
{
|
||||
if (GetElevated())
|
||||
if (!GetElevated())
|
||||
{
|
||||
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||
Process.GetCurrentProcess().Kill();
|
||||
@@ -182,12 +184,16 @@ internal static class Activation
|
||||
|
||||
private static async Task WaitMainWindowAsync()
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
IServiceProvider serviceProvider = Ioc.Default;
|
||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
serviceProvider.GetRequiredService<MainWindow>().Activate();
|
||||
MainWindowReference.SetTarget(serviceProvider.GetRequiredService<MainWindow>());
|
||||
|
||||
await serviceProvider.GetRequiredService<IInfoBarService>().WaitInitializationAsync().ConfigureAwait(false);
|
||||
await serviceProvider
|
||||
.GetRequiredService<IInfoBarService>()
|
||||
.WaitInitializationAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IMetadataService>()
|
||||
@@ -279,16 +285,19 @@ internal static class Activation
|
||||
|
||||
private static async Task HandleLaunchGameActionAsync(string? uid = null)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IMemoryCache>().Set(ViewModel.Game.LaunchGameViewModel.DesiredUid, uid);
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
IServiceProvider serviceProvider = Ioc.Default;
|
||||
IMemoryCache memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();
|
||||
memoryCache.Set(ViewModel.Game.LaunchGameViewModel.DesiredUid, uid);
|
||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
if (!MainWindow.IsPresent)
|
||||
if (!MainWindowReference.TryGetTarget(out _))
|
||||
{
|
||||
_ = Ioc.Default.GetRequiredService<LaunchGameWindow>();
|
||||
_ = serviceProvider.GetRequiredService<LaunchGameWindow>();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Ioc.Default
|
||||
await serviceProvider
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -39,6 +39,8 @@ internal static class SettingKeys
|
||||
/// </summary>
|
||||
public const string PassportPassword = "PassportPassword";
|
||||
|
||||
#region StaticResource
|
||||
|
||||
/// <summary>
|
||||
/// 静态资源合约
|
||||
/// 新增合约时 请注意
|
||||
@@ -66,4 +68,5 @@ internal static class SettingKeys
|
||||
/// 静态资源合约V5 刷新 AvatarIcon
|
||||
/// </summary>
|
||||
public const string StaticResourceV5Contract = "StaticResourceV5Contract";
|
||||
#endregion
|
||||
}
|
||||
@@ -29,19 +29,22 @@ internal static class SemaphoreSlimExtension
|
||||
|
||||
return new SemaphoreSlimReleaser(semaphoreSlim);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct SemaphoreSlimReleaser : IDisposable
|
||||
[SuppressMessage("", "SA1201")]
|
||||
[SuppressMessage("", "SA1400")]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
file readonly struct SemaphoreSlimReleaser : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim semaphoreSlim;
|
||||
|
||||
public SemaphoreSlimReleaser(SemaphoreSlim semaphoreSlim)
|
||||
{
|
||||
private readonly SemaphoreSlim semaphoreSlim;
|
||||
this.semaphoreSlim = semaphoreSlim;
|
||||
}
|
||||
|
||||
public SemaphoreSlimReleaser(SemaphoreSlim semaphoreSlim)
|
||||
{
|
||||
this.semaphoreSlim = semaphoreSlim;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "VSTHRD003")]
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
internal static class TaskExtensions
|
||||
internal static class TaskExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 安全的触发任务
|
||||
@@ -21,6 +21,10 @@ internal static class TaskExtensions
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
#if DEBUG
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -12,6 +12,7 @@ namespace Snap.Hutao.Core;
|
||||
/// 必须为抽象类才能使用泛型日志器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoOptions instead")]
|
||||
internal abstract class WebView2Helper
|
||||
{
|
||||
private static bool hasEverDetected;
|
||||
|
||||
@@ -25,20 +25,19 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessage>
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
where TWindow : Window, IWindowOptionsSource
|
||||
{
|
||||
private readonly WindowOptions<TWindow> options;
|
||||
|
||||
private readonly TWindow window;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly WindowSubclass<TWindow> subclass;
|
||||
|
||||
private ExtendedWindow(TWindow window, FrameworkElement titleBar, IServiceProvider serviceProvider)
|
||||
private ExtendedWindow(TWindow window, IServiceProvider serviceProvider)
|
||||
{
|
||||
options = new(window, titleBar);
|
||||
subclass = new(options);
|
||||
|
||||
this.window = window;
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
subclass = new(window.WindowOptions);
|
||||
|
||||
InitializeWindow();
|
||||
}
|
||||
|
||||
@@ -50,7 +49,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
/// <returns>实例</returns>
|
||||
public static ExtendedWindow<TWindow> Initialize(TWindow window, IServiceProvider serviceProvider)
|
||||
{
|
||||
return new(window, window.TitleBar, serviceProvider);
|
||||
return new(window, serviceProvider);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -63,16 +62,17 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
|
||||
options.AppWindow.Title = string.Format(SH.AppNameAndVersion, hutaoOptions.Version);
|
||||
options.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
|
||||
WindowOptions options = window.WindowOptions;
|
||||
window.AppWindow.Title = string.Format(SH.AppNameAndVersion, hutaoOptions.Version);
|
||||
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
|
||||
ExtendsContentIntoTitleBar();
|
||||
|
||||
Persistence.RecoverOrInit(options);
|
||||
Persistence.RecoverOrInit(window);
|
||||
UpdateImmersiveDarkMode(options.TitleBar, default!);
|
||||
|
||||
// appWindow.Show(true);
|
||||
// appWindow.Show can't bring window to top.
|
||||
// options.Window.Activate();
|
||||
window.Activate();
|
||||
Persistence.BringToForeground(options.Hwnd);
|
||||
|
||||
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
||||
@@ -83,7 +83,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
|
||||
serviceProvider.GetRequiredService<IMessenger>().Register(this);
|
||||
|
||||
options.Window.Closed += OnWindowClosed;
|
||||
window.Closed += OnWindowClosed;
|
||||
options.TitleBar.ActualThemeChanged += UpdateImmersiveDarkMode;
|
||||
}
|
||||
|
||||
@@ -97,9 +97,9 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
|
||||
private void OnWindowClosed(object sender, WindowEventArgs args)
|
||||
{
|
||||
if (options.Window.PersistSize)
|
||||
if (window.WindowOptions.PersistSize)
|
||||
{
|
||||
Persistence.Save(options);
|
||||
Persistence.Save(window);
|
||||
}
|
||||
|
||||
subclass?.Dispose();
|
||||
@@ -107,15 +107,16 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
|
||||
private void ExtendsContentIntoTitleBar()
|
||||
{
|
||||
WindowOptions options = window.WindowOptions;
|
||||
if (options.UseLegacyDragBarImplementation)
|
||||
{
|
||||
// use normal Window method to extend.
|
||||
options.Window.ExtendsContentIntoTitleBar = true;
|
||||
options.Window.SetTitleBar(options.TitleBar);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetTitleBar(options.TitleBar);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppWindowTitleBar appTitleBar = options.AppWindow.TitleBar;
|
||||
AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar;
|
||||
appTitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu;
|
||||
appTitleBar.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
@@ -128,7 +129,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
|
||||
private void UpdateSystemBackdrop(BackdropType backdropType)
|
||||
{
|
||||
options.Window.SystemBackdrop = backdropType switch
|
||||
window.SystemBackdrop = backdropType switch
|
||||
{
|
||||
BackdropType.MicaAlt => new MicaBackdrop() { Kind = MicaKind.BaseAlt },
|
||||
BackdropType.Mica => new MicaBackdrop() { Kind = MicaKind.Base },
|
||||
@@ -139,7 +140,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
|
||||
private void UpdateTitleButtonColor()
|
||||
{
|
||||
AppWindowTitleBar appTitleBar = options.AppWindow.TitleBar;
|
||||
AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar;
|
||||
|
||||
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
@@ -166,12 +167,12 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
private unsafe void UpdateImmersiveDarkMode(FrameworkElement titleBar, object discard)
|
||||
{
|
||||
BOOL isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(titleBar.ActualTheme);
|
||||
DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &isDarkMode, unchecked((uint)sizeof(BOOL)));
|
||||
DwmSetWindowAttribute(window.WindowOptions.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &isDarkMode, unchecked((uint)sizeof(BOOL)));
|
||||
}
|
||||
|
||||
private void UpdateDragRectangles(bool isFlyoutOpened = false)
|
||||
{
|
||||
AppWindowTitleBar appTitleBar = options.AppWindow.TitleBar;
|
||||
AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar;
|
||||
|
||||
if (isFlyoutOpened)
|
||||
{
|
||||
@@ -180,6 +181,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowOptions options = window.WindowOptions;
|
||||
double scale = Persistence.GetScaleForWindowHandle(options.Hwnd);
|
||||
|
||||
// 48 is the navigation button leftInset
|
||||
@@ -187,9 +189,9 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
||||
appTitleBar.SetDragRectangles(dragRect.ToArray());
|
||||
|
||||
// workaround for https://github.com/microsoft/WindowsAppSDK/issues/2976
|
||||
SizeInt32 size = options.AppWindow.ClientSize;
|
||||
SizeInt32 size = window.AppWindow.ClientSize;
|
||||
size.Height -= (int)(31 * scale);
|
||||
options.AppWindow.ResizeClient(size);
|
||||
window.AppWindow.ResizeClient(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
@@ -11,22 +9,12 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// 为扩展窗体提供必要的选项
|
||||
/// </summary>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal interface IExtendedWindowSource
|
||||
internal interface IWindowOptionsSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供的标题栏
|
||||
/// 窗体选项
|
||||
/// </summary>
|
||||
FrameworkElement TitleBar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否持久化尺寸
|
||||
/// </summary>
|
||||
bool PersistSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始大小
|
||||
/// </summary>
|
||||
SizeInt32 InitSize { get; }
|
||||
WindowOptions WindowOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理最大最小信息
|
||||
@@ -22,44 +22,45 @@ internal static class Persistence
|
||||
/// <summary>
|
||||
/// 设置窗体位置
|
||||
/// </summary>
|
||||
/// <param name="options">选项</param>
|
||||
/// <param name="window">选项窗口param>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
public static void RecoverOrInit<TWindow>(in WindowOptions<TWindow> options)
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
public static void RecoverOrInit<TWindow>(TWindow window)
|
||||
where TWindow : Window, IWindowOptionsSource
|
||||
{
|
||||
WindowOptions options = window.WindowOptions;
|
||||
// Set first launch size.
|
||||
double scale = GetScaleForWindowHandle(options.Hwnd);
|
||||
SizeInt32 transformedSize = options.Window.InitSize.Scale(scale);
|
||||
SizeInt32 transformedSize = options.InitSize.Scale(scale);
|
||||
RectInt32 rect = StructMarshal.RectInt32(transformedSize);
|
||||
|
||||
if (options.Window.PersistSize)
|
||||
if (options.PersistSize)
|
||||
{
|
||||
RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (ulong)(CompactRect)rect);
|
||||
if (persistedRect.Size() >= options.Window.InitSize.Size())
|
||||
if (persistedRect.Size() >= options.InitSize.Size())
|
||||
{
|
||||
rect = persistedRect;
|
||||
}
|
||||
}
|
||||
|
||||
TransformToCenterScreen(ref rect);
|
||||
options.AppWindow.MoveAndResize(rect);
|
||||
window.AppWindow.MoveAndResize(rect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存窗体的位置
|
||||
/// </summary>
|
||||
/// <param name="options">选项</param>
|
||||
/// <param name="window">窗口</param>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
public static void Save<TWindow>(in WindowOptions<TWindow> options)
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
public static void Save<TWindow>(TWindow window)
|
||||
where TWindow : Window, IWindowOptionsSource
|
||||
{
|
||||
WINDOWPLACEMENT windowPlacement = StructMarshal.WINDOWPLACEMENT();
|
||||
GetWindowPlacement(options.Hwnd, ref windowPlacement);
|
||||
GetWindowPlacement(window.WindowOptions.Hwnd, ref windowPlacement);
|
||||
|
||||
// prevent save value when we are maximized.
|
||||
if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)options.AppWindow.GetRect());
|
||||
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.Foundation;
|
||||
using WinRT.Interop;
|
||||
|
||||
@@ -13,29 +13,28 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// Window 选项
|
||||
/// </summary>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal readonly struct WindowOptions<TWindow>
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
internal readonly struct WindowOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗体句柄
|
||||
/// </summary>
|
||||
public readonly HWND Hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// AppWindow
|
||||
/// </summary>
|
||||
public readonly AppWindow AppWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 窗体
|
||||
/// </summary>
|
||||
public readonly TWindow Window;
|
||||
|
||||
/// <summary>
|
||||
/// 标题栏元素
|
||||
/// </summary>
|
||||
public readonly FrameworkElement TitleBar;
|
||||
|
||||
/// <summary>
|
||||
/// 初始大小
|
||||
/// </summary>
|
||||
public readonly SizeInt32 InitSize;
|
||||
|
||||
/// <summary>
|
||||
/// 是否持久化尺寸
|
||||
/// </summary>
|
||||
public readonly bool PersistSize;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用 Win UI 3 自带的拓展标题栏实现
|
||||
/// </summary>
|
||||
@@ -46,11 +45,13 @@ internal readonly struct WindowOptions<TWindow>
|
||||
/// </summary>
|
||||
/// <param name="window">窗体</param>
|
||||
/// <param name="titleBar">标题栏</param>
|
||||
public WindowOptions(TWindow window, FrameworkElement titleBar)
|
||||
/// <param name="initSize">初始尺寸</param>
|
||||
/// <param name="persistSize">持久化尺寸</param>
|
||||
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false)
|
||||
{
|
||||
Window = window;
|
||||
Hwnd = (HWND)WindowNative.GetWindowHandle(window);
|
||||
AppWindow = window.AppWindow;
|
||||
TitleBar = titleBar;
|
||||
InitSize = initSize;
|
||||
PersistSize = persistSize;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
@@ -15,12 +16,12 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
[HighQuality]
|
||||
internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
where TWindow : Window, IWindowOptionsSource
|
||||
{
|
||||
private const int WindowSubclassId = 101;
|
||||
private const int DragBarSubclassId = 102;
|
||||
|
||||
private readonly WindowOptions<TWindow> options;
|
||||
private readonly TWindow window;
|
||||
|
||||
// We have to explicitly hold a reference to SUBCLASSPROC
|
||||
private SUBCLASSPROC? windowProc;
|
||||
@@ -30,9 +31,9 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
/// 构造一个新的窗体子类管理器
|
||||
/// </summary>
|
||||
/// <param name="options">选项</param>
|
||||
public WindowSubclass(in WindowOptions<TWindow> options)
|
||||
public WindowSubclass(TWindow window)
|
||||
{
|
||||
this.options = options;
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,6 +42,8 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
/// <returns>是否设置成功</returns>
|
||||
public bool Initialize()
|
||||
{
|
||||
WindowOptions options = window.WindowOptions;
|
||||
|
||||
windowProc = new(OnSubclassProcedure);
|
||||
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0);
|
||||
|
||||
@@ -65,6 +68,8 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
WindowOptions options = window.WindowOptions;
|
||||
|
||||
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
|
||||
windowProc = null;
|
||||
|
||||
@@ -83,14 +88,14 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
case WM_GETMINMAXINFO:
|
||||
{
|
||||
double scalingFactor = Persistence.GetScaleForWindowHandle(hwnd);
|
||||
options.Window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
|
||||
window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
{
|
||||
return (LRESULT)0; // WM_NULL
|
||||
return (LRESULT)(nint)WM_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +110,7 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
{
|
||||
return (LRESULT)0; // WM_NULL
|
||||
return (LRESULT)(nint)WM_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举拓展
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class EnumExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取枚举的描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
/// <param name="enum">枚举值</param>
|
||||
/// <returns>描述</returns>
|
||||
[Obsolete]
|
||||
public static string GetDescription<TEnum>(this TEnum @enum)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
string enumName = Enum.GetName(@enum)!;
|
||||
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||
DescriptionAttribute? attr = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||
return attr?.Description ?? enumName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
/// <param name="enum">枚举值</param>
|
||||
/// <returns>描述</returns>
|
||||
[Obsolete]
|
||||
public static string? GetDescriptionOrNull<TEnum>(this TEnum @enum)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
string enumName = Enum.GetName(@enum)!;
|
||||
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||
DescriptionAttribute? attr = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||
return attr?.Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化的描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
/// <param name="enum">枚举值</param>
|
||||
/// <returns>本地化的描述</returns>
|
||||
public static string GetLocalizedDescription<TEnum>(this TEnum @enum)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
string enumName = Enum.GetName(@enum)!;
|
||||
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||
LocalizationKeyAttribute? attr = field?.GetCustomAttribute<LocalizationKeyAttribute>();
|
||||
string? result = null;
|
||||
if (attr != null)
|
||||
{
|
||||
result = SH.ResourceManager.GetString(attr.Key);
|
||||
}
|
||||
|
||||
return result ?? enumName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化的描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
/// <param name="enum">枚举值</param>
|
||||
/// <returns>本地化的描述</returns>
|
||||
public static string? GetLocalizedDescriptionOrDefault<TEnum>(this TEnum @enum)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
string enumName = Enum.GetName(@enum)!;
|
||||
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||
LocalizationKeyAttribute? attr = field?.GetCustomAttribute<LocalizationKeyAttribute>();
|
||||
string? result = null;
|
||||
if (attr != null)
|
||||
{
|
||||
result = SH.ResourceManager.GetString(attr.Key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,6 +16,6 @@ internal static class ObjectExtension
|
||||
/// <returns>数组</returns>
|
||||
public static T[] ToArray<T>(this T source)
|
||||
{
|
||||
return new[] { source };
|
||||
return new T[] { source };
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,16 @@ namespace Snap.Hutao.Factory;
|
||||
internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
{
|
||||
private readonly MainWindow mainWindow;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的内容对话框工厂
|
||||
/// </summary>
|
||||
/// <param name="taskContext">任务上下文</param>
|
||||
/// <param name="mainWindow">主窗体</param>
|
||||
public ContentDialogFactory(MainWindow mainWindow)
|
||||
public ContentDialogFactory(ITaskContext taskContext, MainWindow mainWindow)
|
||||
{
|
||||
this.taskContext = taskContext;
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
@@ -26,7 +29,7 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
public async ValueTask<ContentDialogResult> ConfirmAsync(string title, string content)
|
||||
{
|
||||
ContentDialog dialog = await CreateForConfirmAsync(title, content).ConfigureAwait(false);
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
return await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
@@ -34,14 +37,14 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
public async ValueTask<ContentDialogResult> ConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close)
|
||||
{
|
||||
ContentDialog dialog = await CreateForConfirmCancelAsync(title, content, defaultButton).ConfigureAwait(false);
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
return await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ContentDialog> CreateForIndeterminateProgressAsync(string title)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
@@ -54,7 +57,7 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
|
||||
private async ValueTask<ContentDialog> CreateForConfirmAsync(string title, string content)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
@@ -69,7 +72,7 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
|
||||
private async ValueTask<ContentDialog> CreateForConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
|
||||
@@ -75,9 +75,7 @@ internal class PickerFactory : IPickerFactory
|
||||
{
|
||||
// Create a folder picker.
|
||||
T picker = new();
|
||||
|
||||
IntPtr hWnd = WindowNative.GetWindowHandle(mainWindow);
|
||||
InitializeWithWindow.Initialize(picker, hWnd);
|
||||
InitializeWithWindow.Initialize(picker, mainWindow.WindowOptions.Hwnd);
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
[
|
||||
{
|
||||
"Name": "AchievementGoalId",
|
||||
"Type": "int",
|
||||
"Documentation": "1-2位 成就分类Id"
|
||||
},
|
||||
{
|
||||
"Name": "AchievementId",
|
||||
"Type": "int",
|
||||
"Documentation": "5位 成就Id"
|
||||
},
|
||||
{
|
||||
"Name": "AvatarId",
|
||||
"Type": "int",
|
||||
"Documentation": "8位 角色Id"
|
||||
},
|
||||
{
|
||||
"Name": "CostumeId",
|
||||
"Type": "int",
|
||||
"Documentation": "6位 角色装扮Id"
|
||||
},
|
||||
{
|
||||
"Name": "EquipAffixId",
|
||||
"Type": "int",
|
||||
@@ -39,14 +54,19 @@
|
||||
"Type": "int",
|
||||
"Documentation": "5位 圣遗物主属性Id"
|
||||
},
|
||||
{
|
||||
"Name": "SkillGroupId",
|
||||
"Type": "int",
|
||||
"Documentation": "3-4位 技能组Id"
|
||||
},
|
||||
{
|
||||
"Name": "SkillId",
|
||||
"Type": "int",
|
||||
"Documentation": "5-6位 技能Id"
|
||||
},
|
||||
{
|
||||
"Name": "WeaponId",
|
||||
"Type": "int",
|
||||
"Documentation": "5位 武器Id"
|
||||
},
|
||||
{
|
||||
"Name": "AchievementId",
|
||||
"Type": "int",
|
||||
"Documentation": "5位 成就Id"
|
||||
}
|
||||
]
|
||||
@@ -4,7 +4,6 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.ViewModel.Game;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
@@ -14,7 +13,7 @@ namespace Snap.Hutao;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedWindowSource
|
||||
internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOptionsSource
|
||||
{
|
||||
private const int MinWidth = 240;
|
||||
private const int MinHeight = 240;
|
||||
@@ -22,6 +21,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedW
|
||||
private const int MaxWidth = 320;
|
||||
private const int MaxHeight = 320;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
private readonly IServiceScope scope;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,19 +33,13 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedW
|
||||
InitializeComponent();
|
||||
|
||||
scope = scopeFactory.CreateScope();
|
||||
windowOptions = new(this, DragableGrid, new(320, 320));
|
||||
ExtendedWindow<LaunchGameWindow>.Initialize(this, scope.ServiceProvider);
|
||||
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService<LaunchGameViewModel>();
|
||||
Closed += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FrameworkElement TitleBar { get => DragableGrid; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool PersistSize { get => false; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SizeInt32 InitSize { get => new(320, 320); }
|
||||
public WindowOptions WindowOptions { get => throw new NotImplementedException(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
||||
@@ -17,11 +17,13 @@ namespace Snap.Hutao;
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed partial class MainWindow : Window, IExtendedWindowSource, IRecipient<WelcomeStateCompleteMessage>
|
||||
internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipient<WelcomeStateCompleteMessage>
|
||||
{
|
||||
private const int MinWidth = 848;
|
||||
private const int MinHeight = 524;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
/// </summary>
|
||||
@@ -29,29 +31,16 @@ internal sealed partial class MainWindow : Window, IExtendedWindowSource, IRecip
|
||||
public MainWindow(IServiceProvider serviceProvider)
|
||||
{
|
||||
InitializeComponent();
|
||||
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
|
||||
ExtendedWindow<MainWindow>.Initialize(this, serviceProvider);
|
||||
IsPresent = true;
|
||||
Closed += (s, e) => IsPresent = false;
|
||||
|
||||
Ioc.Default.GetRequiredService<IMessenger>().Register(this);
|
||||
serviceProvider.GetRequiredService<IMessenger>().Register(this);
|
||||
|
||||
// If not complete we should present the welcome view.
|
||||
ContentSwitchPresenter.Value = StaticResource.IsAnyUnfulfilledContractPresent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否打开
|
||||
/// </summary>
|
||||
public static bool IsPresent { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FrameworkElement TitleBar { get => TitleBarView.DragArea; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool PersistSize { get => true; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SizeInt32 InitSize { get => new(1200, 741); }
|
||||
public WindowOptions WindowOptions { get => windowOptions; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor)
|
||||
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Model.Calculable;
|
||||
|
||||
@@ -26,7 +27,7 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
AvatarId = avatar.Id;
|
||||
LevelMin = 1;
|
||||
LevelMax = 90;
|
||||
Skills = avatar.SkillDepot.EnumerateCompositeSkillsNoInherents().Select(p => p.ToCalculable()).ToList();
|
||||
Skills = avatar.SkillDepot.CompositeSkillsNoInherents().SelectList(p => p.ToCalculable());
|
||||
Name = avatar.Name;
|
||||
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
|
||||
Quality = avatar.Quality;
|
||||
@@ -39,12 +40,12 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
/// 构造一个新的可计算角色
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
public CalculableAvatar(Binding.AvatarProperty.AvatarView avatar)
|
||||
public CalculableAvatar(AvatarView avatar)
|
||||
{
|
||||
AvatarId = avatar.Id;
|
||||
LevelMin = avatar.LevelNumber;
|
||||
LevelMax = 90; // hard coded 90
|
||||
Skills = avatar.Skills.Select(s => s.ToCalculable()).ToList();
|
||||
Skills = avatar.Skills.SelectList(s => s.ToCalculable());
|
||||
Name = avatar.Name;
|
||||
Icon = avatar.Icon;
|
||||
Quality = avatar.Quality;
|
||||
@@ -63,7 +64,7 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
public int LevelMax { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IList<ICalculableSkill> Skills { get; }
|
||||
public List<ICalculableSkill> Skills { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Model.Calculable;
|
||||
|
||||
@@ -38,7 +39,7 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
|
||||
/// 构造一个新的可计算的技能
|
||||
/// </summary>
|
||||
/// <param name="skill">技能</param>
|
||||
public CalculableSkill(Binding.AvatarProperty.SkillView skill)
|
||||
public CalculableSkill(SkillView skill)
|
||||
{
|
||||
GruopId = skill.GroupId;
|
||||
LevelMin = skill.LevelNumber;
|
||||
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Model.Calculable;
|
||||
|
||||
@@ -25,7 +26,7 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
|
||||
{
|
||||
WeaponId = weapon.Id;
|
||||
LevelMin = 1;
|
||||
LevelMax = (int)weapon.Quality >= 3 ? 90 : 70;
|
||||
LevelMax = weapon.MaxLevel;
|
||||
Name = weapon.Name;
|
||||
Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
|
||||
Quality = weapon.RankLevel;
|
||||
@@ -38,11 +39,11 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
|
||||
/// 构造一个新的可计算武器
|
||||
/// </summary>
|
||||
/// <param name="weapon">武器</param>
|
||||
public CalculableWeapon(Binding.AvatarProperty.WeaponView weapon)
|
||||
public CalculableWeapon(WeaponView weapon)
|
||||
{
|
||||
WeaponId = weapon.Id;
|
||||
LevelMin = weapon.LevelNumber;
|
||||
LevelMax = (int)weapon.Quality >= 3 ? 90 : 70;
|
||||
LevelMax = weapon.MaxLevel;
|
||||
Name = weapon.Name;
|
||||
Icon = weapon.Icon;
|
||||
Quality = weapon.Quality;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Model.Calculable;
|
||||
/// 可计算源
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface ICalculable : Binding.INameIcon
|
||||
internal interface ICalculable : INameIcon
|
||||
{
|
||||
/// <summary>
|
||||
/// 星级
|
||||
|
||||
@@ -29,5 +29,5 @@ internal interface ICalculableAvatar : ICalculable
|
||||
/// <summary>
|
||||
/// 技能组
|
||||
/// </summary>
|
||||
IList<ICalculableSkill> Skills { get; }
|
||||
List<ICalculableSkill> Skills { get; }
|
||||
}
|
||||
@@ -23,17 +23,17 @@ internal sealed class Achievement : IEquatable<Achievement>
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存档 Id
|
||||
/// </summary>
|
||||
public Guid ArchiveId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存档
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(ArchiveId))]
|
||||
public AchievementArchive Archive { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 存档Id
|
||||
/// </summary>
|
||||
public Guid ArchiveId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
@@ -100,7 +100,7 @@ internal sealed class Achievement : IEquatable<Achievement>
|
||||
Id = Id,
|
||||
Current = Current,
|
||||
Status = Status,
|
||||
Timestamp = Time.ToUniversalTime().ToUnixTimeSeconds(),
|
||||
Timestamp = Time.ToUnixTimeSeconds(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -101,11 +101,6 @@ internal sealed class DailyNoteEntry : ObservableObject
|
||||
/// </summary>
|
||||
public bool ExpeditionNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否在主页显示小组件
|
||||
/// </summary>
|
||||
public bool ShowInHomeWidget { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的实时便笺
|
||||
/// </summary>
|
||||
@@ -117,8 +112,8 @@ internal sealed class DailyNoteEntry : ObservableObject
|
||||
{
|
||||
UserId = userAndUid.User.InnerId,
|
||||
Uid = userAndUid.Uid.Value,
|
||||
ResinNotifyThreshold = 160,
|
||||
HomeCoinNotifyThreshold = 2400,
|
||||
ResinNotifyThreshold = 120,
|
||||
HomeCoinNotifyThreshold = 1800,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ internal sealed class AppDbContext : DbContext
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,8 @@ using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -47,34 +47,31 @@ internal sealed class GachaArchive : ISelectable
|
||||
/// <summary>
|
||||
/// 初始化或跳过
|
||||
/// </summary>
|
||||
/// <param name="context">上下文</param>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="gachaArchives">数据库集</param>
|
||||
/// <param name="collection">集合</param>
|
||||
public static void SkipOrInit([NotNull] ref GachaArchive? archive, string uid, DbSet<GachaArchive> gachaArchives, ObservableCollection<GachaArchive> collection)
|
||||
public static void SkipOrInit(in GachaArchiveInitializationContext context, [NotNull] ref GachaArchive? archive)
|
||||
{
|
||||
if (archive == null)
|
||||
{
|
||||
Init(out archive, uid, gachaArchives, collection);
|
||||
Init(context, out archive);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="context">上下文</param>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="gachaArchives">数据库集</param>
|
||||
/// <param name="collection">集合</param>
|
||||
public static void Init([NotNull] out GachaArchive? archive, string uid, DbSet<GachaArchive> gachaArchives, ObservableCollection<GachaArchive> collection)
|
||||
[SuppressMessage("", "SH002")]
|
||||
public static void Init(GachaArchiveInitializationContext context, [NotNull] out GachaArchive? archive)
|
||||
{
|
||||
archive = collection.SingleOrDefault(a => a.Uid == uid);
|
||||
archive = context.ArchiveCollection.SingleOrDefault(a => a.Uid == context.Uid);
|
||||
|
||||
if (archive == null)
|
||||
{
|
||||
GachaArchive created = Create(uid);
|
||||
gachaArchives.AddAndSave(created);
|
||||
ThreadHelper.InvokeOnMainThread(() => collection!.Add(created));
|
||||
GachaArchive created = Create(context.Uid);
|
||||
context.GachaArchives.AddAndSave(created);
|
||||
context.TaskContext.InvokeOnMainThread(() => context.ArchiveCollection.Add(created));
|
||||
archive = created;
|
||||
}
|
||||
}
|
||||
@@ -82,24 +79,22 @@ internal sealed class GachaArchive : ISelectable
|
||||
/// <summary>
|
||||
/// 保存祈愿物品
|
||||
/// </summary>
|
||||
/// <param name="itemsToAdd">待添加物品</param>
|
||||
/// <param name="isLazy">是否懒惰</param>
|
||||
/// <param name="endId">结尾Id</param>
|
||||
/// <param name="gachaItems">数据集</param>
|
||||
public void SaveItems(List<GachaItem> itemsToAdd, bool isLazy, long endId, DbSet<GachaItem> gachaItems)
|
||||
/// <param name="context">上下文</param>
|
||||
[SuppressMessage("", "SH002")]
|
||||
public void SaveItems(GachaItemSaveContext context)
|
||||
{
|
||||
if (itemsToAdd.Count > 0)
|
||||
if (context.ItemsToAdd.Count > 0)
|
||||
{
|
||||
// 全量刷新
|
||||
if (!isLazy)
|
||||
if (!context.IsLazy)
|
||||
{
|
||||
gachaItems
|
||||
context.GachaItems
|
||||
.Where(i => i.ArchiveId == InnerId)
|
||||
.Where(i => i.Id >= endId)
|
||||
.Where(i => i.Id >= context.EndId)
|
||||
.ExecuteDelete();
|
||||
}
|
||||
|
||||
gachaItems.AddRangeAndSave(itemsToAdd);
|
||||
context.GachaItems.AddRangeAndSave(context.ItemsToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,12 @@ internal sealed class GachaItem
|
||||
/// <returns>物品类型字符串</returns>
|
||||
public static string GetItemTypeStringByItemId(int itemId)
|
||||
{
|
||||
int idLength = itemId.Place();
|
||||
return idLength == 8 ? "角色" : idLength == 5 ? "武器" : "未知";
|
||||
return itemId.Place() switch
|
||||
{
|
||||
8 => "角色",
|
||||
5 => "武器",
|
||||
_ => "未知",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@@ -12,11 +13,8 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("game_accounts")]
|
||||
internal sealed class GameAccount : INotifyPropertyChanged
|
||||
internal sealed class GameAccount : ObservableObject
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// </summary>
|
||||
@@ -66,7 +64,7 @@ internal sealed class GameAccount : INotifyPropertyChanged
|
||||
public void UpdateAttachUid(string? uid)
|
||||
{
|
||||
AttachUid = uid;
|
||||
PropertyChanged?.Invoke(this, new(nameof(AttachUid)));
|
||||
OnPropertyChanged(nameof(AttachUid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,6 +74,6 @@ internal sealed class GameAccount : INotifyPropertyChanged
|
||||
public void UpdateName(string name)
|
||||
{
|
||||
Name = name;
|
||||
PropertyChanged?.Invoke(this, new(nameof(Name)));
|
||||
OnPropertyChanged($"{nameof(Name)}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 键名
|
||||
/// </summary>
|
||||
internal sealed partial class SettingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public const string GamePath = "GamePath";
|
||||
|
||||
/// <summary>
|
||||
/// 空的历史记录卡池是否可见
|
||||
/// </summary>
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
/// <summary>
|
||||
/// 窗口背景类型
|
||||
/// </summary>
|
||||
public const string SystemBackdropType = "SystemBackdropType";
|
||||
|
||||
/// <summary>
|
||||
/// 启用高级功能
|
||||
/// </summary>
|
||||
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺刷新时间
|
||||
/// </summary>
|
||||
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺提醒式通知
|
||||
/// </summary>
|
||||
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺免打扰模式
|
||||
/// </summary>
|
||||
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 独占全屏
|
||||
/// </summary>
|
||||
public const string LaunchIsExclusive = "Launch.IsExclusive";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 全屏
|
||||
/// </summary>
|
||||
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 无边框
|
||||
/// </summary>
|
||||
public const string LaunchIsBorderless = "Launch.IsBorderless";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 宽度
|
||||
/// </summary>
|
||||
public const string LaunchScreenWidth = "Launch.ScreenWidth";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 高度
|
||||
/// </summary>
|
||||
public const string LaunchScreenHeight = "Launch.ScreenHeight";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 解锁帧率
|
||||
/// </summary>
|
||||
public const string LaunchUnlockFps = "Launch.UnlockFps";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 目标帧率
|
||||
/// </summary>
|
||||
public const string LaunchTargetFps = "Launch.TargetFps";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 显示器编号
|
||||
/// </summary>
|
||||
public const string LaunchMonitor = "Launch.Monitor";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 多倍启动
|
||||
/// </summary>
|
||||
public const string MultipleInstances = "Launch.MultipleInstances";
|
||||
|
||||
/// <summary>
|
||||
/// 语言
|
||||
/// </summary>
|
||||
public const string Culture = "Culture";
|
||||
}
|
||||
@@ -12,96 +12,8 @@ namespace Snap.Hutao.Model.Entity;
|
||||
[HighQuality]
|
||||
[Table("settings")]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class SettingEntry
|
||||
internal sealed partial class SettingEntry
|
||||
{
|
||||
#region EntryNames
|
||||
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public const string GamePath = "GamePath";
|
||||
|
||||
/// <summary>
|
||||
/// 空的历史记录卡池是否可见
|
||||
/// </summary>
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
/// <summary>
|
||||
/// 窗口背景类型
|
||||
/// </summary>
|
||||
public const string SystemBackdropType = "SystemBackdropType";
|
||||
|
||||
/// <summary>
|
||||
/// 启用高级功能
|
||||
/// </summary>
|
||||
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺刷新时间
|
||||
/// </summary>
|
||||
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺提醒式通知
|
||||
/// </summary>
|
||||
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺免打扰模式
|
||||
/// </summary>
|
||||
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 独占全屏
|
||||
/// </summary>
|
||||
public const string LaunchIsExclusive = "Launch.IsExclusive";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 全屏
|
||||
/// </summary>
|
||||
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 无边框
|
||||
/// </summary>
|
||||
public const string LaunchIsBorderless = "Launch.IsBorderless";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 宽度
|
||||
/// </summary>
|
||||
public const string LaunchScreenWidth = "Launch.ScreenWidth";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 高度
|
||||
/// </summary>
|
||||
public const string LaunchScreenHeight = "Launch.ScreenHeight";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 解锁帧率
|
||||
/// </summary>
|
||||
public const string LaunchUnlockFps = "Launch.UnlockFps";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 目标帧率
|
||||
/// </summary>
|
||||
public const string LaunchTargetFps = "Launch.TargetFps";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 显示器编号
|
||||
/// </summary>
|
||||
public const string LaunchMonitor = "Launch.Monitor";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 多倍启动
|
||||
/// </summary>
|
||||
public const string MultipleInstances = "Launch.MultipleInstances";
|
||||
|
||||
/// <summary>
|
||||
/// 语言
|
||||
/// </summary>
|
||||
public const string Culture = "Culture";
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的设置入口
|
||||
/// </summary>
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed class SpiralAbyssEntry : ObservableObject
|
||||
public int ScheduleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 视图中使用的计划Id字符串
|
||||
/// 视图 中使用的计划 Id 字符串
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public string Schedule { get => string.Format(SH.ModelEntitySpiralAbyssScheduleFormat, ScheduleId); }
|
||||
|
||||
@@ -67,9 +67,9 @@ internal sealed class User : ISelectable
|
||||
/// <returns>新创建的用户</returns>
|
||||
public static User Create(Cookie cookie, bool isOversea)
|
||||
{
|
||||
_ = cookie.TryGetAsSToken(isOversea, out Cookie? stoken);
|
||||
_ = cookie.TryGetAsLToken(out Cookie? ltoken);
|
||||
_ = cookie.TryGetAsCookieToken(out Cookie? cookieToken);
|
||||
_ = cookie.TryGetSToken(isOversea, out Cookie? stoken);
|
||||
_ = cookie.TryGetLToken(out Cookie? ltoken);
|
||||
_ = cookie.TryGetCookieToken(out Cookie? cookieToken);
|
||||
|
||||
return new() { SToken = stoken, LToken = ltoken, CookieToken = cookieToken };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
namespace Snap.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 实体与元数据
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
namespace Snap.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 名称与图标
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
namespace Snap.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 包括侧面图标的名称与图标
|
||||
@@ -16,6 +16,6 @@ internal sealed class UIGFItem : GachaLogItem
|
||||
/// 额外祈愿映射
|
||||
/// </summary>
|
||||
[JsonPropertyName("uigf_gacha_type")]
|
||||
[JsonEnum(JsonSerializeType.Int32AsString)]
|
||||
[JsonEnum(JsonSerializeType.NumberString)]
|
||||
public GachaConfigType UIGFGachaType { get; set; } = default!;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 从属地区
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Localization]
|
||||
internal enum AssociationType
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 体型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Localization]
|
||||
internal enum BodyType
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -60,7 +60,27 @@ internal enum ElementType
|
||||
AntiFire = 9,
|
||||
|
||||
/// <summary>
|
||||
/// 默认
|
||||
/// 枫丹玩法
|
||||
/// </summary>
|
||||
Default = 255,
|
||||
VehicleMuteIce = 10,
|
||||
|
||||
/// <summary>
|
||||
/// 弹弹菇
|
||||
/// </summary>
|
||||
Mushroom = 11,
|
||||
|
||||
/// <summary>
|
||||
/// 激元素
|
||||
/// </summary>
|
||||
Overdose = 12,
|
||||
|
||||
/// <summary>
|
||||
/// 木元素
|
||||
/// </summary>
|
||||
Wood = 13,
|
||||
|
||||
/// <summary>
|
||||
/// 个数
|
||||
/// </summary>
|
||||
Count = 14,
|
||||
}
|
||||
@@ -9,6 +9,7 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 战斗属性
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Localization]
|
||||
internal enum FightProperty
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Localization]
|
||||
internal enum ItemQuality
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 武器类型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Localization]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal enum WeaponType
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
namespace Snap.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 物品基类
|
||||
@@ -19,7 +19,7 @@ internal sealed class Achievement
|
||||
/// <summary>
|
||||
/// 分类Id
|
||||
/// </summary>
|
||||
public int Goal { get; set; }
|
||||
public AchievementGoalId Goal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序顺序
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Achievement;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +14,7 @@ internal sealed class AchievementGoal
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public AchievementGoalId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序顺序
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Achievement;
|
||||
|
||||
/// <summary>
|
||||
@@ -11,7 +13,7 @@ internal sealed class Reward
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public MaterialId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数量
|
||||
|
||||
@@ -20,6 +20,7 @@ internal static class EnumExtension
|
||||
internal static FormatMethod GetFormatMethod<TEnum>(this TEnum @enum)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
// TODO: Not use Reflection
|
||||
string enumName = Must.NotNull(Enum.GetName(@enum)!);
|
||||
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||
FormatAttribute? attr = field?.GetCustomAttribute<FormatAttribute>();
|
||||
|
||||
@@ -49,7 +49,7 @@ internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IName
|
||||
/// 转换为基础物品
|
||||
/// </summary>
|
||||
/// <returns>基础物品</returns>
|
||||
public Binding.Item ToItemBase()
|
||||
public Model.Item ToItemBase()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +14,7 @@ internal sealed class Costume
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public CostumeId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +14,7 @@ internal sealed partial class ProudableSkill : Skill
|
||||
/// <summary>
|
||||
/// 组Id
|
||||
/// </summary>
|
||||
public int GroupId { get; set; }
|
||||
public SkillGroupId GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提升属性
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
|
||||
/// <summary>
|
||||
@@ -13,7 +15,7 @@ internal class Skill
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public SkillId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
[HighQuality]
|
||||
internal sealed class SkillDepot
|
||||
{
|
||||
private List<ProudableSkill>? compositeSkills;
|
||||
private List<ProudableSkill>? compositeSkillsNoInherents;
|
||||
|
||||
/// <summary>
|
||||
/// 技能天赋
|
||||
/// </summary>
|
||||
@@ -30,7 +33,18 @@ internal sealed class SkillDepot
|
||||
/// </summary>
|
||||
public List<ProudableSkill> CompositeSkills
|
||||
{
|
||||
get => EnumerateCompositeSkills().ToList();
|
||||
get
|
||||
{
|
||||
if (compositeSkills == null)
|
||||
{
|
||||
compositeSkills = new(Skills.Count + 1 + Inherents.Count);
|
||||
compositeSkills.AddRange(Skills);
|
||||
compositeSkills.Add(EnergySkill);
|
||||
compositeSkills.AddRange(Inherents);
|
||||
}
|
||||
|
||||
return compositeSkills;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,32 +56,16 @@ internal sealed class SkillDepot
|
||||
/// 获取无固有天赋的技能列表
|
||||
/// </summary>
|
||||
/// <returns>天赋列表</returns>
|
||||
public IEnumerable<ProudableSkill> EnumerateCompositeSkillsNoInherents()
|
||||
public List<ProudableSkill> CompositeSkillsNoInherents()
|
||||
{
|
||||
foreach (ProudableSkill skill in Skills)
|
||||
if (compositeSkillsNoInherents == null)
|
||||
{
|
||||
// skip skills like Mona's & Ayaka's shift
|
||||
if (skill.Proud.Parameters.Count > 1)
|
||||
{
|
||||
yield return skill;
|
||||
}
|
||||
compositeSkillsNoInherents = new(Skills.Count + 1);
|
||||
compositeSkillsNoInherents.AddRange(Skills);
|
||||
compositeSkillsNoInherents.Add(EnergySkill);
|
||||
}
|
||||
|
||||
yield return EnergySkill;
|
||||
}
|
||||
|
||||
private IEnumerable<ProudableSkill> EnumerateCompositeSkills()
|
||||
{
|
||||
foreach (ProudableSkill skill in Skills)
|
||||
{
|
||||
yield return skill;
|
||||
}
|
||||
|
||||
yield return EnergySkill;
|
||||
|
||||
foreach (ProudableSkill skill in Inherents)
|
||||
{
|
||||
yield return skill;
|
||||
}
|
||||
// No Inherents
|
||||
return compositeSkillsNoInherents;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
@@ -13,7 +14,7 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// 描述参数解析器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed partial class ParameterDescriptor : ValueConverter<DescriptionsParameters, IList<LevelParameters<string, ParameterDescription>>>
|
||||
internal sealed partial class DescriptionsParametersDescriptor : ValueConverter<DescriptionsParameters, IList<LevelParameters<string, ParameterDescription>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取特定等级的解释
|
||||
@@ -23,58 +24,48 @@ internal sealed partial class ParameterDescriptor : ValueConverter<DescriptionsP
|
||||
/// <returns>特定等级的解释</returns>
|
||||
public static LevelParameters<string, ParameterDescription> Convert(DescriptionsParameters from, int level)
|
||||
{
|
||||
// DO NOT INLINE!
|
||||
// Cache the formats
|
||||
List<DescFormat> formats = from.Descriptions
|
||||
.Select(desc => new DescFormat(desc))
|
||||
.ToList();
|
||||
|
||||
LevelParameters<int, double> param = from.Parameters.Single(param => param.Level == level);
|
||||
|
||||
return new LevelParameters<string, ParameterDescription>($"Lv.{param.Level}", GetParameterInfos(formats, param.Parameters));
|
||||
return new LevelParameters<string, ParameterDescription>($"Lv.{param.Level}", GetParameterDescription(from.Descriptions, param.Parameters));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<LevelParameters<string, ParameterDescription>> Convert(DescriptionsParameters from)
|
||||
{
|
||||
// DO NOT INLINE!
|
||||
// Cache the formats
|
||||
List<DescFormat> formats = from.Descriptions
|
||||
.SelectList(desc => new DescFormat(desc));
|
||||
|
||||
List<LevelParameters<string, ParameterDescription>> parameters = from.Parameters
|
||||
.SelectList(param => new LevelParameters<string, ParameterDescription>(param.Level.ToString(), GetParameterInfos(formats, param.Parameters)));
|
||||
.SelectList(param => new LevelParameters<string, ParameterDescription>($"Lv.{param.Level}", GetParameterDescription(from.Descriptions, param.Parameters)));
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static List<ParameterDescription> GetParameterInfos(List<DescFormat> formats, List<double> param)
|
||||
{
|
||||
Span<DescFormat> span = CollectionsMarshal.AsSpan(formats);
|
||||
List<ParameterDescription> results = new(span.Length);
|
||||
|
||||
foreach (DescFormat descFormat in span)
|
||||
{
|
||||
string format = descFormat.Format;
|
||||
string resultFormatted = ParamRegex().Replace(format, match => EvaluateMatch(match, param));
|
||||
results.Add(new ParameterDescription { Description = descFormat.Description, Parameter = resultFormatted });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[GeneratedRegex("{param[0-9]+.*?}")]
|
||||
private static partial Regex ParamRegex();
|
||||
|
||||
private static string EvaluateMatch(Match match, IList<double> param)
|
||||
private static List<ParameterDescription> GetParameterDescription(List<string> descriptions, List<double> param)
|
||||
{
|
||||
Span<string> span = CollectionsMarshal.AsSpan(descriptions);
|
||||
List<ParameterDescription> results = new(span.Length);
|
||||
|
||||
foreach (ReadOnlySpan<char> desc in span)
|
||||
{
|
||||
int indexOfSeparator = desc.IndexOf('|');
|
||||
ReadOnlySpan<char> description = desc[..indexOfSeparator];
|
||||
ReadOnlySpan<char> format = desc[(indexOfSeparator + 1)..];
|
||||
|
||||
string resultFormatted = ParamRegex().Replace(format.ToString(), match => ReplaceParamInMatch(match, param));
|
||||
results.Add(new ParameterDescription { Description = description.ToString(), Parameter = resultFormatted });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string ReplaceParamInMatch(Match match, List<double> param)
|
||||
{
|
||||
if (match.Success)
|
||||
{
|
||||
// remove parentheses and split by {value:format}
|
||||
// remove parentheses and split by {value:format} like {param1:F}
|
||||
string[] parts = match.Value[1..^1].Split(':', 2);
|
||||
|
||||
int index = int.Parse(parts[0]["param".Length..]) - 1;
|
||||
|
||||
return ParameterFormat.Format($"{{0:{parts[1]}}}", param[index]);
|
||||
}
|
||||
else
|
||||
@@ -82,20 +73,4 @@ internal sealed partial class ParameterDescriptor : ValueConverter<DescriptionsP
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DescFormat
|
||||
{
|
||||
public DescFormat(string desc)
|
||||
{
|
||||
// Spilt rawDesc into two parts: desc and format
|
||||
string[] parts = desc.Split('|', 2);
|
||||
|
||||
Description = parts[0];
|
||||
Format = parts[1];
|
||||
}
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Format { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,28 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
[HighQuality]
|
||||
internal sealed class ElementNameIconConverter : ValueConverter<string, Uri>
|
||||
{
|
||||
private static readonly Dictionary<string, string> LocalizedNameToElementIconName = new()
|
||||
{
|
||||
[SH.ModelIntrinsicElementNameElec] = "Electric",
|
||||
[SH.ModelIntrinsicElementNameFire] = "Fire",
|
||||
[SH.ModelIntrinsicElementNameGrass] = "Grass",
|
||||
[SH.ModelIntrinsicElementNameIce] = "Ice",
|
||||
[SH.ModelIntrinsicElementNameRock] = "Rock",
|
||||
[SH.ModelIntrinsicElementNameWater] = "Water",
|
||||
[SH.ModelIntrinsicElementNameWind] = "Wind",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, ElementType> LocalizedNameToElementType = new()
|
||||
{
|
||||
[SH.ModelIntrinsicElementNameElec] = ElementType.Electric,
|
||||
[SH.ModelIntrinsicElementNameFire] = ElementType.Fire,
|
||||
[SH.ModelIntrinsicElementNameGrass] = ElementType.Grass,
|
||||
[SH.ModelIntrinsicElementNameIce] = ElementType.Ice,
|
||||
[SH.ModelIntrinsicElementNameRock] = ElementType.Rock,
|
||||
[SH.ModelIntrinsicElementNameWater] = ElementType.Water,
|
||||
[SH.ModelIntrinsicElementNameWind] = ElementType.Wind,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 将中文元素名称转换为图标链接
|
||||
/// </summary>
|
||||
@@ -19,17 +41,7 @@ internal sealed class ElementNameIconConverter : ValueConverter<string, Uri>
|
||||
/// <returns>图标链接</returns>
|
||||
public static Uri ElementNameToIconUri(string elementName)
|
||||
{
|
||||
string element = elementName switch
|
||||
{
|
||||
"雷" => "Electric",
|
||||
"火" => "Fire",
|
||||
"草" => "Grass",
|
||||
"冰" => "Ice",
|
||||
"岩" => "Rock",
|
||||
"水" => "Water",
|
||||
"风" => "Wind",
|
||||
_ => string.Empty,
|
||||
};
|
||||
string? element = LocalizedNameToElementIconName.GetValueOrDefault(elementName);
|
||||
|
||||
return string.IsNullOrEmpty(element)
|
||||
? Web.HutaoEndpoints.UIIconNone
|
||||
@@ -45,17 +57,7 @@ internal sealed class ElementNameIconConverter : ValueConverter<string, Uri>
|
||||
/// <returns>元素类型</returns>
|
||||
public static ElementType ElementNameToElementType(string elementName)
|
||||
{
|
||||
return elementName switch
|
||||
{
|
||||
"雷" => ElementType.Electric,
|
||||
"火" => ElementType.Fire,
|
||||
"草" => ElementType.Grass,
|
||||
"冰" => ElementType.Ice,
|
||||
"岩" => ElementType.Rock,
|
||||
"水" => ElementType.Water,
|
||||
"风" => ElementType.Wind,
|
||||
_ => ElementType.None,
|
||||
};
|
||||
return LocalizedNameToElementType.GetValueOrDefault(elementName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Annotation;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// 基础属性翻译器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class PropertyDescriptor : ValueConverter<PropertiesParameters, List<LevelParameters<string, ParameterDescription>>?>
|
||||
internal sealed class PropertiesParametersDescriptor : ValueConverter<PropertiesParameters, List<LevelParameters<string, ParameterDescription>>?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 格式化名称与值
|
||||
@@ -57,14 +57,14 @@ internal sealed class MonsterBaseValue : BaseValue
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FireSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_WATER_SUB_HURT, WaterSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_GRASS_SUB_HURT, GrassSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_ELEC_SUB_HURT, ElecSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_WIND_SUB_HURT, WindSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_ICE_SUB_HURT, IceSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_ROCK_SUB_HURT, RockSubHurt),
|
||||
Converter.PropertyDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, PhysicalSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FireSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_WATER_SUB_HURT, WaterSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_GRASS_SUB_HURT, GrassSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_ELEC_SUB_HURT, ElecSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_WIND_SUB_HURT, WindSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_ICE_SUB_HURT, IceSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_ROCK_SUB_HURT, RockSubHurt),
|
||||
Converter.PropertiesParametersDescriptor.FormatNameValue(Intrinsic.FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, PhysicalSubHurt),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
|
||||
/// <summary>
|
||||
/// 最大等级
|
||||
/// </summary>
|
||||
public int MaxLevel { get => ((int)Quality) >= 3 ? 90 : 70; }
|
||||
internal int MaxLevel { get => ((int)Quality) >= 3 ? 90 : 70; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ICalculableWeapon ToCalculable()
|
||||
@@ -43,7 +43,7 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
|
||||
/// 转换为基础物品
|
||||
/// </summary>
|
||||
/// <returns>基础物品</returns>
|
||||
public Binding.Item ToItemBase()
|
||||
public Model.Item ToItemBase()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Resource.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// 指示此枚举支持本地化
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Enum)]
|
||||
internal sealed class LocalizationAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
namespace Snap.Hutao.Resource.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// 本地化键
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal class LocalizationKeyAttribute : Attribute
|
||||
internal sealed class LocalizationKeyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定本地化键
|
||||
@@ -1536,6 +1536,15 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 同步角色信息 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ViewAvatarPropertySyncDataButtonLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ViewAvatarPropertySyncDataButtonLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 成就统计 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Enka;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
|
||||
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
|
||||
using PropertyAvatar = Snap.Hutao.Model.Binding.AvatarProperty.AvatarView;
|
||||
using PropertyReliquary = Snap.Hutao.Model.Binding.AvatarProperty.ReliquaryView;
|
||||
using PropertyWeapon = Snap.Hutao.Model.Binding.AvatarProperty.WeaponView;
|
||||
using PropertyAvatar = Snap.Hutao.ViewModel.AvatarProperty.AvatarView;
|
||||
using PropertyReliquary = Snap.Hutao.ViewModel.AvatarProperty.ReliquaryView;
|
||||
using PropertyWeapon = Snap.Hutao.ViewModel.AvatarProperty.WeaponView;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
|
||||
@@ -54,7 +55,7 @@ internal sealed class SummaryAvatarFactory
|
||||
|
||||
// webinfo & metadata mixed part
|
||||
Constellations = SummaryHelper.CreateConstellations(avatar.SkillDepot.Talents, avatarInfo.TalentIdList),
|
||||
Skills = SummaryHelper.CreateSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.EnumerateCompositeSkillsNoInherents()),
|
||||
Skills = SummaryHelper.CreateSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.CompositeSkillsNoInherents()),
|
||||
|
||||
// webinfo part
|
||||
FetterLevel = avatarInfo.FetterInfo?.ExpLevel ?? 0,
|
||||
@@ -76,7 +77,7 @@ internal sealed class SummaryAvatarFactory
|
||||
{
|
||||
if (avatarInfo.CostumeId.HasValue)
|
||||
{
|
||||
int costumeId = avatarInfo.CostumeId.Value;
|
||||
CostumeId costumeId = avatarInfo.CostumeId.Value;
|
||||
Model.Metadata.Avatar.Costume costume = avatar.Costumes.Single(c => c.Id == costumeId);
|
||||
|
||||
// Set to costume icon
|
||||
@@ -132,7 +133,7 @@ internal sealed class SummaryAvatarFactory
|
||||
else
|
||||
{
|
||||
subStat.StatValue = subStat.StatValue - Math.Truncate(subStat.StatValue) > 0 ? subStat.StatValue / 100D : subStat.StatValue;
|
||||
subProperty = Model.Metadata.Converter.PropertyDescriptor.FormatNameDescription(subStat.AppendPropId, subStat.StatValue);
|
||||
subProperty = Model.Metadata.Converter.PropertiesParametersDescriptor.FormatNameDescription(subStat.AppendPropId, subStat.StatValue);
|
||||
}
|
||||
|
||||
return new()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user