add metadata service

This commit is contained in:
DismissedLight
2022-07-02 22:02:23 +08:00
parent f4f164acb6
commit ddc06c014f
106 changed files with 3093 additions and 173 deletions

View File

@@ -3,7 +3,8 @@
<TargetFrameworks>net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>SettingsUI</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>

View File

@@ -107,7 +107,7 @@ internal static partial class ServiceCollectionExtensions
lines.Add(lineBuilder.ToString());
}
foreach (string line in lines.OrderByDescending(x => x))
foreach (string line in lines.OrderBy(x => x))
{
sourceCodeBuilder.Append(line);
}

View File

@@ -54,8 +54,8 @@ Global
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|arm64.ActiveCfg = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|arm64.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.ActiveCfg = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.ActiveCfg = Debug|x64
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.Build.0 = Debug|x64
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x86.ActiveCfg = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x86.Build.0 = Debug|Any CPU
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -17,6 +17,8 @@
<StaticResource x:Key="WindowCaptionForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="WindowCaptionForegroundDisabled" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush" />
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>

View File

@@ -4,7 +4,11 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service.Abstraction;
using System.Diagnostics;
using Windows.ApplicationModel.Activation;
namespace Snap.Hutao;
@@ -14,10 +18,10 @@ namespace Snap.Hutao;
public partial class App : Application
{
private static Window? window;
private readonly ILogger<App> logger;
/// <summary>
/// Initializes the singleton application object.
/// This is the first line of authored code executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
@@ -25,6 +29,7 @@ public partial class App : Application
InitializeComponent();
InitializeDependencyInjection();
logger = Ioc.Default.GetRequiredService<ILogger<App>>();
UnhandledException += AppUnhandledException;
}
@@ -34,14 +39,38 @@ public partial class App : Application
public static Window? Window { get => window; set => window = value; }
/// <summary>
/// Invoked when the application is launched normally by the end user.
/// Other entry points will be used such as when the application is launched to open a specific file.
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
[SuppressMessage("", "VSTHRD100")]
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
Window = Ioc.Default.GetRequiredService<MainWindow>();
Window.Activate();
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance mainInstance = AppInstance.FindOrRegisterForKey("main");
if (!mainInstance.IsCurrent)
{
// Redirect the activation (and args) to the "main" instance, and exit.
await mainInstance.RedirectActivationToAsync(activatedEventArgs);
Process.GetCurrentProcess().Kill();
}
else
{
Uri? uri = null;
if (activatedEventArgs.Kind == ExtendedActivationKind.Protocol)
{
IProtocolActivatedEventArgs protocolArgs = (activatedEventArgs.Data as IProtocolActivatedEventArgs)!;
uri = protocolArgs.Uri;
}
Window = Ioc.Default.GetRequiredService<MainWindow>();
Window.Activate();
if (uri != null)
{
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
}
}
}
private static void InitializeDependencyInjection()
@@ -56,7 +85,7 @@ public partial class App : Application
.AddInjections()
.AddDatebase()
.AddHttpClients()
.AddDefaultJsonSerializerOptions()
.AddJsonSerializerOptions()
// Discrete services
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
@@ -68,7 +97,6 @@ public partial class App : Application
private void AppUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
ILogger<App> logger = Ioc.Default.GetRequiredService<ILogger<App>>();
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
}
}

View File

@@ -35,7 +35,7 @@ public class AppDbContext : DbContext
/// </summary>
/// <param name="sqlConnectionString">连接字符串</param>
/// <returns>应用程序数据库上下文</returns>
public static AppDbContext CreateFrom(string sqlConnectionString)
public static AppDbContext Create(string sqlConnectionString)
{
return new(new DbContextOptionsBuilder<AppDbContext>().UseSqlite(sqlConnectionString).Options);
}

View File

@@ -22,6 +22,6 @@ public class AppDbContextDesignTimeFactory : IDesignTimeDbContextFactory<AppDbCo
string dbFile = myDocument.Locate("Userdata.db");
string sqlConnectionString = $"Data Source={dbFile}";
return AppDbContext.CreateFrom(sqlConnectionString);
return AppDbContext.Create(sqlConnectionString);
}
}

View File

@@ -23,6 +23,45 @@ internal abstract class FileSystemContext
this.location = location;
}
/// <summary>
/// 创建文件,若已存在文件,则不会创建
/// </summary>
/// <param name="file">文件</param>
public void CreateFileOrIgnore(string file)
{
file = Locate(file);
if (!File.Exists(file))
{
File.Create(file).Dispose();
}
}
/// <summary>
/// 创建文件夹,若已存在文件,则不会创建
/// </summary>
/// <param name="folder">文件夹</param>
public void CreateFolderOrIgnore(string folder)
{
folder = Locate(folder);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
}
/// <summary>
/// 尝试删除文件夹
/// </summary>
/// <param name="folder">文件夹</param>
public void DeleteFolderOrIgnore(string folder)
{
folder = Locate(folder);
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
}
/// <summary>
/// 检查根目录
/// </summary>
@@ -113,41 +152,22 @@ internal abstract class FileSystemContext
}
/// <summary>
/// 创建文件,若已存在文件,则不会创建
/// 等效于 <see cref="File.OpenRead(string)"/> ,但路径经过解析
/// </summary>
/// <param name="file">文件</param>
public void CreateFileOrIgnore(string file)
/// <param name="file">文件</param>
/// <returns>文件流</returns>
public FileStream OpenRead(string file)
{
file = Locate(file);
if (!File.Exists(file))
{
File.Create(file).Dispose();
}
return File.OpenRead(Locate(file));
}
/// <summary>
/// 创建文件夹,若已存在文件,则不会创建
/// 等效于 <see cref="File.Create(string)"/> ,但路径经过解析
/// </summary>
/// <param name="folder">文件</param>
public void CreateFolderOrIgnore(string folder)
/// <param name="file">文件</param>
/// <returns>文件流</returns>
public FileStream Create(string file)
{
folder = Locate(folder);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
}
/// <summary>
/// 尝试删除文件夹
/// </summary>
/// <param name="folder">文件夹</param>
public void DeleteFolderOrIgnore(string folder)
{
folder = Locate(folder);
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
return File.Create(Locate(file));
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Context.FileSystem.Location;
/// <summary>
/// 我的文档位置
/// </summary>
[Injection(InjectAs.Transient)]
public class Metadata : IFileSystemLocation
{
private string? path;
/// <inheritdoc/>
public string GetPath()
{
if (string.IsNullOrEmpty(path))
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
path = Path.GetFullPath(Path.Combine(myDocument, "Hutao", "Metadata"));
}
return path;
}
}

View File

@@ -24,4 +24,4 @@ public class MyDocument : IFileSystemLocation
return path;
}
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Context.FileSystem.Location;
namespace Snap.Hutao.Context.FileSystem;
/// <summary>
/// 元数据上下文
/// </summary>
[Injection(InjectAs.Transient)]
internal class MetadataContext : FileSystemContext
{
/// <inheritdoc cref="FileSystemContext"/>
public MetadataContext(Metadata metadata)
: base(metadata)
{
}
}

View File

@@ -17,9 +17,11 @@ public class CancellablePage : Page
/// <summary>
/// 初始化
/// </summary>
/// <param name="viewModel">视图模型</param>
public void Initialize(ISupportCancellation viewModel)
/// <typeparam name="TViewModel">视图模型类型</typeparam>
public void InitializeWith<TViewModel>()
where TViewModel : class, ISupportCancellation
{
ISupportCancellation viewModel = Ioc.Default.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewLoadingConcellationTokenSource.Token;
DataContext = viewModel;
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI;
using CommunityToolkit.WinUI.UI.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Extension;
namespace Snap.Hutao.Control.Image;
/// <summary>
/// 缓存图像
/// </summary>
public class CachedImage : ImageEx
{
static CachedImage()
{
ImageCache.Instance.CacheDuration = Timeout.InfiniteTimeSpan;
ImageCache.Instance.RetryCount = 3;
}
/// <summary>
/// 构造一个新的缓存图像
/// </summary>
public CachedImage()
{
IsCacheEnabled = true;
EnableLazyLoading = true;
}
/// <inheritdoc/>
protected override async Task<ImageSource> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{
BitmapImage image;
try
{
image = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, token);
}
catch
{
// maybe the image is corrupted remove it and re-download
await ImageCache.Instance.RemoveAsync(imageUri.Enumerate());
image = await ImageCache.Instance.GetFromCacheAsync(imageUri, false, token);
}
// check token state to determine whether the operation should be canceled.
if (token.IsCancellationRequested)
{
throw new TaskCanceledException("Image source has changed.");
}
else
{
return Must.NotNull(image);
}
}
}

View File

@@ -16,7 +16,7 @@ internal interface ISupportAsyncInitialization
/// <summary>
/// 异步初始化
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
/// <param name="token">取消令牌</param>
/// <returns>初始化任务</returns>
Task<bool> InitializeAsync(CancellationToken cancellationToken = default);
ValueTask<bool> InitializeAsync(CancellationToken token = default);
}

View File

@@ -5,6 +5,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation;
/// <summary>
/// 指示被标注的类型可注入
/// 由源生成器生成注入代码
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class InjectionAttribute : Attribute
@@ -15,27 +16,14 @@ public class InjectionAttribute : Attribute
/// <param name="injectAs">指示注入方法</param>
public InjectionAttribute(InjectAs injectAs)
{
InjectAs = injectAs;
}
/// <summary>
/// 指示该类将注入为带有接口实现的类
/// </summary>
/// <param name="injectAs">指示注入方法</param>
/// <param name="impl">实现的接口类型</param>
public InjectionAttribute(InjectAs injectAs, Type impl)
/// <param name="interfaceType">实现的接口类型</param>
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
{
InterfaceType = impl;
InjectAs = injectAs;
}
/// <summary>
/// 注入类型
/// </summary>
public InjectAs InjectAs { get; }
/// <summary>
/// 该类实现的接口类型
/// </summary>
public Type? InterfaceType { get; }
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Core.Json.Converter;

View File

@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Core.Json.Converter;

View File

@@ -0,0 +1,131 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Core.Json.Converter;
/// <summary>
/// Json字典转换器
/// </summary>
/// <typeparam name="TKeyConverter">键的类型</typeparam>
public class StringEnumKeyDictionaryConverter : JsonConverterFactory
{
/// <inheritdoc/>
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}
if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
{
return false;
}
return typeToConvert.GetGenericArguments()[0].IsEnum;
}
/// <inheritdoc/>
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
{
Type keyType = type.GetGenericArguments()[0];
Type valueType = type.GetGenericArguments()[1];
Type innerConverterType = typeof(StringEnumDictionaryConverterInner<,>).MakeGenericType(keyType, valueType);
JsonConverter converter = (JsonConverter)Activator.CreateInstance(innerConverterType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { options }, null)!;
return converter;
}
private class StringEnumDictionaryConverterInner<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
where TKey : struct, Enum
{
private readonly JsonConverter<TValue>? valueConverter;
private readonly Type keyType;
private readonly Type valueType;
public StringEnumDictionaryConverterInner(JsonSerializerOptions options)
{
valueConverter = (JsonConverter<TValue>)options.GetConverter(typeof(TValue));
// Cache the key and value types.
keyType = typeof(TKey);
valueType = typeof(TValue);
}
public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
Dictionary<TKey, TValue> dictionary = new();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}
// Get the key.
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string? propertyName = reader.GetString();
if (!Enum.TryParse(propertyName, out TKey key))
{
throw new JsonException($"Unable to convert \"{propertyName}\" to Enum \"{keyType}\".");
}
// Get the value.
TValue value;
if (valueConverter != null)
{
reader.Read();
value = valueConverter.Read(ref reader, valueType, options)!;
}
else
{
value = JsonSerializer.Deserialize<TValue>(ref reader, options)!;
}
// Add to dictionary.
dictionary.Add(key, value);
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, IDictionary<TKey, TValue> dictionary, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach ((TKey key, TValue value) in dictionary)
{
string? propertyName = key.ToString();
string? convertedName = options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName;
writer.WritePropertyName(convertedName);
if (valueConverter != null)
{
valueConverter.Write(writer, value, options);
}
else
{
JsonSerializer.Serialize(writer, value, options);
}
}
writer.WriteEndObject();
}
}
}

View File

@@ -19,7 +19,7 @@ internal static class Property<TOwner>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty Depend<TProperty>(string name)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new PropertyMetadata(null));
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(default(TProperty)));
}
/// <summary>
@@ -31,7 +31,7 @@ internal static class Property<TOwner>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty Depend<TProperty>(string name, TProperty defaultValue)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new PropertyMetadata(defaultValue));
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
@@ -44,7 +44,7 @@ internal static class Property<TOwner>
/// <returns>注册的依赖属性</returns>
public static DependencyProperty Depend<TProperty>(string name, TProperty defaultValue, PropertyChangedCallback callback)
{
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new PropertyMetadata(defaultValue, callback));
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue, callback));
}
/// <summary>
@@ -55,7 +55,7 @@ internal static class Property<TOwner>
/// <returns>注册的附加属性</returns>
public static DependencyProperty Attach<TProperty>(string name)
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new PropertyMetadata(null));
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(default(TProperty)));
}
/// <summary>
@@ -67,7 +67,7 @@ internal static class Property<TOwner>
/// <returns>注册的附加属性</returns>
public static DependencyProperty Attach<TProperty>(string name, TProperty defaultValue)
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new PropertyMetadata(defaultValue));
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
}
/// <summary>
@@ -79,6 +79,6 @@ internal static class Property<TOwner>
/// <returns>注册的附加属性</returns>
public static DependencyProperty Attach<TProperty>(string name, PropertyChangedCallback callback)
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new PropertyMetadata(callback));
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(callback));
}
}

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Core.Setting;
internal static class LocalSetting
{
/// <summary>
/// 由于 <see cref="Windows.Foundation.Collections.IPropertySet"/> 没有启用 nullable,
/// 由于 <see cref="Windows.Foundation.Collections.IPropertySet"/> 没有 nullable context,
/// 在处理引用类型时需要格外小心
/// 将值类型的操作与引用类型区分开,可以提升一定的性能
/// </summary>

View File

@@ -92,8 +92,9 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
{
if (asyncRelayCommand.ExecutionTask?.Exception is AggregateException exception)
{
logger.LogError(exception, "异步命令发生了错误");
Crashes.TrackError(exception);
Exception baseException = exception.GetBaseException();
logger.LogError(baseException, "异步命令发生了错误");
Crashes.TrackError(baseException);
}
}
}

View File

@@ -10,6 +10,7 @@ global using Snap.Hutao.Core.Validation;
global using System;
global using System.ComponentModel;
global using System.Diagnostics.CodeAnalysis;
global using System.Text.Json;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Windows.Input;
global using System.Windows.Input;

View File

@@ -7,7 +7,7 @@ using Snap.Hutao.Context.Database;
using Snap.Hutao.Context.FileSystem;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
namespace Snap.Hutao;
@@ -22,20 +22,20 @@ internal static class IocConfiguration
/// </summary>
/// <param name="services">集合</param>
/// <returns>可继续操作的集合</returns>
public static IServiceCollection AddDefaultJsonSerializerOptions(this IServiceCollection services)
public static IServiceCollection AddJsonSerializerOptions(this IServiceCollection services)
{
// default json options, global configuration
return services
.AddSingleton(new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
});
}
/// <summary>
/// 添加数据库
/// 添加专用数据库
/// </summary>
/// <param name="services">集合</param>
/// <returns>可继续操作的集合</returns>
@@ -48,13 +48,12 @@ internal static class IocConfiguration
string sqlConnectionString = $"Data Source={dbFile}";
// temporarily create a context
using (AppDbContext context = AppDbContext.CreateFrom(sqlConnectionString))
using (AppDbContext context = AppDbContext.Create(sqlConnectionString))
{
if (context.Database.GetPendingMigrations().Any())
{
Debug.WriteLine("Migrate started");
Debug.WriteLine("Performing Migrations");
context.Database.Migrate();
Debug.WriteLine("Migrate completed");
}
}

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license.
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Enka;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
@@ -25,10 +27,16 @@ internal static class IocHttpClientConfiguration
/// <returns>可继续操作的集合</returns>
public static IServiceCollection AddHttpClients(this IServiceCollection services)
{
// services
services.AddHttpClient<MetadataService>(DefaultConfiguration);
// normal clients
services.AddHttpClient<HutaoClient>(DefaultConfiguration);
services.AddHttpClient<AnnouncementClient>(DefaultConfiguration);
services.AddHttpClient<UserGameRoleClient>(DefaultConfiguration);
services.AddHttpClient<EnkaClient>(DefaultConfiguration);
// x-rpc clients
services.AddHttpClient<GameRecordClient>(XRpcConfiguration);
services.AddHttpClient<UserClient>(XRpcConfiguration);

View File

@@ -5,9 +5,10 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:view="using:Snap.Hutao.View"
mc:Ignorable="d">
mc:Ignorable="d"
Closed="MainWindowClosed">
<Grid>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="48.8"/>
<RowDefinition/>

View File

@@ -12,24 +12,25 @@ namespace Snap.Hutao;
[Injection(InjectAs.Singleton)]
public sealed partial class MainWindow : Window
{
private readonly AppDbContext appDbContext;
/// <summary>
/// 构造一个新的主窗体
/// </summary>
public MainWindow()
/// <param name="appDbContext">数据库上下文</param>
public MainWindow(AppDbContext appDbContext)
{
this.appDbContext = appDbContext;
InitializeComponent();
ExtendsContentIntoTitleBar = true;
SetTitleBar(TitleBarView.DragableArea);
Closed += MainWindowClosed;
}
private void MainWindowClosed(object sender, WindowEventArgs args)
{
// save datebase
AppDbContext appDbContext = Ioc.Default.GetRequiredService<AppDbContext>();
int changes = appDbContext.SaveChanges();
Verify.Operation(changes == 0, "存在可避免的未经处理的数据库更改");
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 装备类型
/// https://github.com/Grasscutters/Grasscutter/blob/development/src/main/java/emu/grasscutter/game/inventory/EquipType.java
/// </summary>
public enum EquipType
{
/// <summary>
/// 无
/// </summary>
EQUIP_NONE = 0,
/// <summary>
/// 花
/// </summary>
EQUIP_BRACER = 1,
/// <summary>
/// 羽毛
/// </summary>
EQUIP_NECKLACE = 2,
/// <summary>
/// 沙
/// </summary>
EQUIP_SHOES = 3,
/// <summary>
/// 杯
/// </summary>
EQUIP_RING = 4,
/// <summary>
/// 头
/// </summary>
EQUIP_DRESS = 5,
/// <summary>
/// 武器
/// </summary>
EQUIP_WEAPON = 6,
}

View File

@@ -0,0 +1,491 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 战斗属性
/// https://github.com/Grasscutters/Grasscutter/tree/development/src/main/java/emu/grasscutter/game/props/FightProperty.java
/// </summary>
public enum FightProperty
{
/// <summary>
/// 空
/// </summary>
FIGHT_PROP_NONE = 0,
/// <summary>
/// 基础生命值
/// </summary>
FIGHT_PROP_BASE_HP = 1,
/// <summary>
/// 生命值加成
/// </summary>
FIGHT_PROP_HP = 2,
/// <summary>
/// 生命值加成百分比
/// </summary>
FIGHT_PROP_HP_PERCENT = 3,
/// <summary>
/// 基础攻击力
/// </summary>
FIGHT_PROP_BASE_ATTACK = 4,
/// <summary>
/// 攻击力加成
/// </summary>
FIGHT_PROP_ATTACK = 5,
/// <summary>
/// 攻击力百分比
/// </summary>
FIGHT_PROP_ATTACK_PERCENT = 6,
/// <summary>
/// 基础防御力
/// </summary>
FIGHT_PROP_BASE_DEFENSE = 7,
/// <summary>
/// 防御力加成
/// </summary>
FIGHT_PROP_DEFENSE = 8,
/// <summary>
/// 防御力百分比
/// </summary>
FIGHT_PROP_DEFENSE_PERCENT = 9,
/// <summary>
/// 基础速度
/// </summary>
FIGHT_PROP_BASE_SPEED = 10,
/// <summary>
/// 速度加成
/// </summary>
FIGHT_PROP_SPEED_PERCENT = 11,
/// <summary>
///
/// </summary>
FIGHT_PROP_HP_MP_PERCENT = 12,
/// <summary>
///
/// </summary>
FIGHT_PROP_ATTACK_MP_PERCENT = 13,
/// <summary>
/// 暴击率
/// </summary>
FIGHT_PROP_CRITICAL = 20,
/// <summary>
/// 抗暴击率
/// </summary>
FIGHT_PROP_ANTI_CRITICAL = 21,
/// <summary>
/// 暴击伤害
/// </summary>
FIGHT_PROP_CRITICAL_HURT = 22,
/// <summary>
/// 元素充能效率
/// </summary>
FIGHT_PROP_CHARGE_EFFICIENCY = 23,
/// <summary>
/// 伤害加成
/// </summary>
FIGHT_PROP_ADD_HURT = 24,
/// <summary>
/// 抗性提升
/// </summary>
FIGHT_PROP_SUB_HURT = 25,
/// <summary>
/// 治疗提升
/// </summary>
FIGHT_PROP_HEAL_ADD = 26,
/// <summary>
/// 受治疗提升
/// </summary>
FIGHT_PROP_HEALED_ADD = 27,
/// <summary>
/// 元素精通
/// </summary>
FIGHT_PROP_ELEMENT_MASTERY = 28,
/// <summary>
/// 物理抗性提升
/// </summary>
FIGHT_PROP_PHYSICAL_SUB_HURT = 29,
/// <summary>
/// 物理伤害加成
/// </summary>
FIGHT_PROP_PHYSICAL_ADD_HURT = 30,
/// <summary>
/// 无视防御力百分比
/// </summary>
FIGHT_PROP_DEFENCE_IGNORE_RATIO = 31,
/// <summary>
/// 防御力降低
/// </summary>
FIGHT_PROP_DEFENCE_IGNORE_DELTA = 32,
/// <summary>
/// 火元素伤害加成
/// </summary>
FIGHT_PROP_FIRE_ADD_HURT = 40,
/// <summary>
/// 雷元素伤害加成
/// </summary>
FIGHT_PROP_ELEC_ADD_HURT = 41,
/// <summary>
/// 水元素伤害加成
/// </summary>
FIGHT_PROP_WATER_ADD_HURT = 42,
/// <summary>
/// 草元素伤害加成
/// </summary>
FIGHT_PROP_GRASS_ADD_HURT = 43,
/// <summary>
/// 风元素伤害加成
/// </summary>
FIGHT_PROP_WIND_ADD_HURT = 44,
/// <summary>
/// 岩元素伤害加成
/// </summary>
FIGHT_PROP_ROCK_ADD_HURT = 45,
/// <summary>
/// 冰元素伤害加成
/// </summary>
FIGHT_PROP_ICE_ADD_HURT = 46,
/// <summary>
/// 弱点伤害加成
/// </summary>
FIGHT_PROP_HIT_HEAD_ADD_HURT = 47,
/// <summary>
/// 火元素抗性提升
/// </summary>
FIGHT_PROP_FIRE_SUB_HURT = 50,
/// <summary>
/// 雷元素抗性提升
/// </summary>
FIGHT_PROP_ELEC_SUB_HURT = 51,
/// <summary>
/// 雷元素抗性提升
/// </summary>
FIGHT_PROP_WATER_SUB_HURT = 52,
/// <summary>
/// 草元素抗性提升
/// </summary>
FIGHT_PROP_GRASS_SUB_HURT = 53,
/// <summary>
/// 风元素抗性提升
/// </summary>
FIGHT_PROP_WIND_SUB_HURT = 54,
/// <summary>
/// 岩元素抗性提升
/// </summary>
FIGHT_PROP_ROCK_SUB_HURT = 55,
/// <summary>
/// 冰元素抗性提升
/// </summary>
FIGHT_PROP_ICE_SUB_HURT = 56,
/// <summary>
///
/// </summary>
FIGHT_PROP_EFFECT_HIT = 60,
/// <summary>
///
/// </summary>
FIGHT_PROP_EFFECT_RESIST = 61,
/// <summary>
/// 冻结抗性
/// </summary>
FIGHT_PROP_FREEZE_RESIST = 62,
/// <summary>
/// 迟缓抗性
/// </summary>
FIGHT_PROP_TORPOR_RESIST = 63,
/// <summary>
/// 眩晕抗性
/// </summary>
FIGHT_PROP_DIZZY_RESIST = 64,
/// <summary>
/// 冻结缩减
/// </summary>
FIGHT_PROP_FREEZE_SHORTEN = 65,
/// <summary>
/// 迟缓缩减
/// </summary>
FIGHT_PROP_TORPOR_SHORTEN = 66,
/// <summary>
/// 眩晕缩减
/// </summary>
FIGHT_PROP_DIZZY_SHORTEN = 67,
/// <summary>
/// 火元素爆发能量
/// </summary>
FIGHT_PROP_MAX_FIRE_ENERGY = 70,
/// <summary>
/// 雷元素爆发能量
/// </summary>
FIGHT_PROP_MAX_ELEC_ENERGY = 71,
/// <summary>
/// 水元素爆发能量
/// </summary>
FIGHT_PROP_MAX_WATER_ENERGY = 72,
/// <summary>
/// 草元素爆发能量
/// </summary>
FIGHT_PROP_MAX_GRASS_ENERGY = 73,
/// <summary>
/// 风元素爆发能量
/// </summary>
FIGHT_PROP_MAX_WIND_ENERGY = 74,
/// <summary>
/// 冰元素爆发能量
/// </summary>
FIGHT_PROP_MAX_ICE_ENERGY = 75,
/// <summary>
/// 岩元素爆发能量
/// </summary>
FIGHT_PROP_MAX_ROCK_ENERGY = 76,
/// <summary>
/// 技能冷却缩减
/// </summary>
FIGHT_PROP_SKILL_CD_MINUS_RATIO = 80,
/// <summary>
/// 护盾强效
/// </summary>
FIGHT_PROP_SHIELD_COST_MINUS_RATIO = 81,
/// <summary>
/// 火元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_FIRE_ENERGY = 1000,
/// <summary>
/// 雷元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_ELEC_ENERGY = 1001,
/// <summary>
/// 水元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_WATER_ENERGY = 1002,
/// <summary>
/// 草元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_GRASS_ENERGY = 1003,
/// <summary>
/// 风元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_WIND_ENERGY = 1004,
/// <summary>
/// 冰元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_ICE_ENERGY = 1005,
/// <summary>
/// 岩元素爆发当前能量
/// </summary>
FIGHT_PROP_CUR_ROCK_ENERGY = 1006,
/// <summary>
/// 当前生命值
/// </summary>
FIGHT_PROP_CUR_HP = 1010,
/// <summary>
/// 最大生命值
/// </summary>
FIGHT_PROP_MAX_HP = 2000,
/// <summary>
/// 当前攻击力
/// </summary>
FIGHT_PROP_CUR_ATTACK = 2001,
/// <summary>
/// 当前防御力
/// </summary>
FIGHT_PROP_CUR_DEFENSE = 2002,
/// <summary>
/// 当前速度
/// </summary>
FIGHT_PROP_CUR_SPEED = 2003,
/// <summary>
/// 总攻击力
/// </summary>
FIGHT_PROP_NONEXTRA_ATTACK = 3000,
/// <summary>
/// 总防御力
/// </summary>
FIGHT_PROP_NONEXTRA_DEFENSE = 3001,
/// <summary>
/// 总暴击率
/// </summary>
FIGHT_PROP_NONEXTRA_CRITICAL = 3002,
/// <summary>
/// 总抗暴击率
/// </summary>
FIGHT_PROP_NONEXTRA_ANTI_CRITICAL = 3003,
/// <summary>
/// 总暴击伤害
/// </summary>
FIGHT_PROP_NONEXTRA_CRITICAL_HURT = 3004,
/// <summary>
/// 总元素充能效率
/// </summary>
FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY = 3005,
/// <summary>
/// 元素精通
/// </summary>
FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY = 3006,
/// <summary>
/// 总物理抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT = 3007,
/// <summary>
/// 总火元素伤害提升
/// </summary>
FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT = 3008,
/// <summary>
/// 总雷元素伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT = 3009,
/// <summary>
/// 总水元素伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_WATER_ADD_HURT = 3010,
/// <summary>
/// 总草元素伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT = 3011,
/// <summary>
/// 总风元素伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_WIND_ADD_HURT = 3012,
/// <summary>
/// 总岩元素伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT = 3013,
/// <summary>
/// 总冰元素伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_ICE_ADD_HURT = 3014,
/// <summary>
/// 总火元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT = 3015,
/// <summary>
/// 总雷元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT = 3016,
/// <summary>
/// 总水元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_WATER_SUB_HURT = 3017,
/// <summary>
/// 总草元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT = 3018,
/// <summary>
/// 总风元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_WIND_SUB_HURT = 3019,
/// <summary>
/// 总岩元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT = 3020,
/// <summary>
/// 总冰元素抗性提升
/// </summary>
FIGHT_PROP_NONEXTRA_ICE_SUB_HURT = 3021,
/// <summary>
/// 总冷却缩减
/// </summary>
FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO = 3022,
/// <summary>
/// 总护盾强效
/// </summary>
FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO = 3023,
/// <summary>
/// 总物理伤害加成
/// </summary>
FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT = 3024,
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 稀有度

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 物品类型
/// https://github.com/Grasscutters/Grasscutter/tree/development/src/main/java/emu/grasscutter/game/inventory/ItemType.java
/// </summary>
public enum ItemType
{
/// <summary>
/// 无
/// </summary>
ITEM_NONE = 0,
/// <summary>
/// 贵重道具
/// </summary>
ITEM_VIRTUAL = 1,
/// <summary>
/// 材料
/// </summary>
ITEM_MATERIAL = 2,
/// <summary>
/// 圣遗物
/// </summary>
ITEM_RELIQUARY = 3,
/// <summary>
/// 武器
/// </summary>
ITEM_WEAPON = 4,
/// <summary>
/// 任务等
/// </summary>
ITEM_DISPLAY = 5,
/// <summary>
/// 家具
/// </summary>
ITEM_FURNITURE = 6,
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 武器类型

View File

@@ -0,0 +1,57 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>
/// 成就
/// </summary>
public class Achievement
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 分类Id
/// </summary>
public int Goal { get; set; }
/// <summary>
/// 排序顺序
/// </summary>
public int Order { get; set; }
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 完成奖励
/// </summary>
public Reward FinishReward { get; set; } = default!;
/// <summary>
/// 总进度
/// </summary>
public int Progress { get; set; }
/// <summary>
/// 触发器
/// </summary>
public IEnumerable<AchievementTrigger>? Triggers { get; set; }
/// <summary>
/// 图标
/// </summary>
public string? Icon { get; set; }
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>
/// 成就分类
/// </summary>
public class AchievementGoal
{
/// <summary>
/// 排序顺序
/// </summary>
public int Order { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 完成奖励
/// </summary>
public Reward? FinishReward { get; set; }
/// <summary>
/// 图标
/// </summary>
public string? Icon { get; set; }
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>
/// 成就触发器
/// </summary>
public class AchievementTrigger
{
/// <summary>
/// 触发器类型
/// </summary>
public AchievementTriggerType Type { get; set; }
/// <summary>
/// Id
/// </summary>
public string Id { get; set; } = default!;
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>
/// 成就触发器类型
/// </summary>
public enum AchievementTriggerType
{
/// <summary>
/// 任务
/// </summary>
Quest = 1,
/// <summary>
/// 子任务
/// </summary>
SubQuest = 2,
/// <summary>
/// 日常任务
/// </summary>
DailyTask = 3,
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>
/// 奖励
/// </summary>
public class Reward
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 数量
/// </summary>
public int Count { get; set; }
}

View File

@@ -0,0 +1,83 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 角色
/// </summary>
public class Avatar
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 排序号
/// </summary>
public int Sort { get; set; }
/// <summary>
/// 体型
/// </summary>
public string Body { get; set; } = default!;
/// <summary>
/// 正面图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 侧面图标
/// </summary>
public string SideIcon { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 角色加入游戏时间
/// </summary>
public DateTimeOffset BeginTime { get; set; }
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; set; }
/// <summary>
/// 武器类型
/// </summary>
public WeaponType Weapon { get; set; }
/// <summary>
/// 属性
/// </summary>
public PropertyInfo Property { get; set; } = default!;
/// <summary>
/// 技能
/// </summary>
public SkillDepot SkillDepot { get; set; } = default!;
/// <summary>
/// 好感信息
/// </summary>
public FetterInfo FetterInfo { get; set; } = default!;
/// <summary>
/// 皮肤
/// </summary>
public IEnumerable<Costume> Costumes { get; set; } = default!;
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 皮肤
/// </summary>
public class Costume
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 是否为默认
/// </summary>
public bool IsDefault { get; set; }
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 描述与参数
/// </summary>
public class DescParam
{
/// <summary>
/// 描述
/// </summary>
public IEnumerable<string> Descriptions { get; set; } = default!;
/// <summary>
/// 参数
/// </summary>
public IEnumerable<LevelParam<int>> Parameters { get; set; } = default!;
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 好感故事
/// </summary>
public class Fetter
{
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; } = default!;
/// <summary>
/// 上下文
/// </summary>
public string Context { get; set; } = default!;
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 好感信息
/// </summary>
public class FetterInfo
{
/// <summary>
/// 称号
/// </summary>
public string Title { get; set; } = default!;
/// <summary>
/// 详细
/// </summary>
public string Detail { get; set; } = default!;
/// <summary>
/// 地区
/// </summary>
public string Association { get; set; } = default!;
/// <summary>
/// 属于组织
/// </summary>
public string Native { get; set; } = default!;
/// <summary>
/// 生月
/// </summary>
public int BirthMonth { get; set; }
/// <summary>
/// 生日
/// </summary>
public int BirthDay { get; set; }
/// <summary>
/// 神之眼属性-前
/// </summary>
public string VisionBefore { get; set; } = default!;
/// <summary>
/// 神之眼属性-后
/// </summary>
public string VisionAfter { get; set; } = default!;
/// <summary>
/// 命座-前
/// </summary>
public string ConstellationBefore { get; set; } = default!;
/// <summary>
/// 命座-后
/// </summary>
public string ConstellationAfter { get; set; } = default!;
/// <summary>
/// 中文CV
/// </summary>
public string CvChinese { get; set; } = default!;
/// <summary>
/// 日语CV
/// </summary>
public string CvJapanese { get; set; } = default!;
/// <summary>
/// 英语CV
/// </summary>
public string CvEnglish { get; set; } = default!;
/// <summary>
/// 韩语CV
/// </summary>
public string CvKorean { get; set; } = default!;
/// <summary>
/// 好感语音
/// </summary>
public IEnumerable<Fetter> Fetters { get; set; } = default!;
/// <summary>
/// 好感故事
/// </summary>
public IEnumerable<Fetter> FetterStories { get; set; } = default!;
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 技能信息
/// </summary>
public class ProudableSkill : SkillBase
{
/// <summary>
/// 组Id
/// </summary>
public int GroupId { get; set; }
/// <summary>
/// 提升属性
/// </summary>
public DescParam Proud { get; set; } = default!;
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 技能基础
/// 命座
/// </summary>
public class SkillBase
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 技能组
/// </summary>
public class SkillDepot
{
/// <summary>
/// 技能天赋
/// </summary>
public IEnumerable<ProudableSkill> Skills { get; set; } = default!;
/// <summary>
/// 大招
/// </summary>
public ProudableSkill EnergySkill { get; set; } = default!;
/// <summary>
/// 固有天赋
/// </summary>
public IEnumerable<ProudableSkill> Inherents { get; set; } = default!;
/// <summary>
/// 命之座
/// </summary>
public IEnumerable<SkillBase> Talents { get; set; } = default!;
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata;
/// <summary>
/// 等级与参数
/// </summary>
/// <typeparam name="TLevel">等级的类型</typeparam>
public class LevelParam<TLevel>
{
/// <summary>
/// 等级
/// </summary>
public TLevel Level { get; set; } = default!;
/// <summary>
/// 参数
/// </summary>
public IEnumerable<double> Parameters { get; set; } = default!;
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata;
/// <summary>
/// 属性信息
/// </summary>
public class PropertyInfo
{
/// <summary>
/// 提升的属性
/// </summary>
public IEnumerable<FightProperty> Properties { get; set; } = default!;
/// <summary>
/// 参数
/// </summary>
public IEnumerable<LevelParam<string>> Parameters { get; set; } = default!;
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Reliquary;
/// <summary>
/// 圣遗物信息
/// </summary>
public class Reliquary
{
/// <summary>
/// 表示同种类的Id
/// </summary>
public IEnumerable<int> Ids { get; set; } = default!;
/// <summary>
/// 允许出现的等级
/// </summary>
public IEnumerable<ItemQuality> RankLevels { get; set; } = default!;
/// <summary>
/// 套装Id
/// </summary>
public int SetId { get; set; }
/// <summary>
/// 装备类型
/// </summary>
public EquipType EquipType { get; set; } = default!;
/// <summary>
/// 物品类型
/// </summary>
public ItemType ItemType { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Reliquary;
/// <summary>
/// 圣遗物突破属性
/// </summary>
public class ReliquaryAffix : ReliquaryAffixBase
{
/// <summary>
/// 值
/// </summary>
public double Value { get; set; }
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Reliquary;
/// <summary>
/// 圣遗物主属性
/// </summary>
public class ReliquaryAffixBase
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 战斗属性
/// </summary>
public FightProperty Type { get; set; }
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Model.Metadata.Reliquary;
/// <summary>
/// 圣遗物套装
/// </summary>
public class ReliquarySet
{
/// <summary>
/// 套装Id
/// </summary>
public int SetId { get; set; } = default!;
/// <summary>
/// 需要的数量
/// </summary>
public IEnumerable<int> NeedNumber { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public IEnumerable<string> Descriptions { get; set; } = default!;
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 武器
/// </summary>
public class Weapon
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 武器类型
/// </summary>
public WeaponType WeaponType { get; set; }
/// <summary>
/// 等级
/// </summary>
public ItemQuality RankLevel { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 觉醒图标
/// </summary>
public string AwakenIcon { get; set; } = default!;
/// <summary>
/// 属性
/// </summary>
public PropertyInfo Property { get; set; } = default!;
}

View File

@@ -9,7 +9,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.0.4.0" />
Version="1.0.6.0" />
<Properties>
<DisplayName>胡桃</DisplayName>
@@ -32,13 +32,20 @@
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="胡桃"
Description="Snap Genshin 但是 WinUI3"
Description="A Snap Hutao Software"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="hutao">
<uap:DisplayName>胡桃</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>
</Applications>

View File

@@ -20,8 +20,8 @@ public static class Program
private static void Main(string[] args)
{
XamlCheckProcessRequirements();
ComWrappersSupport.InitializeComWrappers();
Application.Start(p =>
{
DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread());

View File

@@ -45,7 +45,7 @@ public interface IInfoBarService
/// </summary>
/// <param name="message">消息</param>
/// <param name="delay">关闭延迟</param>
void Information(string message, int delay = 3000);
void Information(string message, int delay = 5000);
/// <summary>
/// 显示提示信息
@@ -53,7 +53,7 @@ public interface IInfoBarService
/// <param name="title">标题</param>
/// <param name="message">消息</param>
/// <param name="delay">关闭延迟</param>
void Information(string title, string message, int delay = 3000);
void Information(string title, string message, int delay = 5000);
/// <summary>
/// 使用指定的 <see cref="StackPanel"/> 初始化服务
@@ -66,7 +66,7 @@ public interface IInfoBarService
/// </summary>
/// <param name="message">消息</param>
/// <param name="delay">关闭延迟</param>
void Success(string message, int delay = 3000);
void Success(string message, int delay = 5000);
/// <summary>
/// 显示成功信息
@@ -74,7 +74,7 @@ public interface IInfoBarService
/// <param name="title">标题</param>
/// <param name="message">消息</param>
/// <param name="delay">关闭延迟</param>
void Success(string title, string message, int delay = 3000);
void Success(string title, string message, int delay = 5000);
/// <summary>
/// 显示警告信息

View File

@@ -0,0 +1,79 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Weapon;
using System.Collections.Generic;
namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 元数据服务
/// </summary>
internal interface IMetadataService
{
/// <summary>
/// 异步初始化服务,尝试更新元数据
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>初始化是否成功</returns>
ValueTask<bool> InitializeAsync(CancellationToken token = default);
/// <summary>
/// 异步获取成就列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>成就列表</returns>
ValueTask<IEnumerable<Achievement>> GetAchievementsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取成就分类列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>成就分类列表</returns>
ValueTask<IEnumerable<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取角色列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
ValueTask<IEnumerable<Avatar>> GetAvatarsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物列表</returns>
ValueTask<IEnumerable<Reliquary>> GetReliquariesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物强化属性列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物强化属性列表</returns>
ValueTask<IEnumerable<ReliquaryAffix>> GetReliquaryAffixesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物主属性强化属性列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物强化属性列表</returns>
ValueTask<IEnumerable<ReliquaryAffixBase>> GetReliquaryMainAffixesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取武器列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>武器列表</returns>
ValueTask<IEnumerable<Weapon>> GetWeaponsAsync(CancellationToken token = default);
/// <summary>
/// 异步更新元数据
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>更新是否完成</returns>
Task<bool> UpdateMetadataAsync(CancellationToken token = default);
}

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Service;
[Injection(InjectAs.Transient, typeof(IAnnouncementService))]
internal class AnnouncementService : IAnnouncementService
{
private const string CacheKey = $"{nameof(IAnnouncementService)}.Cache.{nameof(AnnouncementWrapper)}";
private const string CacheKey = $"{nameof(AnnouncementService)}.Cache.{nameof(AnnouncementWrapper)}";
private readonly AnnouncementClient announcementClient;
private readonly IMemoryCache memoryCache;

View File

@@ -19,25 +19,25 @@ internal class InfoBarService : IInfoBarService
}
/// <inheritdoc/>
public void Information(string message, int delay = 3000)
public void Information(string message, int delay = 5000)
{
PrepareInfoBarAndShow(InfoBarSeverity.Informational, null, message, delay);
}
/// <inheritdoc/>
public void Information(string title, string message, int delay = 3000)
public void Information(string title, string message, int delay = 5000)
{
PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay);
}
/// <inheritdoc/>
public void Success(string message, int delay = 3000)
public void Success(string message, int delay = 5000)
{
PrepareInfoBarAndShow(InfoBarSeverity.Success, null, message, delay);
}
/// <inheritdoc/>
public void Success(string title, string message, int delay = 3000)
public void Success(string title, string message, int delay = 5000)
{
PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay);
}

View File

@@ -0,0 +1,249 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Context.FileSystem;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Service.Abstraction;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Cryptography;
namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 元数据服务
/// </summary>
[Injection(InjectAs.Singleton, typeof(IMetadataService))]
internal class MetadataService : IMetadataService, ISupportAsyncInitialization
{
private const string MetaAPIHost = "http://hutao-metadata.snapgenshin.com";
private const string MetaFileName = "Meta.json";
private readonly IInfoBarService infoBarService;
private readonly HttpClient httpClient;
private readonly FileSystemContext metadataContext;
private readonly JsonSerializerOptions options;
private readonly ILogger<MetadataService> logger;
private readonly IMemoryCache memoryCache;
private bool isInitialized = false;
/// <summary>
/// 构造一个新的元数据服务
/// </summary>
/// <param name="infoBarService">信息条服务</param>
/// <param name="httpClient">http客户端</param>
/// <param name="metadataContext">我的文档上下文</param>
/// <param name="options">json序列化选项</param>
/// <param name="logger">日志器</param>
/// <param name="memoryCache">内存缓存</param>
public MetadataService(
IInfoBarService infoBarService,
HttpClient httpClient,
MetadataContext metadataContext,
JsonSerializerOptions options,
ILogger<MetadataService> logger,
IMemoryCache memoryCache)
{
this.infoBarService = infoBarService;
this.httpClient = httpClient;
this.metadataContext = metadataContext;
this.options = options;
this.logger = logger;
this.memoryCache = memoryCache;
}
/// <inheritdoc/>
public bool IsInitialized { get => isInitialized; private set => isInitialized = value; }
/// <inheritdoc/>
public async ValueTask<bool> InitializeAsync(CancellationToken token = default)
{
if (IsInitialized)
{
return true;
}
metadataContext.EnsureDirectory();
if (metadataContext.FileExists(MetaFileName))
{
IDictionary<string, string>? metaMd5Map;
using (Stream metaFile = metadataContext.OpenRead(MetaFileName))
{
metaMd5Map = await JsonSerializer
.DeserializeAsync<IDictionary<string, string>>(metaFile, options, token)
.ConfigureAwait(false);
}
await CheckMetadataAsync(Must.NotNull(metaMd5Map!), token)
.ConfigureAwait(false);
IsInitialized = true;
}
else
{
IsInitialized = await UpdateMetadataAsync(token)
.ConfigureAwait(false);
}
return IsInitialized;
}
/// <inheritdoc/>
public async Task<bool> UpdateMetadataAsync(CancellationToken token = default)
{
IDictionary<string, string>? metaMd5Map = await httpClient
.GetFromJsonAsync<IDictionary<string, string>>($"{MetaAPIHost}/{MetaFileName}", options, token)
.ConfigureAwait(false);
if (metaMd5Map is null)
{
infoBarService.Error("元数据校验文件解析失败");
return false;
}
await CheckMetadataAsync(metaMd5Map, token).ConfigureAwait(false);
using (FileStream metaFileStream = metadataContext.Create(MetaFileName))
{
await JsonSerializer
.SerializeAsync(metaFileStream, metaMd5Map, options, token)
.ConfigureAwait(false);
}
return true;
}
/// <inheritdoc/>
public ValueTask<IEnumerable<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<AchievementGoal>>("AchievementGoal", token);
}
/// <inheritdoc/>
public ValueTask<IEnumerable<Achievement>> GetAchievementsAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<Achievement>>("Achievement", token);
}
/// <inheritdoc/>
public ValueTask<IEnumerable<Avatar>> GetAvatarsAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<Avatar>>("Avatar", token);
}
/// <inheritdoc/>
public ValueTask<IEnumerable<Reliquary>> GetReliquariesAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<Reliquary>>("Reliquary", token);
}
/// <inheritdoc/>
public ValueTask<IEnumerable<ReliquaryAffix>> GetReliquaryAffixesAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<ReliquaryAffix>>("ReliquaryAffix", token);
}
/// <inheritdoc/>
public ValueTask<IEnumerable<ReliquaryAffixBase>> GetReliquaryMainAffixesAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<ReliquaryAffixBase>>("ReliquaryMainAffix", token);
}
/// <inheritdoc/>
public ValueTask<IEnumerable<Weapon>> GetWeaponsAsync(CancellationToken token = default)
{
return GetMetadataAsync<IEnumerable<Weapon>>("Weapon", token);
}
private async ValueTask<T> GetMetadataAsync<T>(string fileName, CancellationToken token)
where T : class
{
Verify.Operation(IsInitialized, "元数据服务尚未初始化,或初始化失败");
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
return Must.NotNull((value as T)!);
}
T? result = await JsonSerializer
.DeserializeAsync<T>(metadataContext.OpenRead($"{fileName}.json"), options, token)
.ConfigureAwait(false);
return memoryCache.Set(cacheKey, Must.NotNull(result!));
}
private async Task<string> GetFileMd5Async(string fileFullName, CancellationToken token)
{
using (FileStream stream = metadataContext.OpenRead(fileFullName))
{
byte[] bytes = await MD5.Create()
.ComputeHashAsync(stream, token)
.ConfigureAwait(false);
return Convert.ToHexString(bytes);
}
}
/// <summary>
/// 检查元数据的Md5值是否匹配
/// 如果不匹配则尝试下载
/// </summary>
/// <param name="metaMd5Map">元数据校验表</param>
/// <param name="token">取消令牌</param>
/// <returns>令牌</returns>
private async Task CheckMetadataAsync(IDictionary<string, string> metaMd5Map, CancellationToken token)
{
// enumerate files and compare md5
foreach ((string fileName, string md5) in metaMd5Map)
{
string fileFullName = $"{fileName}.json";
bool skip = false;
if (metadataContext.FileExists(fileFullName))
{
skip = md5 == await GetFileMd5Async(fileFullName, token)
.ConfigureAwait(false);
}
if (!skip)
{
logger.LogInformation("{file} 文件 MD5 不匹配", fileFullName);
await DownloadMetadataAsync(fileFullName, token)
.ConfigureAwait(false);
}
}
}
private async Task DownloadMetadataAsync(string fileFullName, CancellationToken token)
{
Stream sourceStream = await httpClient
.GetStreamAsync($"{MetaAPIHost}/{fileFullName}", token)
.ConfigureAwait(false);
// Write stream while convert LF to CRLF
using (StreamReader streamReader = new(sourceStream))
{
using (StreamWriter streamWriter = new(metadataContext.Create(fileFullName)))
{
while (await streamReader.ReadLineAsync().ConfigureAwait(false) is string line)
{
await (streamReader.EndOfStream
? streamWriter.WriteAsync(line) // Don't append the last line
: streamWriter.WriteLineAsync(line))
.ConfigureAwait(false);
}
}
}
logger.LogInformation("{file} 下载完成", fileFullName);
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction.Navigation;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 表示导航等待器

View File

@@ -1,8 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction.Navigation;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 为 <see cref="NavigationExtra"/> 提供抽象接口
/// </summary>
public interface INavigationExtra
{
/// <summary>

View File

@@ -3,7 +3,7 @@
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Service.Abstraction.Navigation;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 导航服务

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction.Navigation;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 导航额外信息

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction.Navigation;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 导航结果

View File

@@ -4,12 +4,11 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Abstraction.Navigation;
using Snap.Hutao.View.Helper;
using Snap.Hutao.View.Page;
using System.Linq;
namespace Snap.Hutao.Service;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 导航服务

View File

@@ -5,8 +5,8 @@
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>Snap.Hutao</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<ImplicitUsings>false</ImplicitUsings>
@@ -31,9 +31,11 @@
<None Remove="stylecop.json" />
<None Remove="View\Dialog\UserDialog.xaml" />
<None Remove="View\MainView.xaml" />
<None Remove="View\Page\AchievementPage.xaml" />
<None Remove="View\Page\AnnouncementContentPage.xaml" />
<None Remove="View\Page\AnnouncementPage.xaml" />
<None Remove="View\Page\SettingPage.xaml" />
<None Remove="View\Page\WelcomePage.xaml" />
<None Remove="View\TitleView.xaml" />
<None Remove="View\UserView.xaml" />
</ItemGroup>
@@ -130,6 +132,16 @@
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Page\AchievementPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Page\WelcomePage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\UserDialog.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -28,6 +28,12 @@
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="成就" helper:NavHelper.NavigateTo="page:AchievementPage">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xE7C4;"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
<NavigationView.PaneFooter>

View File

@@ -3,7 +3,8 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Abstraction.Navigation;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Page;
namespace Snap.Hutao.View;
@@ -27,6 +28,6 @@ public sealed partial class MainView : UserControl
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
navigationService.Initialize(NavView, ContentFrame);
navigationService.Navigate<Page.AnnouncementPage>(INavigationAwaiter.Default, true);
navigationService.Navigate<WelcomePage>(INavigationAwaiter.Default, false);
}
}

View File

@@ -0,0 +1,35 @@
<shcc:CancellablePage
x:Class="Snap.Hutao.View.Page.AchievementPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Snap.Hutao.View.Page"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
xmlns:settings="using:SettingsUI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="Loaded">
<mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
<Grid>
<ScrollViewer>
<ItemsControl
Margin="12,0,16,12"
ItemsSource="{Binding AchievementsView}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<settings:Setting
Margin="0,12,0,0"
Header="{Binding Title}"
Description="{Binding Description}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</shcc:CancellablePage>

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control.Cancellable;
using Snap.Hutao.ViewModel;
namespace Snap.Hutao.View.Page;
/// <summary>
/// 成就页面
/// </summary>
public sealed partial class AchievementPage : CancellablePage
{
/// <summary>
/// 构造一个新的成就页面
/// </summary>
public AchievementPage()
{
InitializeWith<AchievementViewModel>();
InitializeComponent();
}
}

View File

@@ -4,7 +4,8 @@
using Microsoft.UI.Xaml.Navigation;
using Microsoft.VisualStudio.Threading;
using Snap.Hutao.Core;
using Snap.Hutao.Service.Abstraction.Navigation;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Navigation;
namespace Snap.Hutao.View.Page;
@@ -38,7 +39,7 @@ openInWebview: function(url){ location.href = url }}";
if (e.Parameter is INavigationExtra extra)
{
targetContent = extra.Data as string;
LoadAnnouncementAsync(extra).Forget();
LoadAnnouncementAsync(extra).SafeForget();
}
}
@@ -60,4 +61,4 @@ openInWebview: function(url){ location.href = url }}";
WebView.NavigateToString(targetContent);
extra.NotifyNavigationCompleted();
}
}
}

View File

@@ -43,9 +43,9 @@
Margin="0,0,0,12"
Style="{StaticResource TitleTextBlockStyle}"/>
<cwucont:AdaptiveGridView
cwua:ItemsReorderAnimation.Duration="0:0:0.06"
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
SelectionMode="None"
DesiredWidth="280"
DesiredWidth="300"
HorizontalAlignment="Stretch"
ItemsSource="{Binding List}"
Margin="0,0,2,0">

View File

@@ -16,7 +16,7 @@ public sealed partial class AnnouncementPage : CancellablePage
/// </summary>
public AnnouncementPage()
{
Initialize(Ioc.Default.GetRequiredService<AnnouncementViewModel>());
InitializeWith<AnnouncementViewModel>();
InitializeComponent();
}
}

View File

@@ -0,0 +1,16 @@
<Page
x:Class="Snap.Hutao.View.Page.WelcomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Snap.Hutao.View.Page"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Padding="12">
<TextBlock
Text="欢迎使用胡桃"
Style="{StaticResource TitleTextBlockStyle}"/>
</Grid>
</Page>

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.View.Page;
/// <summary>
/// 欢迎页
/// </summary>
public sealed partial class WelcomePage : Microsoft.UI.Xaml.Controls.Page
{
/// <summary>
/// 构造一个新的欢迎页
/// </summary>
public WelcomePage()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI.UI;
using Microsoft.VisualStudio.Threading;
using Snap.Hutao.Control.Cancellable;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Service.Metadata;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.ViewModel;
/// <summary>
/// 成就视图模型
/// </summary>
[Injection(InjectAs.Transient)]
internal class AchievementViewModel : ObservableObject, ISupportCancellation
{
private readonly IMetadataService metadataService;
private AdvancedCollectionView? achievementsView;
/// <summary>
/// 构造一个新的成就视图模型
/// </summary>
/// <param name="metadataService">元数据服务</param>
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
public AchievementViewModel(IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
{
this.metadataService = metadataService;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
}
/// <inheritdoc/>
public CancellationToken CancellationToken { get; set; }
/// <summary>
/// 成就视图
/// </summary>
public AdvancedCollectionView? AchievementsView
{
get => achievementsView;
set => SetProperty(ref achievementsView, value);
}
/// <summary>
/// 打开页面命令
/// </summary>
public ICommand OpenUICommand { get; }
private async Task OpenUIAsync(CancellationToken token)
{
using (CancellationTokenExtensions.CombinedCancellationToken combined = token.CombineWith(CancellationToken))
{
if (await metadataService.InitializeAsync(combined.Token))
{
IEnumerable<Achievement> achievements = await metadataService.GetAchievementsAsync(combined.Token);
// TODO
AchievementsView = new(achievements.ToList());
}
}
}
}

View File

@@ -8,7 +8,8 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Abstraction.Navigation;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Page;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
namespace Snap.Hutao.ViewModel;
@@ -99,7 +100,7 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
if (WebView2Helper.IsSupported)
{
navigationService.Navigate<View.Page.AnnouncementContentPage>(data: new NavigationExtra(content));
navigationService.Navigate<AnnouncementContentPage>(data: new NavigationExtra(content));
}
else
{

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Enka.Model;
using Snap.Hutao.Web.Hoyolab;
using System.Net.Http;
using System.Net.Http.Json;
namespace Snap.Hutao.Web.Enka;
/// <summary>
/// Enka API 客户端
/// </summary>
[Injection(InjectAs.Transient)]
internal class EnkaClient
{
private const string EnkaAPI = "https://enka.shinshin.moe/u/{0}/__data.json";
private readonly HttpClient httpClient;
/// <summary>
/// 构造一个新的 Enka API 客户端
/// </summary>
/// <param name="httpClient">http客户端</param>
public EnkaClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
/// <summary>
/// 异步获取 Enka API 响应
/// </summary>
/// <param name="playerUid">玩家Uid</param>
/// <param name="token">取消令牌</param>
/// <returns>Enka API 响应</returns>
public Task<EnkaResponse?> GetDataAsync(PlayerUid playerUid, CancellationToken token)
{
return httpClient.GetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPI, playerUid.Value), token);
}
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Converter;
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 角色信息
/// </summary>
public class AvatarInfo
{
/// <summary>
/// 角色Id
/// Character ID
/// </summary>
[JsonPropertyName("avatarId")]
public int AvatarId { get; set; }
/// <summary>
/// 基础属性
/// Character Info Properties List
/// <see cref="PlayerProperty.PROP_EXP"/>
/// </summary>
[JsonPropertyName("propMap")]
[JsonConverter(typeof(StringEnumKeyDictionaryConverter))]
public IDictionary<PlayerProperty, TypeValue> PropMap { get; set; } = default!;
/// <summary>
/// 命座 Id
/// </summary>
[JsonPropertyName("talentIdList")]
public IList<int> TalentIdList { get; set; } = default!;
/// <summary>
/// 属性Map
/// Map of Character's Combat Properties.
/// </summary>
[JsonPropertyName("fightPropMap")]
[JsonConverter(typeof(StringEnumKeyDictionaryConverter))]
public IDictionary<FightProperty, double> FightPropMap { get; set; } = default!;
/// <summary>
/// 技能组Id
/// Character Skill Set ID
/// </summary>
[JsonPropertyName("skillDepotId")]
public int SkillDepotId { get; set; }
/// <summary>
/// List of Unlocked Skill Ids
/// 被动天赋
/// </summary>
[JsonPropertyName("inherentProudSkillList")]
public IList<int> InherentProudSkillList { get; set; } = default!;
/// <summary>
/// Map of Skill Levels
/// </summary>
[JsonPropertyName("skillLevelMap")]
public IDictionary<string, int> SkillLevelMap { get; set; } = default!;
/// <summary>
/// 装备列表
/// 最后一个为武器
/// List of Equipments: Weapon, Ariftacts
/// </summary>
[JsonPropertyName("equipList")]
public IList<Equip> EquipList { get; set; } = default!;
/// <summary>
/// 好感度信息
/// Character Friendship Level
/// </summary>
[JsonPropertyName("fetterInfo")]
public FetterInfo FetterInfo { get; set; } = default!;
/// <summary>
/// 皮肤 Id
/// </summary>
[JsonPropertyName("costumeId")]
public int? CostumeId { get; set; }
/// <summary>
/// 命座额外技能等级
/// </summary>
[JsonPropertyName("proudSkillExtraLevelMap")]
public IDictionary<string, int> ProudSkillExtraLevelMap { get; set; } = default!;
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// Enka API 响应
/// </summary>
public class EnkaResponse
{
/// <summary>
/// 玩家基础信息
/// </summary>
[JsonPropertyName("playerInfo")]
public PlayerInfo? PlayerInfo { get; set; } = default!;
/// <summary>
/// 展示的角色详细信息列表
/// </summary>
[JsonPropertyName("avatarInfoList")]
public IList<AvatarInfo>? AvatarInfoList { get; set; } = default!;
/// <summary>
/// 刷新剩余秒数
/// 生存时间值
/// </summary>
[JsonPropertyName("ttl")]
public int? Ttl { get; set; }
/// <summary>
/// 此响应是否有效
/// </summary>
public bool IsValid
{
get => Ttl.HasValue;
}
/// <summary>
/// 是否包含角色详细数据
/// </summary>
public bool HasDetail
{
get => AvatarInfoList != null;
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 装备
/// </summary>
public class Equip
{
/// <summary>
/// 物品Id
/// Equipment ID
/// </summary>
[JsonPropertyName("itemId")]
public int ItemId { get; set; }
/// <summary>
/// 圣遗物
/// Artifact Base Info
/// </summary>
[JsonPropertyName("reliquary")]
public Reliquary? Reliquary { get; set; }
/// <summary>
/// 武器
/// Weapon Base Info
/// </summary>
[JsonPropertyName("weapon")]
public Weapon? Weapon { get; set; }
/// <summary>
/// Detailed Info of Equipment
/// </summary>
[JsonPropertyName("flat")]
public Flat Flat { get; set; } = default!;
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 好感度信息
/// </summary>
public class FetterInfo
{
/// <summary>
/// 好感度等级
/// </summary>
[JsonPropertyName("expLevel")]
public int ExpLevel { get; set; }
}

View File

@@ -0,0 +1,84 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 平展值
/// </summary>
public class Flat
{
/// <summary>
/// 名称
/// Hash for Equipment Name
/// </summary>
[JsonPropertyName("nameTextMapHash")]
public string NameTextMapHash { get; set; } = default!;
/// <summary>
/// 套装名称
/// Hash for Artifact Set Name
/// </summary>
[JsonPropertyName("setNameTextMapHash")]
public string? SetNameTextMapHash { get; set; }
/// <summary>
/// 等级
/// Rarity Level of Equipment
/// </summary>
[JsonPropertyName("rankLevel")]
public int RankLevel { get; set; }
/// <summary>
/// 圣遗物主属性
/// Artifact Main Stat
/// </summary>
[JsonPropertyName("reliquaryMainstat")]
public ReliquaryMainstat? ReliquaryMainstat { get; set; }
/// <summary>
/// 圣遗物副属性
/// List of Artifact Substats
/// </summary>
[JsonPropertyName("reliquarySubstats")]
public IList<ReliquarySubstat>? ReliquarySubstats { get; set; }
/// <summary>
/// 物品类型
/// Equipment Type: Weapon or Artifact
/// ITEM_WEAPON
/// ITEM_RELIQUARY
/// </summary>
[JsonPropertyName("itemType")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public ItemType ItemType { get; set; } = default!;
/// <summary>
/// 图标
/// Equipment Icon Name
/// </summary>
[JsonPropertyName("icon")]
public string Icon { get; set; } = default!;
/// <summary>
/// 圣遗物类型
/// 当为武器时
/// 值为 <see cref="EquipType.EQUIP_NONE"/>
/// </summary>
[JsonPropertyName("equipType")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public EquipType EquipType { get; set; }
/// <summary>
/// 武器主副属性
/// 0 基础攻击力
/// 1 主属性
/// List of Weapon Stat: Base ATK, Substat
/// </summary>
[JsonPropertyName("weaponStats")]
public IList<WeaponStat>? WeaponStats { get; set; }
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 玩家信息
/// </summary>
public class PlayerInfo
{
/// <summary>
/// 昵称
/// Player Nickname
/// </summary>
[JsonPropertyName("nickname")]
public string Nickname { get; set; } = default!;
/// <summary>
/// 等级
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 签名
/// Profile Signature
/// </summary>
[JsonPropertyName("signature")]
public string Signature { get; set; } = default!;
/// <summary>
/// 世界等级
/// Player World Level
/// </summary>
[JsonPropertyName("worldLevel")]
public int WorldLevel { get; set; }
/// <summary>
/// 名片的Id
/// Profile Namecard ID
/// </summary>
[JsonPropertyName("nameCardId")]
public int NameCardId { get; set; }
/// <summary>
/// 完成的成就个数
/// Number of Completed Achievements
/// </summary>
[JsonPropertyName("finishAchievementNum")]
public int FinishAchievementNum { get; set; }
/// <summary>
/// 深渊层数
/// Abyss Floor
/// </summary>
[JsonPropertyName("towerFloorIndex")]
public int TowerFloorIndex { get; set; }
/// <summary>
/// 深渊间数
/// Abyss Floor's Level
/// </summary>
[JsonPropertyName("towerLevelIndex")]
public int TowerLevelIndex { get; set; }
/// <summary>
/// 展示的角色信息
/// List of Character IDs and Levels
/// </summary>
[JsonPropertyName("showAvatarInfoList")]
public IList<ShowAvatarInfo> ShowAvatarInfoList { get; set; } = default!;
/// <summary>
/// 展示的名片信息
/// List of Namecard IDs
/// </summary>
[JsonPropertyName("showNameCardIdList")]
public IList<int> ShowNameCardIdList { get; set; } = default!;
/// <summary>
/// 头像信息
/// Character ID of Profile Picture
/// </summary>
[JsonPropertyName("profilePicture")]
public ProfilePicture ProfilePicture { get; set; } = default!;
}

View File

@@ -0,0 +1,228 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 玩家属性
/// https://github.com/Grasscutters/Grasscutter/tree/development/src/main/java/emu/grasscutter/game/props/PlayerProperty.java
/// </summary>
public enum PlayerProperty
{
/// <summary>
/// 空
/// </summary>
PROP_NONE = 0,
/// <summary>
/// 经验值
/// </summary>
PROP_EXP = 1001,
/// <summary>
/// 突破等级
/// </summary>
PROP_BREAK_LEVEL = 1002,
/// <summary>
/// 饱食度
/// </summary>
PROP_SATIATION_VAL = 1003,
/// <summary>
/// 饱食度冷却
/// </summary>
PROP_SATIATION_PENALTY_TIME = 1004,
/// <summary>
/// 等级
/// </summary>
PROP_LEVEL = 4001,
/// <summary>
/// 上次切换角色的时间
/// </summary>
PROP_LAST_CHANGE_AVATAR_TIME = 10001,
/// <summary>
/// Maximum volume of the Statue of the Seven for the player [0, 8500000]
/// </summary>
PROP_MAX_SPRING_VOLUME = 10002,
/// <summary>
/// Current volume of the Statue of the Seven [0, PROP_MAX_SPRING_VOLUME]
/// </summary>
PROP_CUR_SPRING_VOLUME = 10003,
/// <summary>
/// Auto HP recovery when approaching the Statue of the Seven [0, 1]
/// </summary>
PROP_IS_SPRING_AUTO_USE = 10004,
/// <summary>
/// Auto HP recovery percentage [0, 100]
/// </summary>
PROP_SPRING_AUTO_USE_PERCENT = 10005,
/// <summary>
/// Are you in a state that disables your flying ability? e.g. new player [0, 1]
/// </summary>
PROP_IS_FLYABLE = 10006,
/// <summary>
/// 天气是否锁定
/// </summary>
PROP_IS_WEATHER_LOCKED = 10007,
/// <summary>
/// 游戏时间是否锁定
/// </summary>
PROP_IS_GAME_TIME_LOCKED = 10008,
/// <summary>
///
/// </summary>
PROP_IS_TRANSFERABLE = 10009,
/// <summary>
/// Maximum stamina of the player =0 - 24000
/// </summary>
PROP_MAX_STAMINA = 10010,
/// <summary>
/// Used stamina of the player =0 - PROP_MAX_STAMINA
/// </summary>
PROP_CUR_PERSIST_STAMINA = 10011,
/// <summary>
/// 临时体力,食物?
/// </summary>
PROP_CUR_TEMPORARY_STAMINA = 10012,
/// <summary>
/// 玩家等级
/// </summary>
PROP_PLAYER_LEVEL = 10013,
/// <summary>
/// 玩家经验
/// </summary>
PROP_PLAYER_EXP = 10014,
/// <summary>
/// Primogem =-inf, +inf
/// It is known that Mihoyo will make Primogem negative in the cases that a player spends
/// his gems and then got a money refund, so negative is allowed.
/// </summary>
PROP_PLAYER_HCOIN = 10015,
/// <summary>
/// Mora [0, +inf
/// </summary>
PROP_PLAYER_SCOIN = 10016,
/// <summary>
/// Do you allow other players to join your game? [0=no 1=direct 2=approval]
/// </summary>
PROP_PLAYER_MP_SETTING_TYPE = 10017,
/// <summary>
/// 0 if in quest or something that disables MP [0, 1]
/// </summary>
PROP_IS_MP_MODE_AVAILABLE = 10018,
/// <summary>
/// [0, 8]
/// </summary>
PROP_PLAYER_WORLD_LEVEL = 10019,
/// <summary>
/// Original Resin [0, 2000] - note that values above 160 require refills
/// </summary>
PROP_PLAYER_RESIN = 10020,
/// <summary>
///
/// </summary>
PROP_PLAYER_WAIT_SUB_HCOIN = 10022,
/// <summary>
///
/// </summary>
PROP_PLAYER_WAIT_SUB_SCOIN = 10023,
/// <summary>
/// Is only MP with PlayStation players? [0, 1]
/// </summary>
PROP_IS_ONLY_MP_WITH_PS_PLAYER = 10024,
/// <summary>
/// Genesis Crystal =-inf, +inf see 10015
/// </summary>
PROP_PLAYER_MCOIN = 10025,
/// <summary>
///
/// </summary>
PROP_PLAYER_WAIT_SUB_MCOIN = 10026,
/// <summary>
///
/// </summary>
PROP_PLAYER_LEGENDARY_KEY = 10027,
/// <summary>
///
/// </summary>
PROP_IS_HAS_FIRST_SHARE = 10028,
/// <summary>
///
/// </summary>
PROP_PLAYER_FORGE_POINT = 10029,
/// <summary>
///
/// </summary>
PROP_CUR_CLIMATE_METER = 10035,
/// <summary>
///
/// </summary>
PROP_CUR_CLIMATE_TYPE = 10036,
/// <summary>
///
/// </summary>
PROP_CUR_CLIMATE_AREA_ID = 10037,
/// <summary>
///
/// </summary>
PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE = 10038,
/// <summary>
///
/// </summary>
PROP_PLAYER_WORLD_LEVEL_LIMIT = 10039,
/// <summary>
///
/// </summary>
PROP_PLAYER_WORLD_LEVEL_ADJUST_CD = 10040,
/// <summary>
///
/// </summary>
PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM = 10041,
/// <summary>
/// Realm currency [0, +inf
/// </summary>
PROP_PLAYER_HOME_COIN = 10042,
/// <summary>
///
/// </summary>
PROP_PLAYER_WAIT_SUB_HOME_COIN = 10043,
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 档案头像
/// </summary>
public class ProfilePicture
{
/// <summary>
/// 使用的角色Id
/// </summary>
[JsonPropertyName("avatarId")]
public int AvatarId { get; set; }
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 圣遗物
/// </summary>
public class Reliquary
{
/// <summary>
/// 等级 +20 = 21
/// [1,21]
/// Artifact Level [1-21]
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 主属性Id
/// Artifact Main Stat ID
/// </summary>
[JsonPropertyName("mainPropId")]
public int MainPropId { get; set; }
/// <summary>
/// 强化属性Id
/// </summary>
[JsonPropertyName("appendPropIdList")]
public IList<int> AppendPropIdList { get; set; } = default!;
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 圣遗物主属性
/// </summary>
public class ReliquaryMainstat
{
/// <summary>
/// Equipment Append Property Name.
/// </summary>
[JsonPropertyName("mainPropId")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public FightProperty MainPropId { get; set; }
/// <summary>
/// Property Value
/// </summary>
[JsonPropertyName("statValue")]
public double StatValue { get; set; }
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 圣遗物副属性
/// </summary>
public class ReliquarySubstat
{
/// <summary>
/// 增加属性
/// </summary>
[JsonPropertyName("appendPropId")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public FightProperty AppendPropId { get; set; }
/// <summary>
/// 值
/// </summary>
[JsonPropertyName("statValue")]
public double StatValue { get; set; }
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 角色列表信息
/// </summary>
public class ShowAvatarInfo
{
/// <summary>
/// 角色Id
/// Character ID
/// </summary>
[JsonPropertyName("avatarId")]
public int AvatarId { get; set; }
/// <summary>
/// 角色等级
/// Character Level
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 可能的皮肤Id
/// </summary>
[JsonPropertyName("costumeId")]
public int? CostumeId { get; set; }
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 类型与值
/// </summary>
public class TypeValue
{
/// <summary>
/// 类型
/// </summary>
[JsonPropertyName("type")]
public PlayerProperty Type { get; set; }
/// <summary>
/// 值
/// </summary>
[JsonPropertyName("val")]
public string? Value { get; set; }
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 武器信息
/// </summary>
public class Weapon
{
/// <summary>
/// 等级
/// Weapon Level
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 突破等级
/// Weapon Ascension Level
/// </summary>
[JsonPropertyName("promoteLevel")]
public int PromoteLevel { get; set; }
/// <summary>
/// 精炼 相较于实际等级 -1
/// Weapon Refinement Level [0-4]
/// </summary>
[JsonPropertyName("affixMap")]
public IDictionary<string, int> AffixMap { get; set; } = default!;
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Enka.Model;
/// <summary>
/// 武器属性
/// </summary>
public class WeaponStat
{
/// <summary>
/// 提升属性Id
/// </summary>
[JsonPropertyName("appendPropId")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public FightProperty AppendPropId { get; set; }
/// <summary>
/// 值
/// </summary>
[JsonPropertyName("statValue")]
public double StatValue { get; set; }
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Hoyolab.Bbs.User;

View File

@@ -5,7 +5,6 @@ using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Response;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
namespace Snap.Hutao.Web.Hoyolab.Bbs.User;

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Hoyolab.Bbs.User;

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Core.Convertion;
using System.Linq;
using System.Text.Json;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Extension;
using System.Net.Http;
using System.Text.Json;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;

View File

@@ -6,8 +6,6 @@ using Snap.Hutao.Web.Response;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
@@ -52,8 +50,7 @@ internal class AnnouncementClient
/// <returns>公告内容列表</returns>
public async Task<List<AnnouncementContent>> GetAnnouncementContentsAsync(CancellationToken cancellationToken = default)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Response<ListWrapper<AnnouncementContent>>? resp = await httpClient
.GetFromJsonAsync<Response<ListWrapper<AnnouncementContent>>>(ApiEndpoints.AnnContent, jsonSerializerOptions, cancellationToken)
.ConfigureAwait(false);

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi;
namespace Snap.Hutao.Web.Hoyolab;
/// <summary>
/// 玩家 Uid

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;

Some files were not shown because too many files have changed in this diff Show More