mapping abstraction

This commit is contained in:
DismissedLight
2023-07-18 16:54:42 +08:00
parent e9ee31a604
commit cd066e1462
78 changed files with 381 additions and 321 deletions

View File

@@ -74,7 +74,7 @@ public sealed partial class App : Application
private void LogDiagnosticInformation() private void LogDiagnosticInformation()
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
logger.LogInformation("FamilyName: {name}", hutaoOptions.FamilyName); logger.LogInformation("FamilyName: {name}", hutaoOptions.FamilyName);
logger.LogInformation("Version: {version}", hutaoOptions.Version); logger.LogInformation("Version: {version}", hutaoOptions.Version);

View File

@@ -26,7 +26,7 @@ namespace Snap.Hutao.Control.Image;
internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Control internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{ {
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged); private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new(); private readonly ConcurrentCancellationTokenSource loadingTokenSource = new();
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
@@ -92,7 +92,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg) private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{ {
CompositionImage image = (CompositionImage)sender; CompositionImage image = (CompositionImage)sender;
CancellationToken token = LoadingTokenSource.Register(image); CancellationToken token = image.loadingTokenSource.Register();
IServiceProvider serviceProvider = image.serviceProvider; IServiceProvider serviceProvider = image.serviceProvider;
ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>(); ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>();

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics.Contracts;
namespace Snap.Hutao.Core.Abstraction;
internal interface IMappingFrom<TSelf, TFrom>
where TSelf : IMappingFrom<TSelf, TFrom>
{
[Pure]
static abstract TSelf From(TFrom source);
}
internal interface IMappingFrom<TSelf, T1, T2>
where TSelf : IMappingFrom<TSelf, T1, T2>
{
[Pure]
static abstract TSelf From(T1 t1, T2 t2);
}

View File

@@ -221,7 +221,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
return cacheFolder!; return cacheFolder!;
} }
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache; baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName)); DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
cacheFolder = info.FullName; cacheFolder = info.FullName;

View File

@@ -37,7 +37,7 @@ internal static class IocConfiguration
private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder) private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder)
{ {
HutaoOptions hutaoOptions = provider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = provider.GetRequiredService<RuntimeOptions>();
string dbFile = System.IO.Path.Combine(hutaoOptions.DataFolder, "Userdata.db"); string dbFile = System.IO.Path.Combine(hutaoOptions.DataFolder, "Userdata.db");
string sqlConnectionString = $"Data Source={dbFile}"; string sqlConnectionString = $"Data Source={dbFile}";

View File

@@ -29,7 +29,7 @@ internal static partial class IocHttpClientConfiguration
/// <param name="client">配置后的客户端</param> /// <param name="client">配置后的客户端</param>
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client) private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
client.Timeout = Timeout.InfiniteTimeSpan; client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent); client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);

View File

@@ -40,6 +40,8 @@ internal static class AppInstanceExtension
ReadOnlySpan<HANDLE> handles = new(redirectEventHandle); ReadOnlySpan<HANDLE> handles = new(redirectEventHandle);
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _); CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
// TODO: Release handle
} }
private static void RunAction(object? state) private static void RunAction(object? state)

View File

@@ -8,30 +8,31 @@ using Snap.Hutao.Core.Setting;
using System.IO; using System.IO;
using System.Security.Principal; using System.Security.Principal;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.Foundation.Metadata;
using Windows.Storage; using Windows.Storage;
namespace Snap.Hutao.Core; namespace Snap.Hutao.Core;
/// <summary> /// <summary>
/// 胡桃选项
/// 存储环境相关的选项 /// 存储环境相关的选项
/// 运行时运算得到的选项,无数据库交互 /// 运行时运算得到的选项,无数据库交互
/// </summary> /// </summary>
[Injection(InjectAs.Singleton)] [Injection(InjectAs.Singleton)]
internal sealed class HutaoOptions : IOptions<HutaoOptions> internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
{ {
private readonly ILogger<HutaoOptions> logger; private readonly ILogger<RuntimeOptions> logger;
private readonly bool isWebView2Supported; private readonly bool isWebView2Supported;
private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected; private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected;
private bool? isElevated; private bool? isElevated;
private bool? isWindows11;
/// <summary> /// <summary>
/// 构造一个新的胡桃选项 /// 构造一个新的胡桃选项
/// </summary> /// </summary>
/// <param name="logger">日志器</param> /// <param name="logger">日志器</param>
public HutaoOptions(ILogger<HutaoOptions> logger) public RuntimeOptions(ILogger<RuntimeOptions> logger)
{ {
this.logger = logger; this.logger = logger;
@@ -97,8 +98,14 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
/// </summary> /// </summary>
public bool IsElevated { get => isElevated ??= GetElevated(); } public bool IsElevated { get => isElevated ??= GetElevated(); }
/// <summary>
/// 系统版本是否大于等于 Windows 11
/// %programfiles(x86)%\Windows Kits\10\Platforms\UAP\10.0.22000.0\PreviousPlatforms.xml
/// </summary>
public bool Windows11OrHigher { get => isWindows11 ?? ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 14); }
/// <inheritdoc/> /// <inheritdoc/>
public HutaoOptions Value { get => this; } public RuntimeOptions Value { get => this; }
private static string GetDataFolderPath() private static string GetDataFolderPath()
{ {

View File

@@ -13,12 +13,14 @@ internal sealed class FeatureOptions : IReadOnlyCollection<Feature>
/// <summary> /// <summary>
/// 启用实时便笺无感验证 /// 启用实时便笺无感验证
/// </summary> /// </summary>
public Feature IsDailyNoteSilentVerificationEnabled { get; } = new("IsDailyNoteSilentVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSilentVerificationEnabled", true); public Feature IsDailyNoteSilentVerificationEnabled { get; } = new(
"IsDailyNoteSilentVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSilentVerificationEnabled", true);
/// <summary> /// <summary>
/// 元数据检查是否忽略 /// 元数据检查是否忽略
/// </summary> /// </summary>
public Feature IsMetadataUpdateCheckSuppressed { get; } = new("IsMetadataUpdateCheckSuppressed", "禁用元数据更新检查", "IsMetadataUpdateCheckSuppressed", false); public Feature IsMetadataUpdateCheckSuppressed { get; } = new(
"IsMetadataUpdateCheckSuppressed", "禁用元数据更新检查", "IsMetadataUpdateCheckSuppressed", false);
/// <inheritdoc/> /// <inheritdoc/>
public int Count { get => 2; } public int Count { get => 2; }

View File

@@ -49,35 +49,4 @@ internal static class SettingKeys
/// 1.7.0 版本指引状态 /// 1.7.0 版本指引状态
/// </summary> /// </summary>
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState"; public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
#region StaticResource
/// <summary>
/// 静态资源合约
/// 新增合约时 请注意
/// <see cref="StaticResource.FulfillAllContracts"/>
/// 与 <see cref="StaticResource.IsAnyUnfulfilledContractPresent"/>
/// </summary>
public const string StaticResourceV1Contract = "StaticResourceV1Contract";
/// <summary>
/// 静态资源合约V2 成就图标与物品图标
/// </summary>
public const string StaticResourceV2Contract = "StaticResourceV2Contract";
/// <summary>
/// 静态资源合约V3 刷新 Skill Talent
/// </summary>
public const string StaticResourceV3Contract = "StaticResourceV3Contract";
/// <summary>
/// 静态资源合约V4 刷新 AvatarIcon
/// </summary>
public const string StaticResourceV4Contract = "StaticResourceV4Contract";
/// <summary>
/// 静态资源合约V5 刷新 AvatarIcon
/// </summary>
public const string StaticResourceV5Contract = "StaticResourceV5Contract";
#endregion
} }

View File

@@ -9,6 +9,31 @@ namespace Snap.Hutao.Core.Setting;
[HighQuality] [HighQuality]
internal static class StaticResource internal static class StaticResource
{ {
/// <summary>
/// 静态资源合约
/// </summary>
public const string V1Contract = "StaticResourceV1Contract";
/// <summary>
/// 静态资源合约V2 成就图标与物品图标
/// </summary>
public const string V2Contract = "StaticResourceV2Contract";
/// <summary>
/// 静态资源合约V3 刷新 Skill Talent
/// </summary>
public const string V3Contract = "StaticResourceV3Contract";
/// <summary>
/// 静态资源合约V4 刷新 AvatarIcon
/// </summary>
public const string V4Contract = "StaticResourceV4Contract";
/// <summary>
/// 静态资源合约V5 刷新 AvatarIcon
/// </summary>
public const string V5Contract = "StaticResourceV5Contract";
/// <summary> /// <summary>
/// 完成所有合约 /// 完成所有合约
/// </summary> /// </summary>
@@ -41,18 +66,19 @@ internal static class StaticResource
/// <returns>静态资源合约尚未完成</returns> /// <returns>静态资源合约尚未完成</returns>
public static bool IsAnyUnfulfilledContractPresent() public static bool IsAnyUnfulfilledContractPresent()
{ {
return !LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false) return !LocalSetting.Get(V1Contract, false)
|| (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false)) || (!LocalSetting.Get(V2Contract, false))
|| (!LocalSetting.Get(SettingKeys.StaticResourceV3Contract, false)) || (!LocalSetting.Get(V3Contract, false))
|| (!LocalSetting.Get(SettingKeys.StaticResourceV4Contract, false)); || (!LocalSetting.Get(V4Contract, false))
|| (!LocalSetting.Get(V5Contract, false));
} }
private static void SetContractsState(bool state) private static void SetContractsState(bool state)
{ {
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, state); LocalSetting.Set(V1Contract, state);
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, state); LocalSetting.Set(V2Contract, state);
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, state); LocalSetting.Set(V3Contract, state);
LocalSetting.Set(SettingKeys.StaticResourceV4Contract, state); LocalSetting.Set(V4Contract, state);
LocalSetting.Set(SettingKeys.StaticResourceV5Contract, state); LocalSetting.Set(V5Contract, state);
} }
} }

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using System.Collections.Concurrent;
namespace Snap.Hutao.Core.Threading; namespace Snap.Hutao.Core.Threading;
/// <summary> /// <summary>
@@ -24,31 +22,4 @@ internal class ConcurrentCancellationTokenSource
source = new(); source = new();
return source.Token; return source.Token;
} }
}
/// <summary>
/// 有区分项的并发<see cref="CancellationTokenSource"/>
/// </summary>
/// <typeparam name="TItem">项类型</typeparam>
[HighQuality]
[SuppressMessage("", "SA1402")]
internal class ConcurrentCancellationTokenSource<TItem>
where TItem : notnull
{
private readonly ConcurrentDictionary<TItem, CancellationTokenSource> waitingItems = new();
/// <summary>
/// 为某个项注册取消令牌
/// </summary>
/// <param name="item">区分项</param>
/// <returns>取消令牌</returns>
public CancellationToken Register(TItem item)
{
if (waitingItems.TryRemove(item, out CancellationTokenSource? previousSource))
{
previousSource.Cancel();
}
return waitingItems.GetOrAdd(item, new CancellationTokenSource()).Token;
}
} }

View File

@@ -1,6 +1,8 @@
namespace Snap.Hutao.Core.Threading; // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
[SuppressMessage("", "SA1201")]
internal readonly struct SemaphoreSlimToken : IDisposable internal readonly struct SemaphoreSlimToken : IDisposable
{ {
private readonly SemaphoreSlim semaphoreSlim; private readonly SemaphoreSlim semaphoreSlim;

View File

@@ -38,7 +38,7 @@ internal sealed class TaskContext : ITaskContext
/// <inheritdoc/> /// <inheritdoc/>
public void InvokeOnMainThread(Action action) public void InvokeOnMainThread(Action action)
{ {
if (dispatcherQueue!.HasThreadAccess) if (dispatcherQueue.HasThreadAccess)
{ {
action(); action();
} }

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
namespace Snap.Hutao.Core.Threading; namespace Snap.Hutao.Core.Threading;
/// <summary> /// <summary>
@@ -30,7 +32,7 @@ internal static class TaskExtension
{ {
if (System.Diagnostics.Debugger.IsAttached) if (System.Diagnostics.Debugger.IsAttached)
{ {
_ = ex; System.Diagnostics.Debug.WriteLine(ExceptionFormat.Format(ex));
System.Diagnostics.Debugger.Break(); System.Diagnostics.Debugger.Break();
} }
} }
@@ -58,7 +60,7 @@ internal static class TaskExtension
} }
catch (Exception e) catch (Exception e)
{ {
logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), e.GetBaseException()); logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), ExceptionFormat.Format(e.GetBaseException()));
} }
} }
@@ -80,7 +82,7 @@ internal static class TaskExtension
} }
catch (Exception e) catch (Exception e)
{ {
logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), e.GetBaseException()); logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), ExceptionFormat.Format(e.GetBaseException()));
onException?.Invoke(e); onException?.Invoke(e);
} }
} }
@@ -104,7 +106,7 @@ internal static class TaskExtension
} }
catch (Exception e) catch (Exception e)
{ {
logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), e.GetBaseException()); logger?.LogError(e, "{Caller}:\r\n{Exception}", nameof(SafeForget), ExceptionFormat.Format(e.GetBaseException()));
onException?.Invoke(e); onException?.Invoke(e);
} }
} }

View File

@@ -52,19 +52,4 @@ internal static class Must
{ {
throw new NotSupportedException(context); throw new NotSupportedException(context);
} }
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> if the specified parameter's value is null.
/// </summary>
/// <typeparam name="T">The type of the parameter.</typeparam>
/// <param name="value">The value of the argument.</param>
/// <param name="parameterName">The name of the parameter to include in any thrown exception.</param>
/// <returns>The value of the parameter.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is <c>null</c>.</exception>
[MethodImpl(MethodImplOptions.NoInlining)]
public static T NotNull<T>([NotNull] T value, [CallerArgumentExpression(nameof(value))] string? parameterName = null)
where T : class // ensures value-types aren't passed to a null checking method
{
return value ?? throw new ArgumentNullException(parameterName);
}
} }

View File

@@ -60,7 +60,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
private void InitializeWindow() private void InitializeWindow()
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
WindowOptions options = window.WindowOptions; WindowOptions options = window.WindowOptions;
window.AppWindow.Title = string.Format(SH.AppNameAndVersion, hutaoOptions.Version); window.AppWindow.Title = string.Format(SH.AppNameAndVersion, hutaoOptions.Version);
@@ -73,7 +73,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
// appWindow.Show(true); // appWindow.Show(true);
// appWindow.Show can't bring window to top. // appWindow.Show can't bring window to top.
window.Activate(); window.Activate();
Persistence.BringToForeground(options.Hwnd); options.BringToForeground();
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>(); AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
UpdateSystemBackdrop(appOptions.BackdropType); UpdateSystemBackdrop(appOptions.BackdropType);
@@ -182,7 +182,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
else else
{ {
WindowOptions options = window.WindowOptions; WindowOptions options = window.WindowOptions;
double scale = Persistence.GetScaleForWindowHandle(options.Hwnd); double scale = options.GetWindowScale();
// 48 is the navigation button leftInset // 48 is the navigation button leftInset
RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale); RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale);

View File

@@ -30,7 +30,7 @@ internal static class Persistence
WindowOptions options = window.WindowOptions; WindowOptions options = window.WindowOptions;
// Set first launch size // Set first launch size
double scale = GetScaleForWindowHandle(options.Hwnd); double scale = options.GetWindowScale();
SizeInt32 transformedSize = options.InitSize.Scale(scale); SizeInt32 transformedSize = options.InitSize.Scale(scale);
RectInt32 rect = StructMarshal.RectInt32(transformedSize); RectInt32 rect = StructMarshal.RectInt32(transformedSize);
@@ -65,40 +65,6 @@ internal static class Persistence
} }
} }
/// <summary>
/// 获取窗体当前的DPI缩放比
/// </summary>
/// <param name="hwnd">窗体句柄</param>
/// <returns>缩放比</returns>
public static double GetScaleForWindowHandle(in HWND hwnd)
{
uint dpi = GetDpiForWindow(hwnd);
return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero);
}
/// <summary>
/// 将窗口设为前台窗口
/// </summary>
/// <param name="hwnd">窗口句柄</param>
public static unsafe void BringToForeground(in HWND hwnd)
{
HWND fgHwnd = GetForegroundWindow();
uint threadIdHwnd = GetWindowThreadProcessId(hwnd);
uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd);
if (threadIdHwnd != threadIdFgHwnd)
{
AttachThreadInput(threadIdHwnd, threadIdFgHwnd, true);
SetForegroundWindow(hwnd);
AttachThreadInput(threadIdHwnd, threadIdFgHwnd, false);
}
else
{
SetForegroundWindow(hwnd);
}
}
private static void TransformToCenterScreen(ref RectInt32 rect) private static void TransformToCenterScreen(ref RectInt32 rect)
{ {
DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Primary); DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Primary);

View File

@@ -6,6 +6,7 @@ using Microsoft.UI.Xaml;
using Windows.Graphics; using Windows.Graphics;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
using WinRT.Interop; using WinRT.Interop;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Core.Windowing; namespace Snap.Hutao.Core.Windowing;
@@ -53,4 +54,37 @@ internal readonly struct WindowOptions
InitSize = initSize; InitSize = initSize;
PersistSize = persistSize; PersistSize = persistSize;
} }
/// <summary>
/// 获取窗体当前的DPI缩放比
/// </summary>
/// <returns>缩放比</returns>
public double GetWindowScale()
{
uint dpi = GetDpiForWindow(Hwnd);
return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero);
}
/// <summary>
/// 将窗口设为前台窗口
/// </summary>
/// <param name="hwnd">窗口句柄</param>
public unsafe void BringToForeground()
{
HWND fgHwnd = GetForegroundWindow();
uint threadIdHwnd = GetWindowThreadProcessId(Hwnd);
uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd);
if (threadIdHwnd != threadIdFgHwnd)
{
AttachThreadInput(threadIdHwnd, threadIdFgHwnd, true);
SetForegroundWindow(Hwnd);
AttachThreadInput(threadIdHwnd, threadIdFgHwnd, false);
}
else
{
SetForegroundWindow(Hwnd);
}
}
} }

View File

@@ -93,7 +93,8 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
{ {
case WM_GETMINMAXINFO: case WM_GETMINMAXINFO:
{ {
double scalingFactor = Persistence.GetScaleForWindowHandle(hwnd); uint dpi = GetDpiForWindow(hwnd);
double scalingFactor = Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero);
window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor); window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
break; break;
} }

View File

@@ -66,7 +66,7 @@ internal static partial class EnumerableExtension
} }
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/> /// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>
public static Dictionary<TKey, TSource> ToDictionaryOverride<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) public static Dictionary<TKey, TSource> ToDictionaryIgnoringDuplicateKeys<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
where TKey : notnull where TKey : notnull
{ {
Dictionary<TKey, TSource> dictionary = new(); Dictionary<TKey, TSource> dictionary = new();
@@ -80,14 +80,14 @@ internal static partial class EnumerableExtension
} }
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey, TElement}(IEnumerable{TSource}, Func{TSource, TKey}, Func{TSource, TElement})"/> /// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey, TElement}(IEnumerable{TSource}, Func{TSource, TKey}, Func{TSource, TElement})"/>
public static Dictionary<TKey, TValue> ToDictionaryOverride<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> valueSelector) public static Dictionary<TKey, TValue> ToDictionaryIgnoringDuplicateKeys<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> elementSelector)
where TKey : notnull where TKey : notnull
{ {
Dictionary<TKey, TValue> dictionary = new(); Dictionary<TKey, TValue> dictionary = new();
foreach (TSource value in source) foreach (TSource value in source)
{ {
dictionary[keySelector(value)] = valueSelector(value); dictionary[keySelector(value)] = elementSelector(value);
} }
return dictionary; return dictionary;

View File

@@ -31,6 +31,7 @@ internal static partial class EnumerableExtension
/// <typeparam name="TSource">源的类型</typeparam> /// <typeparam name="TSource">源的类型</typeparam>
/// <param name="source">源</param> /// <param name="source">源</param>
/// <returns>集合</returns> /// <returns>集合</returns>
[Obsolete("Use C# 12 Collection Literal instead")]
public static IEnumerable<TSource> Enumerate<TSource>(this TSource source) public static IEnumerable<TSource> Enumerate<TSource>(this TSource source)
{ {
yield return source; yield return source;

View File

@@ -16,13 +16,12 @@ internal static class ObjectExtension
/// <typeparam name="T">数据类型</typeparam> /// <typeparam name="T">数据类型</typeparam>
/// <param name="source">源</param> /// <param name="source">源</param>
/// <returns>数组</returns> /// <returns>数组</returns>
[Obsolete("Use C# 12 Collection Literals")] [Obsolete("Use C# 12 Collection Literals when we migrate")]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] ToArray<T>(this T source) public static T[] ToArray<T>(this T source)
{ {
// TODO: use C# 12 collection literals // TODO: use C# 12 collection literals
// [ source ] // [ source ]
// and mark this as Obsolete
return new[] { source }; return new[] { source };
} }
} }

View File

@@ -17,7 +17,7 @@ internal interface IContentDialogFactory
/// <param name="title">标题</param> /// <param name="title">标题</param>
/// <param name="content">内容</param> /// <param name="content">内容</param>
/// <returns>结果</returns> /// <returns>结果</returns>
ValueTask<ContentDialogResult> ConfirmAsync(string title, string content); ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content);
/// <summary> /// <summary>
/// 异步确认或取消 /// 异步确认或取消
@@ -26,7 +26,7 @@ internal interface IContentDialogFactory
/// <param name="content">内容</param> /// <param name="content">内容</param>
/// <param name="defaultButton">默认按钮</param> /// <param name="defaultButton">默认按钮</param>
/// <returns>结果</returns> /// <returns>结果</returns>
ValueTask<ContentDialogResult> ConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close); ValueTask<ContentDialogResult> CreateForConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close);
/// <summary> /// <summary>
/// 异步创建一个新的内容对话框,用于提示未知的进度 /// 异步创建一个新的内容对话框,用于提示未知的进度

View File

@@ -26,18 +26,35 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
} }
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<ContentDialogResult> ConfirmAsync(string title, string content) public async ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content)
{ {
ContentDialog dialog = await CreateForConfirmAsync(title, content).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = new()
{
XamlRoot = mainWindow.Content.XamlRoot,
Title = title,
Content = content,
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
};
return await dialog.ShowAsync(); return await dialog.ShowAsync();
} }
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<ContentDialogResult> ConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close) public async ValueTask<ContentDialogResult> CreateForConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close)
{ {
ContentDialog dialog = await CreateForConfirmCancelAsync(title, content, defaultButton).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = new()
{
XamlRoot = mainWindow.Content.XamlRoot,
Title = title,
Content = content,
DefaultButton = defaultButton,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
CloseButtonText = SH.ContentDialogCancelCloseButtonText,
};
return await dialog.ShowAsync(); return await dialog.ShowAsync();
} }
@@ -54,35 +71,4 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
return dialog; return dialog;
} }
private async ValueTask<ContentDialog> CreateForConfirmAsync(string title, string content)
{
await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = new()
{
XamlRoot = mainWindow.Content.XamlRoot,
Title = title,
Content = content,
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
};
return dialog;
}
private async ValueTask<ContentDialog> CreateForConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close)
{
await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = new()
{
XamlRoot = mainWindow.Content.XamlRoot,
Title = title,
Content = content,
DefaultButton = defaultButton,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
CloseButtonText = SH.ContentDialogCancelCloseButtonText,
};
return dialog;
}
} }

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core;
using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Factory.Abstraction;
using Windows.Foundation.Metadata;
using Windows.Storage.Pickers; using Windows.Storage.Pickers;
using WinRT.Interop; using WinRT.Interop;
@@ -10,21 +10,14 @@ namespace Snap.Hutao.Factory;
/// <inheritdoc cref="IPickerFactory"/> /// <inheritdoc cref="IPickerFactory"/>
[HighQuality] [HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Transient, typeof(IPickerFactory))] [Injection(InjectAs.Transient, typeof(IPickerFactory))]
internal class PickerFactory : IPickerFactory internal sealed partial class PickerFactory : IPickerFactory
{ {
private const string AnyType = "*"; private const string AnyType = "*";
private readonly MainWindow mainWindow; private readonly MainWindow mainWindow;
private readonly RuntimeOptions runtimeOptions;
/// <summary>
/// 构造一个新的文件选择器工厂
/// </summary>
/// <param name="mainWindow">主窗体的引用注入</param>
public PickerFactory(MainWindow mainWindow)
{
this.mainWindow = mainWindow;
}
/// <inheritdoc/> /// <inheritdoc/>
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes) public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
@@ -40,7 +33,7 @@ internal class PickerFactory : IPickerFactory
} }
// below Windows 11 // below Windows 11
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 13)) if (!runtimeOptions.Windows11OrHigher)
{ {
// https://github.com/microsoft/WindowsAppSDK/issues/2931 // https://github.com/microsoft/WindowsAppSDK/issues/2931
picker.FileTypeFilter.Add(AnyType); picker.FileTypeFilter.Add(AnyType);
@@ -72,7 +65,7 @@ internal class PickerFactory : IPickerFactory
FolderPicker picker = GetInitializedPicker<FolderPicker>(); FolderPicker picker = GetInitializedPicker<FolderPicker>();
// below Windows 11 // below Windows 11
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 13)) if (!runtimeOptions.Windows11OrHigher)
{ {
// https://github.com/microsoft/WindowsAppSDK/issues/2931 // https://github.com/microsoft/WindowsAppSDK/issues/2931
picker.FileTypeFilter.Add(AnyType); picker.FileTypeFilter.Add(AnyType);

View File

@@ -2,7 +2,9 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty; using Snap.Hutao.ViewModel.AvatarProperty;
@@ -13,7 +15,11 @@ namespace Snap.Hutao.Model.Calculable;
/// 可计算角色 /// 可计算角色
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar internal sealed class CalculableAvatar
: ObservableObject,
ICalculableAvatar,
IMappingFrom<CalculableAvatar, Avatar>,
IMappingFrom<CalculableAvatar, AvatarView>
{ {
private uint levelCurrent; private uint levelCurrent;
private uint levelTarget; private uint levelTarget;
@@ -22,7 +28,7 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
/// 构造一个新的可计算角色 /// 构造一个新的可计算角色
/// </summary> /// </summary>
/// <param name="avatar">角色</param> /// <param name="avatar">角色</param>
public CalculableAvatar(Metadata.Avatar.Avatar avatar) private CalculableAvatar(Avatar avatar)
{ {
AvatarId = avatar.Id; AvatarId = avatar.Id;
LevelMin = 1; LevelMin = 1;
@@ -40,7 +46,7 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
/// 构造一个新的可计算角色 /// 构造一个新的可计算角色
/// </summary> /// </summary>
/// <param name="avatar">角色</param> /// <param name="avatar">角色</param>
public CalculableAvatar(AvatarView avatar) private CalculableAvatar(AvatarView avatar)
{ {
AvatarId = avatar.Id; AvatarId = avatar.Id;
LevelMin = avatar.LevelNumber; LevelMin = avatar.LevelNumber;
@@ -80,4 +86,14 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
/// <inheritdoc/> /// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public static CalculableAvatar From(Avatar source)
{
return new(source);
}
public static CalculableAvatar From(AvatarView source)
{
return new(source);
}
} }

View File

@@ -6,8 +6,18 @@ namespace Snap.Hutao.Model.Calculable;
/// <summary> /// <summary>
/// 可计算物品选项 /// 可计算物品选项
/// </summary> /// </summary>
internal sealed class CalculableOptions internal readonly struct CalculableOptions
{ {
/// <summary>
/// 角色
/// </summary>
public readonly ICalculableAvatar? Avatar;
/// <summary>
/// 武器
/// </summary>
public readonly ICalculableWeapon? Weapon;
/// <summary> /// <summary>
/// 构造一个新的可计算物品选项 /// 构造一个新的可计算物品选项
/// </summary> /// </summary>
@@ -18,14 +28,4 @@ internal sealed class CalculableOptions
Avatar = avatar; Avatar = avatar;
Weapon = weapon; Weapon = weapon;
} }
/// <summary>
/// 角色
/// </summary>
public ICalculableAvatar? Avatar { get; }
/// <summary>
/// 武器
/// </summary>
public ICalculableWeapon? Weapon { get; }
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
@@ -14,7 +15,11 @@ namespace Snap.Hutao.Model.Calculable;
/// 可计算的技能 /// 可计算的技能
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class CalculableSkill : ObservableObject, ICalculableSkill internal sealed class CalculableSkill
: ObservableObject,
ICalculableSkill,
IMappingFrom<CalculableSkill, ProudableSkill>,
IMappingFrom<CalculableSkill, SkillView>
{ {
private uint levelCurrent; private uint levelCurrent;
private uint levelTarget; private uint levelTarget;
@@ -23,7 +28,7 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
/// 构造一个新的可计算的技能 /// 构造一个新的可计算的技能
/// </summary> /// </summary>
/// <param name="skill">技能</param> /// <param name="skill">技能</param>
public CalculableSkill(ProudableSkill skill) private CalculableSkill(ProudableSkill skill)
{ {
GroupId = skill.GroupId; GroupId = skill.GroupId;
LevelMin = 1; LevelMin = 1;
@@ -40,7 +45,7 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
/// 构造一个新的可计算的技能 /// 构造一个新的可计算的技能
/// </summary> /// </summary>
/// <param name="skill">技能</param> /// <param name="skill">技能</param>
public CalculableSkill(SkillView skill) private CalculableSkill(SkillView skill)
{ {
GroupId = skill.GroupId; GroupId = skill.GroupId;
LevelMin = skill.LevelNumber; LevelMin = skill.LevelNumber;
@@ -76,4 +81,14 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
/// <inheritdoc/> /// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public static CalculableSkill From(ProudableSkill source)
{
return new(source);
}
public static CalculableSkill From(SkillView source)
{
return new(source);
}
} }

View File

@@ -2,8 +2,10 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty; using Snap.Hutao.ViewModel.AvatarProperty;
@@ -13,7 +15,11 @@ namespace Snap.Hutao.Model.Calculable;
/// 可计算武器 /// 可计算武器
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal class CalculableWeapon : ObservableObject, ICalculableWeapon internal sealed class CalculableWeapon
: ObservableObject,
ICalculableWeapon,
IMappingFrom<CalculableWeapon, Weapon>,
IMappingFrom<CalculableWeapon, WeaponView>
{ {
private uint levelCurrent; private uint levelCurrent;
private uint levelTarget; private uint levelTarget;
@@ -22,7 +28,7 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
/// 构造一个新的可计算武器 /// 构造一个新的可计算武器
/// </summary> /// </summary>
/// <param name="weapon">武器</param> /// <param name="weapon">武器</param>
public CalculableWeapon(Metadata.Weapon.Weapon weapon) private CalculableWeapon(Weapon weapon)
{ {
WeaponId = weapon.Id; WeaponId = weapon.Id;
LevelMin = 1; LevelMin = 1;
@@ -39,7 +45,7 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
/// 构造一个新的可计算武器 /// 构造一个新的可计算武器
/// </summary> /// </summary>
/// <param name="weapon">武器</param> /// <param name="weapon">武器</param>
public CalculableWeapon(WeaponView weapon) private CalculableWeapon(WeaponView weapon)
{ {
WeaponId = weapon.Id; WeaponId = weapon.Id;
LevelMin = weapon.LevelNumber; LevelMin = weapon.LevelNumber;
@@ -75,4 +81,14 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
/// <inheritdoc/> /// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public static CalculableWeapon From(Weapon source)
{
return new(source);
}
public static CalculableWeapon From(WeaponView source)
{
return new(source);
}
} }

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics.Contracts;
namespace Snap.Hutao.Model.Entity.Abstraction;
internal interface IDbMappingForeignKeyFrom<TSource, TFrom>
{
[Pure]
static abstract TSource From(in Guid foreignKey, in TFrom from);
}
internal interface IDbMappingForeignKeyFrom<TSource, T1, T2>
{
[Pure]
static abstract TSource From(in Guid foreignKey, in T1 param1, in T2 param2);
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Entity.Abstraction;
using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
@@ -14,7 +15,11 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("achievements")] [Table("achievements")]
internal sealed class Achievement : IEquatable<Achievement> [SuppressMessage("", "SA1124")]
internal sealed class Achievement
: IEquatable<Achievement>,
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -57,14 +62,14 @@ internal sealed class Achievement : IEquatable<Achievement>
/// <summary> /// <summary>
/// 创建一个新的成就 /// 创建一个新的成就
/// </summary> /// </summary>
/// <param name="userId">对应的用户id</param> /// <param name="archiveId">对应的用户id</param>
/// <param name="id">成就Id</param> /// <param name="id">成就Id</param>
/// <returns>新创建的成就</returns> /// <returns>新创建的成就</returns>
public static Achievement Create(in Guid userId, in AchievementId id) public static Achievement From(in Guid archiveId, in AchievementId id)
{ {
return new() return new()
{ {
ArchiveId = userId, ArchiveId = archiveId,
Id = id, Id = id,
Current = 0, Current = 0,
Time = DateTimeOffset.MinValue, Time = DateTimeOffset.MinValue,
@@ -77,7 +82,7 @@ internal sealed class Achievement : IEquatable<Achievement>
/// <param name="userId">对应的用户id</param> /// <param name="userId">对应的用户id</param>
/// <param name="uiaf">uiaf项</param> /// <param name="uiaf">uiaf项</param>
/// <returns>新创建的成就</returns> /// <returns>新创建的成就</returns>
public static Achievement Create(in Guid userId, UIAFItem uiaf) public static Achievement From(in Guid userId, in UIAFItem uiaf)
{ {
return new() return new()
{ {
@@ -89,21 +94,6 @@ internal sealed class Achievement : IEquatable<Achievement>
}; };
} }
/// <summary>
/// 转换到UIAF物品
/// </summary>
/// <returns>UIAF物品</returns>
public UIAFItem ToUIAFItem()
{
return new()
{
Id = Id,
Current = Current,
Status = Status,
Timestamp = Time.ToUnixTimeSeconds(),
};
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Achievement? other) public bool Equals(Achievement? other)
{ {
@@ -121,6 +111,8 @@ internal sealed class Achievement : IEquatable<Achievement>
} }
} }
#region Object
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
@@ -132,4 +124,5 @@ internal sealed class Achievement : IEquatable<Achievement>
{ {
return HashCode.Combine(ArchiveId, Id, Current, Status, Time); return HashCode.Combine(ArchiveId, Id, Current, Status, Time);
} }
#endregion
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Model.Entity;
/// 成就存档 /// 成就存档
/// </summary> /// </summary>
[Table("achievement_archives")] [Table("achievement_archives")]
internal sealed class AchievementArchive : ISelectable internal sealed class AchievementArchive : ISelectable, IMappingFrom<AchievementArchive, string>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -35,7 +36,7 @@ internal sealed class AchievementArchive : ISelectable
/// </summary> /// </summary>
/// <param name="name">名称</param> /// <param name="name">名称</param>
/// <returns>新存档</returns> /// <returns>新存档</returns>
public static AchievementArchive Create(string name) public static AchievementArchive From(string name)
{ {
return new() { Name = name }; return new() { Name = name };
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -11,10 +12,10 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("avatar_infos")] [Table("avatar_infos")]
internal sealed class AvatarInfo internal sealed class AvatarInfo : IMappingFrom<AvatarInfo, string, Web.Enka.Model.AvatarInfo>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部 Id
/// </summary> /// </summary>
[Key] [Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@@ -36,7 +37,7 @@ internal sealed class AvatarInfo
/// <param name="uid">uid</param> /// <param name="uid">uid</param>
/// <param name="info">角色信息</param> /// <param name="info">角色信息</param>
/// <returns>实体角色信息</returns> /// <returns>实体角色信息</returns>
public static AvatarInfo Create(string uid, Web.Enka.Model.AvatarInfo info) public static AvatarInfo From(string uid, Web.Enka.Model.AvatarInfo info)
{ {
return new() { Uid = uid, Info = info }; return new() { Uid = uid, Info = info };
} }

View File

@@ -16,7 +16,7 @@ internal sealed class AvatarInfoConfiguration : IEntityTypeConfiguration<AvatarI
public void Configure(EntityTypeBuilder<AvatarInfo> builder) public void Configure(EntityTypeBuilder<AvatarInfo> builder)
{ {
builder.Property(e => e.Info) builder.Property(e => e.Info)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion<JsonTextValueConverter<Web.Enka.Model.AvatarInfo>>(); .HasConversion<JsonTextValueConverter<Web.Enka.Model.AvatarInfo>>();
} }
} }

View File

@@ -16,7 +16,7 @@ internal sealed class DailyNoteEntryConfiguration : IEntityTypeConfiguration<Dai
public void Configure(EntityTypeBuilder<DailyNoteEntry> builder) public void Configure(EntityTypeBuilder<DailyNoteEntry> builder)
{ {
builder.Property(e => e.DailyNote) builder.Property(e => e.DailyNote)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote>>(); .HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote>>();
} }
} }

View File

@@ -16,7 +16,7 @@ internal sealed class InventoryReliquaryConfiguration : IEntityTypeConfiguration
public void Configure(EntityTypeBuilder<InventoryReliquary> builder) public void Configure(EntityTypeBuilder<InventoryReliquary> builder)
{ {
builder.Property(e => e.AppendPropIdList) builder.Property(e => e.AppendPropIdList)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion( .HasConversion(
list => list.ToString(','), list => list.ToString(','),
text => text.Split(',', StringSplitOptions.None).Select(int.Parse).ToList()); text => text.Split(',', StringSplitOptions.None).Select(int.Parse).ToList());

View File

@@ -16,7 +16,7 @@ internal sealed class SpiralAbyssEntryConfiguration : IEntityTypeConfiguration<S
public void Configure(EntityTypeBuilder<SpiralAbyssEntry> builder) public void Configure(EntityTypeBuilder<SpiralAbyssEntry> builder)
{ {
builder.Property(e => e.SpiralAbyss) builder.Property(e => e.SpiralAbyss)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss>>(); .HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss>>();
} }
} }

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Entity.Configuration;
internal sealed class SqliteTypeNames
{
public const string Integer = "INTEGER";
public const string Real = "REAL";
public const string Blob = "BLOB";
public const string Text = "TEXT";
}

View File

@@ -17,15 +17,15 @@ internal sealed class UserConfiguration : IEntityTypeConfiguration<User>
public void Configure(EntityTypeBuilder<User> builder) public void Configure(EntityTypeBuilder<User> builder)
{ {
builder.Property(e => e.CookieToken) builder.Property(e => e.CookieToken)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e)); .HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
builder.Property(e => e.LToken) builder.Property(e => e.LToken)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e)); .HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
builder.Property(e => e.SToken) builder.Property(e => e.SToken)
.HasColumnType("TEXT") .HasColumnType(SqliteTypeNames.Text)
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e)); .HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Entity.Abstraction;
using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Entity.Primitive;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -12,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("cultivate_entries")] [Table("cultivate_entries")]
internal sealed class CultivateEntry internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry, CultivateType, uint>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -49,7 +50,7 @@ internal sealed class CultivateEntry
/// <param name="type">类型</param> /// <param name="type">类型</param>
/// <param name="id">主Id</param> /// <param name="id">主Id</param>
/// <returns>养成入口点</returns> /// <returns>养成入口点</returns>
public static CultivateEntry Create(in Guid projectId, CultivateType type, uint id) public static CultivateEntry From(in Guid projectId, in CultivateType type, in uint id)
{ {
return new() return new()
{ {

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Entity.Abstraction;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("cultivate_items")] [Table("cultivate_items")]
internal sealed class CultivateItem internal sealed class CultivateItem : IDbMappingForeignKeyFrom<CultivateItem, Web.Hoyolab.Takumi.Event.Calculate.Item>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -50,16 +51,15 @@ internal sealed class CultivateItem
/// 创建一个新的养成物品 /// 创建一个新的养成物品
/// </summary> /// </summary>
/// <param name="entryId">入口点 Id</param> /// <param name="entryId">入口点 Id</param>
/// <param name="itemId">物品 Id</param> /// <param name="item">物品</param>
/// <param name="count">个数</param>
/// <returns>养成物品</returns> /// <returns>养成物品</returns>
public static CultivateItem Create(in Guid entryId, int itemId, int count) public static CultivateItem From(in Guid entryId, in Web.Hoyolab.Takumi.Event.Calculate.Item item)
{ {
return new() return new()
{ {
EntryId = entryId, EntryId = entryId,
ItemId = itemId, ItemId = item.Id,
Count = count, Count = item.Num,
}; };
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -12,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("cultivate_projects")] [Table("cultivate_projects")]
internal sealed class CultivateProject : ISelectable internal sealed class CultivateProject : ISelectable, IMappingFrom<CultivateProject, string, string>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -42,7 +43,7 @@ internal sealed class CultivateProject : ISelectable
/// <param name="name">名称</param> /// <param name="name">名称</param>
/// <param name="attachedUid">绑定的Uid</param> /// <param name="attachedUid">绑定的Uid</param>
/// <returns>新的养成计划</returns> /// <returns>新的养成计划</returns>
public static CultivateProject Create(string name, string? attachedUid = null) public static CultivateProject From(string name, string? attachedUid = null)
{ {
return new() { Name = name, AttachedUid = attachedUid }; return new() { Name = name, AttachedUid = attachedUid };
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
@@ -15,7 +16,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("daily_notes")] [Table("daily_notes")]
internal sealed class DailyNoteEntry : ObservableObject internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteEntry, UserAndUid>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -106,7 +107,7 @@ internal sealed class DailyNoteEntry : ObservableObject
/// </summary> /// </summary>
/// <param name="userAndUid">用户与角色</param> /// <param name="userAndUid">用户与角色</param>
/// <returns>新的实时便笺</returns> /// <returns>新的实时便笺</returns>
public static DailyNoteEntry Create(UserAndUid userAndUid) public static DailyNoteEntry From(UserAndUid userAndUid)
{ {
return new() return new()
{ {

View File

@@ -3,6 +3,7 @@
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Service.GachaLog; using Snap.Hutao.Service.GachaLog;
@@ -17,7 +18,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[Table("gacha_archives")] [Table("gacha_archives")]
internal sealed class GachaArchive : ISelectable internal sealed class GachaArchive : ISelectable, IMappingFrom<GachaArchive, string>
{ {
/// <summary> /// <summary>
/// 内部Id /// 内部Id
@@ -39,7 +40,7 @@ internal sealed class GachaArchive : ISelectable
/// </summary> /// </summary>
/// <param name="uid">uid</param> /// <param name="uid">uid</param>
/// <returns>新的卡池存档</returns> /// <returns>新的卡池存档</returns>
public static GachaArchive Create(string uid) public static GachaArchive From(string uid)
{ {
return new() { Uid = uid }; return new() { Uid = uid };
} }
@@ -69,7 +70,7 @@ internal sealed class GachaArchive : ISelectable
if (archive == null) if (archive == null)
{ {
GachaArchive created = Create(context.Uid); GachaArchive created = From(context.Uid);
context.GachaArchives.AddAndSave(created); context.GachaArchives.AddAndSave(created);
context.TaskContext.InvokeOnMainThread(() => context.ArchiveCollection.Add(created)); context.TaskContext.InvokeOnMainThread(() => context.ArchiveCollection.Add(created));
archive = created; archive = created;

View File

@@ -51,7 +51,7 @@ internal sealed class UIAFInfo
/// <returns>专用 UIAF 信息</returns> /// <returns>专用 UIAF 信息</returns>
public static UIAFInfo Create(IServiceProvider serviceProvider) public static UIAFInfo Create(IServiceProvider serviceProvider)
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return new() return new()
{ {

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.InterChange.Achievement; namespace Snap.Hutao.Model.InterChange.Achievement;
@@ -8,7 +9,7 @@ namespace Snap.Hutao.Model.InterChange.Achievement;
/// <summary> /// <summary>
/// UIAF 项 /// UIAF 项
/// </summary> /// </summary>
internal sealed class UIAFItem internal sealed class UIAFItem : IMappingFrom<UIAFItem, Entity.Achievement>
{ {
/// <summary> /// <summary>
/// 成就Id /// 成就Id
@@ -34,4 +35,15 @@ internal sealed class UIAFItem
/// </summary> /// </summary>
[JsonPropertyName("status")] [JsonPropertyName("status")]
public AchievementStatus Status { get; set; } public AchievementStatus Status { get; set; }
public static UIAFItem From(Entity.Achievement source)
{
return new()
{
Id = source.Id,
Current = source.Current,
Status = source.Status,
Timestamp = source.Time.ToUnixTimeSeconds(),
};
}
} }

View File

@@ -64,7 +64,7 @@ internal sealed class UIGFInfo
/// <returns>专用 UIGF 信息</returns> /// <returns>专用 UIGF 信息</returns>
public static UIGFInfo Create(IServiceProvider serviceProvider, string uid) public static UIGFInfo Create(IServiceProvider serviceProvider, string uid)
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return new() return new()
{ {

View File

@@ -64,7 +64,7 @@ internal sealed class UIIFInfo
/// <returns>专用 UIGF 信息</returns> /// <returns>专用 UIGF 信息</returns>
public static UIIFInfo Create(IServiceProvider serviceProvider, string uid) public static UIIFInfo Create(IServiceProvider serviceProvider, string uid)
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return new() return new()
{ {

View File

@@ -43,7 +43,7 @@ internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IName
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableAvatar ToCalculable() public ICalculableAvatar ToCalculable()
{ {
return new CalculableAvatar(this); return CalculableAvatar.From(this);
} }
/// <summary> /// <summary>

View File

@@ -13,6 +13,6 @@ internal sealed partial class ProudableSkill : ICalculableSource<ICalculableSkil
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableSkill ToCalculable() public ICalculableSkill ToCalculable()
{ {
return new CalculableSkill(this); return CalculableSkill.From(this);
} }
} }

View File

@@ -36,7 +36,7 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableWeapon ToCalculable() public ICalculableWeapon ToCalculable()
{ {
return new CalculableWeapon(this); return CalculableWeapon.From(this);
} }
/// <summary> /// <summary>

View File

@@ -63,7 +63,7 @@ internal sealed partial class AchievementDbBulkOperation
if (entity is null && uiaf is not null) if (entity is null && uiaf is not null)
{ {
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf)); appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
add++; add++;
continue; continue;
} }
@@ -86,7 +86,7 @@ internal sealed partial class AchievementDbBulkOperation
if (aggressive) if (aggressive)
{ {
appDbContext.Achievements.RemoveAndSave(entity); appDbContext.Achievements.RemoveAndSave(entity);
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf)); appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
update++; update++;
} }
} }
@@ -96,7 +96,7 @@ internal sealed partial class AchievementDbBulkOperation
moveEntity = false; moveEntity = false;
moveUIAF = true; moveUIAF = true;
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf)); appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
add++; add++;
} }
} }

View File

@@ -37,7 +37,7 @@ internal sealed partial class AchievementService
case ImportStrategy.Overwrite: case ImportStrategy.Overwrite:
{ {
IEnumerable<EntityAchievement> orederedUIAF = list IEnumerable<EntityAchievement> orederedUIAF = list
.Select(uiaf => EntityAchievement.Create(archiveId, uiaf)) .Select(uiaf => EntityAchievement.From(archiveId, uiaf))
.OrderBy(a => a.Id); .OrderBy(a => a.Id);
return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF); return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF);
} }
@@ -58,7 +58,7 @@ internal sealed partial class AchievementService
List<UIAFItem> list = appDbContext.Achievements List<UIAFItem> list = appDbContext.Achievements
.Where(i => i.ArchiveId == archive.InnerId) .Where(i => i.ArchiveId == archive.InnerId)
.AsEnumerable() .AsEnumerable()
.Select(i => i.ToUIAFItem()) .Select(UIAFItem.From)
.ToList(); .ToList();
return new() return new()

View File

@@ -52,7 +52,7 @@ internal sealed partial class AchievementService : IAchievementService
return metadata.SelectList(meta => return metadata.SelectList(meta =>
{ {
EntityAchievement entity = entityMap.GetValueOrDefault(meta.Id) ?? EntityAchievement.Create(archive.InnerId, meta.Id); EntityAchievement entity = entityMap.GetValueOrDefault(meta.Id) ?? EntityAchievement.From(archive.InnerId, meta.Id);
return new AchievementView(entity, meta); return new AchievementView(entity, meta);
}); });
} }

View File

@@ -204,7 +204,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
{ {
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId }; EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
transformer.Transform(ref avatarInfo, source); transformer.Transform(ref avatarInfo, source);
entity = ModelAvatarInfo.Create(uid, avatarInfo); entity = ModelAvatarInfo.From(uid, avatarInfo);
appDbContext.AvatarInfos.AddAndSave(entity); appDbContext.AvatarInfos.AddAndSave(entity);
} }
else else
@@ -221,7 +221,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
{ {
if (entity == null) if (entity == null)
{ {
entity = ModelAvatarInfo.Create(uid, webInfo); entity = ModelAvatarInfo.From(uid, webInfo);
appDbContext.AvatarInfos.AddAndSave(entity); appDbContext.AvatarInfos.AddAndSave(entity);
} }
else else

View File

@@ -222,14 +222,14 @@ internal sealed partial class CultivationService : ICultivationService
if (entry == null) if (entry == null)
{ {
entry = CultivateEntry.Create(projectId, type, itemId); entry = CultivateEntry.From(projectId, type, itemId);
await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false); await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false);
} }
Guid entryId = entry.InnerId; Guid entryId = entry.InnerId;
await appDbContext.CultivateItems.ExecuteDeleteWhereAsync(i => i.EntryId == entryId).ConfigureAwait(false); await appDbContext.CultivateItems.ExecuteDeleteWhereAsync(i => i.EntryId == entryId).ConfigureAwait(false);
IEnumerable<CultivateItem> toAdd = items.Select(i => CultivateItem.Create(entryId, i.Id, i.Num)); IEnumerable<CultivateItem> toAdd = items.Select(item => CultivateItem.From(entryId, item));
await appDbContext.CultivateItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false); await appDbContext.CultivateItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false);
} }

View File

@@ -48,7 +48,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
if (!appDbContext.DailyNotes.Any(n => n.Uid == roleUid)) if (!appDbContext.DailyNotes.Any(n => n.Uid == roleUid))
{ {
DailyNoteEntry newEntry = DailyNoteEntry.Create(role); DailyNoteEntry newEntry = DailyNoteEntry.From(role);
Web.Response.Response<WebDailyNote> dailyNoteResponse = await scope.ServiceProvider Web.Response.Response<WebDailyNote> dailyNoteResponse = await scope.ServiceProvider
.PickRequiredService<IGameRecordClient>(PlayerUid.IsOversea(roleUid)) .PickRequiredService<IGameRecordClient>(PlayerUid.IsOversea(roleUid))

View File

@@ -76,7 +76,7 @@ internal sealed partial class HutaoCloudService : IHutaoCloudService
{ {
if (archive == null) if (archive == null)
{ {
archive = Model.Entity.GachaArchive.Create(uid); archive = Model.Entity.GachaArchive.From(uid);
await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false); await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false);
} }

View File

@@ -32,7 +32,7 @@ internal sealed partial class GameService : IGameService
private readonly PackageConverter packageConverter; private readonly PackageConverter packageConverter;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly LaunchOptions launchOptions; private readonly LaunchOptions launchOptions;
private readonly HutaoOptions hutaoOptions; private readonly RuntimeOptions hutaoOptions;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly AppOptions appOptions; private readonly AppOptions appOptions;
@@ -302,7 +302,7 @@ internal sealed partial class GameService : IGameService
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask DetectGameAccountAsync() public async ValueTask DetectGameAccountAsync()
{ {
Must.NotNull(gameAccounts!); ArgumentNullException.ThrowIfNull(gameAccounts);
string? registrySdk = RegistryInterop.Get(); string? registrySdk = RegistryInterop.Get();
if (!string.IsNullOrEmpty(registrySdk)) if (!string.IsNullOrEmpty(registrySdk))
@@ -350,7 +350,7 @@ internal sealed partial class GameService : IGameService
/// <inheritdoc/> /// <inheritdoc/>
public GameAccount? DetectCurrentGameAccount() public GameAccount? DetectCurrentGameAccount()
{ {
Must.NotNull(gameAccounts!); ArgumentNullException.ThrowIfNull(gameAccounts);
string? registrySdk = RegistryInterop.Get(); string? registrySdk = RegistryInterop.Get();

View File

@@ -203,7 +203,7 @@ internal sealed partial class PackageConverter
} }
// Cache folder // Cache folder
Core.HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<Core.HutaoOptions>(); Core.RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<Core.RuntimeOptions>();
string cacheFolder = Path.Combine(hutaoOptions.DataFolder, "ServerCache"); string cacheFolder = Path.Combine(hutaoOptions.DataFolder, "ServerCache");
// 执行下载与移动操作 // 执行下载与移动操作

View File

@@ -19,7 +19,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
private readonly AppOptions appOptions; private readonly AppOptions appOptions;
private readonly HutaoOptions hutaoOptions; private readonly RuntimeOptions hutaoOptions;
private string? localeName; private string? localeName;
private string? fallbackDataFolder; private string? fallbackDataFolder;

View File

@@ -43,7 +43,8 @@ internal sealed partial class MetadataService
if (memoryCache.TryGetValue(cacheKey, out object? value)) if (memoryCache.TryGetValue(cacheKey, out object? value))
{ {
return Must.NotNull((Dictionary<MaterialId, DisplayItem>)value!); ArgumentNullException.ThrowIfNull(value);
return (Dictionary<MaterialId, DisplayItem>)value;
} }
Dictionary<MaterialId, DisplayItem> displays = await FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false); Dictionary<MaterialId, DisplayItem> displays = await FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false);

View File

@@ -167,7 +167,8 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
if (memoryCache.TryGetValue(cacheKey, out object? value)) if (memoryCache.TryGetValue(cacheKey, out object? value))
{ {
return Must.NotNull((T)value!); ArgumentNullException.ThrowIfNull(value);
return (T)value;
} }
string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json"); string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json");
@@ -176,7 +177,8 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
using (Stream fileStream = File.OpenRead(path)) using (Stream fileStream = File.OpenRead(path))
{ {
T? result = await JsonSerializer.DeserializeAsync<T>(fileStream, options, token).ConfigureAwait(false); T? result = await JsonSerializer.DeserializeAsync<T>(fileStream, options, token).ConfigureAwait(false);
return memoryCache.Set(cacheKey, Must.NotNull(result!)); ArgumentNullException.ThrowIfNull(result);
return memoryCache.Set(cacheKey, result);
} }
} }
else else
@@ -193,11 +195,12 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
if (memoryCache.TryGetValue(cacheKey, out object? value)) if (memoryCache.TryGetValue(cacheKey, out object? value))
{ {
return Must.NotNull((Dictionary<TKey, TValue>)value!); ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
} }
List<TValue> list = await FromCacheOrFileAsync<List<TValue>>(fileName, token).ConfigureAwait(false); List<TValue> list = await FromCacheOrFileAsync<List<TValue>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = list.ToDictionaryOverride(keySelector); Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items
return memoryCache.Set(cacheKey, dict); return memoryCache.Set(cacheKey, dict);
} }
@@ -208,11 +211,12 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
if (memoryCache.TryGetValue(cacheKey, out object? value)) if (memoryCache.TryGetValue(cacheKey, out object? value))
{ {
return Must.NotNull((Dictionary<TKey, TValue>)value!); ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
} }
List<TData> list = await FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false); List<TData> list = await FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = list.ToDictionaryOverride(keySelector, valueSelector); Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
return memoryCache.Set(cacheKey, dict); return memoryCache.Set(cacheKey, dict);
} }
} }

View File

@@ -41,7 +41,8 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
navigationView.PaneOpened -= OnPaneStateChanged; navigationView.PaneOpened -= OnPaneStateChanged;
} }
navigationView = Must.NotNull(value!); ArgumentNullException.ThrowIfNull(value);
navigationView = value;
// add new listener // add new listener
if (navigationView != null) if (navigationView != null)

View File

@@ -42,7 +42,7 @@ internal sealed partial class CultivateProjectDialog : ContentDialog
? Ioc.Default.GetRequiredService<IUserService>().Current?.SelectedUserGameRole?.GameUid ? Ioc.Default.GetRequiredService<IUserService>().Current?.SelectedUserGameRole?.GameUid
: null; : null;
return new(true, CultivateProject.Create(text, uid)); return new(true, CultivateProject.From(text, uid));
} }
return new(false, null!); return new(false, null!);

View File

@@ -28,7 +28,7 @@ internal sealed partial class TitleView : UserControl
{ {
get get
{ {
Core.HutaoOptions hutaoOptions = Ioc.Default.GetRequiredService<Core.HutaoOptions>(); Core.RuntimeOptions hutaoOptions = Ioc.Default.GetRequiredService<Core.RuntimeOptions>();
string format = string format =
#if DEBUG #if DEBUG

View File

@@ -202,7 +202,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
if (isOk) if (isOk)
{ {
ArchiveAddResult result = await achievementService.TryAddArchiveAsync(EntityAchievementArchive.Create(name)).ConfigureAwait(false); ArchiveAddResult result = await achievementService.TryAddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
switch (result) switch (result)
{ {
@@ -230,7 +230,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
if (Archives != null && SelectedArchive != null) if (Archives != null && SelectedArchive != null)
{ {
ContentDialogResult result = await contentDialogFactory ContentDialogResult result = await contentDialogFactory
.ConfirmCancelAsync( .CreateForConfirmCancelAsync(
string.Format(SH.ViewModelAchievementRemoveArchiveTitle, SelectedArchive.Name), string.Format(SH.ViewModelAchievementRemoveArchiveTitle, SelectedArchive.Name),
SH.ViewModelAchievementRemoveArchiveContent) SH.ViewModelAchievementRemoveArchiveContent)
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@@ -106,6 +106,6 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableAvatar ToCalculable() public ICalculableAvatar ToCalculable()
{ {
return new CalculableAvatar(this); return CalculableAvatar.From(this);
} }
} }

View File

@@ -36,6 +36,6 @@ internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalcul
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableSkill ToCalculable() public ICalculableSkill ToCalculable()
{ {
return new CalculableSkill(this); return CalculableSkill.From(this);
} }
} }

View File

@@ -55,6 +55,6 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableWeapon ToCalculable() public ICalculableWeapon ToCalculable()
{ {
return new CalculableWeapon(this); return CalculableWeapon.From(this);
} }
} }

View File

@@ -25,14 +25,14 @@ internal sealed partial class ExperimentalFeaturesViewModel : ObservableObject
[Command("OpenCacheFolderCommand")] [Command("OpenCacheFolderCommand")]
private Task OpenCacheFolderAsync() private Task OpenCacheFolderAsync()
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return Launcher.LaunchFolderPathAsync(hutaoOptions.LocalCache).AsTask(); return Launcher.LaunchFolderPathAsync(hutaoOptions.LocalCache).AsTask();
} }
[Command("OpenDataFolderCommand")] [Command("OpenDataFolderCommand")]
private Task OpenDataFolderAsync() private Task OpenDataFolderAsync()
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return Launcher.LaunchFolderPathAsync(hutaoOptions.DataFolder).AsTask(); return Launcher.LaunchFolderPathAsync(hutaoOptions.DataFolder).AsTask();
} }
@@ -43,7 +43,7 @@ internal sealed partial class ExperimentalFeaturesViewModel : ObservableObject
{ {
ContentDialogResult result = await scope.ServiceProvider ContentDialogResult result = await scope.ServiceProvider
.GetRequiredService<IContentDialogFactory>() .GetRequiredService<IContentDialogFactory>()
.ConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent) .CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
.ConfigureAwait(false); .ConfigureAwait(false);
if (result == ContentDialogResult.Primary) if (result == ContentDialogResult.Primary)

View File

@@ -223,7 +223,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
} }
else else
{ {
await contentDialogFactory.ConfirmAsync(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage).ConfigureAwait(false); await contentDialogFactory.CreateForConfirmAsync(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage).ConfigureAwait(false);
} }
} }
} }
@@ -267,7 +267,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
if (Archives != null && SelectedArchive != null) if (Archives != null && SelectedArchive != null)
{ {
ContentDialogResult result = await contentDialogFactory ContentDialogResult result = await contentDialogFactory
.ConfirmCancelAsync(string.Format(SH.ViewModelGachaLogRemoveArchiveTitle, SelectedArchive.Uid), SH.ViewModelGachaLogRemoveArchiveDescription) .CreateForConfirmCancelAsync(string.Format(SH.ViewModelGachaLogRemoveArchiveTitle, SelectedArchive.Uid), SH.ViewModelGachaLogRemoveArchiveDescription)
.ConfigureAwait(false); .ConfigureAwait(false);
if (result == ContentDialogResult.Primary) if (result == ContentDialogResult.Primary)

View File

@@ -35,7 +35,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly LaunchOptions launchOptions; private readonly LaunchOptions launchOptions;
private readonly HutaoOptions hutaoOptions; private readonly RuntimeOptions hutaoOptions;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IGameService gameService; private readonly IGameService gameService;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
@@ -86,7 +86,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
/// <summary> /// <summary>
/// 胡桃选项 /// 胡桃选项
/// </summary> /// </summary>
public HutaoOptions HutaoOptions { get => hutaoOptions; } public RuntimeOptions HutaoOptions { get => hutaoOptions; }
/// <summary> /// <summary>
/// 应用选项 /// 应用选项

View File

@@ -39,7 +39,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private readonly IGameService gameService; private readonly IGameService gameService;
private readonly ILogger<SettingViewModel> logger; private readonly ILogger<SettingViewModel> logger;
private readonly AppOptions options; private readonly AppOptions options;
private readonly HutaoOptions hutaoOptions; private readonly RuntimeOptions hutaoOptions;
private readonly HutaoUserOptions hutaoUserOptions; private readonly HutaoUserOptions hutaoUserOptions;
private readonly ExperimentalFeaturesViewModel experimental; private readonly ExperimentalFeaturesViewModel experimental;
@@ -54,7 +54,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
/// <summary> /// <summary>
/// 胡桃选项 /// 胡桃选项
/// </summary> /// </summary>
public HutaoOptions HutaoOptions { get => hutaoOptions; } public RuntimeOptions HutaoOptions { get => hutaoOptions; }
/// <summary> /// <summary>
/// 胡桃用户选项 /// 胡桃用户选项

View File

@@ -25,7 +25,7 @@ internal sealed partial class UserViewModel : ObservableObject
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly Core.HutaoOptions hutaoOptions; private readonly Core.RuntimeOptions hutaoOptions;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IUserService userService; private readonly IUserService userService;

View File

@@ -70,7 +70,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
{ {
Dictionary<string, DownloadSummary> downloadSummaries = new(); Dictionary<string, DownloadSummary> downloadSummaries = new();
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV1Contract)) if (StaticResource.IsContractUnfulfilled(StaticResource.V1Contract))
{ {
downloadSummaries.TryAdd("Bg", new(serviceProvider, "Bg")); downloadSummaries.TryAdd("Bg", new(serviceProvider, "Bg"));
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon")); downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
@@ -83,7 +83,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent")); downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
} }
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV2Contract)) if (StaticResource.IsContractUnfulfilled(StaticResource.V2Contract))
{ {
downloadSummaries.TryAdd("AchievementIcon", new(serviceProvider, "AchievementIcon")); downloadSummaries.TryAdd("AchievementIcon", new(serviceProvider, "AchievementIcon"));
downloadSummaries.TryAdd("ItemIcon", new(serviceProvider, "ItemIcon")); downloadSummaries.TryAdd("ItemIcon", new(serviceProvider, "ItemIcon"));
@@ -91,18 +91,18 @@ internal sealed partial class WelcomeViewModel : ObservableObject
downloadSummaries.TryAdd("RelicIcon", new(serviceProvider, "RelicIcon")); downloadSummaries.TryAdd("RelicIcon", new(serviceProvider, "RelicIcon"));
} }
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV3Contract)) if (StaticResource.IsContractUnfulfilled(StaticResource.V3Contract))
{ {
downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill")); downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill"));
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent")); downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
} }
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV4Contract)) if (StaticResource.IsContractUnfulfilled(StaticResource.V4Contract))
{ {
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon")); downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
} }
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV5Contract)) if (StaticResource.IsContractUnfulfilled(StaticResource.V5Contract))
{ {
downloadSummaries.TryAdd("MonsterIcon", new(serviceProvider, "MonsterIcon")); downloadSummaries.TryAdd("MonsterIcon", new(serviceProvider, "MonsterIcon"));
} }
@@ -134,7 +134,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
{ {
taskContext = serviceProvider.GetRequiredService<ITaskContext>(); taskContext = serviceProvider.GetRequiredService<ITaskContext>();
httpClient = serviceProvider.GetRequiredService<HttpClient>(); httpClient = serviceProvider.GetRequiredService<HttpClient>();
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;

View File

@@ -45,7 +45,7 @@ internal sealed class HomaLogUploadClient
private static HutaoLog BuildFromException(IServiceProvider serviceProvider, Exception exception) private static HutaoLog BuildFromException(IServiceProvider serviceProvider, Exception exception)
{ {
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>(); RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return new() return new()
{ {