mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
mapping abstraction
This commit is contained in:
@@ -74,7 +74,7 @@ public sealed partial class App : Application
|
||||
|
||||
private void LogDiagnosticInformation()
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
logger.LogInformation("FamilyName: {name}", hutaoOptions.FamilyName);
|
||||
logger.LogInformation("Version: {version}", hutaoOptions.Version);
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Snap.Hutao.Control.Image;
|
||||
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 ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
|
||||
private readonly ConcurrentCancellationTokenSource loadingTokenSource = new();
|
||||
|
||||
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)
|
||||
{
|
||||
CompositionImage image = (CompositionImage)sender;
|
||||
CancellationToken token = LoadingTokenSource.Register(image);
|
||||
CancellationToken token = image.loadingTokenSource.Register();
|
||||
IServiceProvider serviceProvider = image.serviceProvider;
|
||||
ILogger<CompositionImage> logger = serviceProvider.GetRequiredService<ILogger<CompositionImage>>();
|
||||
|
||||
|
||||
20
src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IMappingFrom.cs
Normal file
20
src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IMappingFrom.cs
Normal 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);
|
||||
}
|
||||
@@ -221,7 +221,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
return cacheFolder!;
|
||||
}
|
||||
|
||||
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache;
|
||||
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
|
||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||
cacheFolder = info.FullName;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ internal static class IocConfiguration
|
||||
|
||||
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 sqlConnectionString = $"Data Source={dbFile}";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
|
||||
|
||||
@@ -40,6 +40,8 @@ internal static class AppInstanceExtension
|
||||
|
||||
ReadOnlySpan<HANDLE> handles = new(redirectEventHandle);
|
||||
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
|
||||
|
||||
// TODO: Release handle
|
||||
}
|
||||
|
||||
private static void RunAction(object? state)
|
||||
|
||||
@@ -8,30 +8,31 @@ using Snap.Hutao.Core.Setting;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃选项
|
||||
/// 存储环境相关的选项
|
||||
/// 运行时运算得到的选项,无数据库交互
|
||||
/// </summary>
|
||||
[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 string webView2Version = SH.CoreWebView2HelperVersionUndetected;
|
||||
|
||||
private bool? isElevated;
|
||||
private bool? isWindows11;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃选项
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
public HutaoOptions(ILogger<HutaoOptions> logger)
|
||||
public RuntimeOptions(ILogger<RuntimeOptions> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
@@ -97,8 +98,14 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
/// </summary>
|
||||
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/>
|
||||
public HutaoOptions Value { get => this; }
|
||||
public RuntimeOptions Value { get => this; }
|
||||
|
||||
private static string GetDataFolderPath()
|
||||
{
|
||||
@@ -13,12 +13,14 @@ internal sealed class FeatureOptions : IReadOnlyCollection<Feature>
|
||||
/// <summary>
|
||||
/// 启用实时便笺无感验证
|
||||
/// </summary>
|
||||
public Feature IsDailyNoteSilentVerificationEnabled { get; } = new("IsDailyNoteSilentVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSilentVerificationEnabled", true);
|
||||
public Feature IsDailyNoteSilentVerificationEnabled { get; } = new(
|
||||
"IsDailyNoteSilentVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSilentVerificationEnabled", true);
|
||||
|
||||
/// <summary>
|
||||
/// 元数据检查是否忽略
|
||||
/// </summary>
|
||||
public Feature IsMetadataUpdateCheckSuppressed { get; } = new("IsMetadataUpdateCheckSuppressed", "禁用元数据更新检查", "IsMetadataUpdateCheckSuppressed", false);
|
||||
public Feature IsMetadataUpdateCheckSuppressed { get; } = new(
|
||||
"IsMetadataUpdateCheckSuppressed", "禁用元数据更新检查", "IsMetadataUpdateCheckSuppressed", false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Count { get => 2; }
|
||||
|
||||
@@ -49,35 +49,4 @@ internal static class SettingKeys
|
||||
/// 1.7.0 版本指引状态
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
@@ -9,6 +9,31 @@ namespace Snap.Hutao.Core.Setting;
|
||||
[HighQuality]
|
||||
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>
|
||||
@@ -41,18 +66,19 @@ internal static class StaticResource
|
||||
/// <returns>静态资源合约尚未完成</returns>
|
||||
public static bool IsAnyUnfulfilledContractPresent()
|
||||
{
|
||||
return !LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false)
|
||||
|| (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false))
|
||||
|| (!LocalSetting.Get(SettingKeys.StaticResourceV3Contract, false))
|
||||
|| (!LocalSetting.Get(SettingKeys.StaticResourceV4Contract, false));
|
||||
return !LocalSetting.Get(V1Contract, false)
|
||||
|| (!LocalSetting.Get(V2Contract, false))
|
||||
|| (!LocalSetting.Get(V3Contract, false))
|
||||
|| (!LocalSetting.Get(V4Contract, false))
|
||||
|| (!LocalSetting.Get(V5Contract, false));
|
||||
}
|
||||
|
||||
private static void SetContractsState(bool state)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, state);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, state);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, state);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV4Contract, state);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV5Contract, state);
|
||||
LocalSetting.Set(V1Contract, state);
|
||||
LocalSetting.Set(V2Contract, state);
|
||||
LocalSetting.Set(V3Contract, state);
|
||||
LocalSetting.Set(V4Contract, state);
|
||||
LocalSetting.Set(V5Contract, state);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
@@ -24,31 +22,4 @@ internal class ConcurrentCancellationTokenSource
|
||||
source = new();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
private readonly SemaphoreSlim semaphoreSlim;
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed class TaskContext : ITaskContext
|
||||
/// <inheritdoc/>
|
||||
public void InvokeOnMainThread(Action action)
|
||||
{
|
||||
if (dispatcherQueue!.HasThreadAccess)
|
||||
if (dispatcherQueue.HasThreadAccess)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +32,7 @@ internal static class TaskExtension
|
||||
{
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
_ = ex;
|
||||
System.Diagnostics.Debug.WriteLine(ExceptionFormat.Format(ex));
|
||||
System.Diagnostics.Debugger.Break();
|
||||
}
|
||||
}
|
||||
@@ -58,7 +60,7 @@ internal static class TaskExtension
|
||||
}
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -104,7 +106,7 @@ internal static class TaskExtension
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,19 +52,4 @@ internal static class Must
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
|
||||
|
||||
private void InitializeWindow()
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
WindowOptions options = window.WindowOptions;
|
||||
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 can't bring window to top.
|
||||
window.Activate();
|
||||
Persistence.BringToForeground(options.Hwnd);
|
||||
options.BringToForeground();
|
||||
|
||||
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
||||
UpdateSystemBackdrop(appOptions.BackdropType);
|
||||
@@ -182,7 +182,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
|
||||
else
|
||||
{
|
||||
WindowOptions options = window.WindowOptions;
|
||||
double scale = Persistence.GetScaleForWindowHandle(options.Hwnd);
|
||||
double scale = options.GetWindowScale();
|
||||
|
||||
// 48 is the navigation button leftInset
|
||||
RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale);
|
||||
|
||||
@@ -30,7 +30,7 @@ internal static class Persistence
|
||||
WindowOptions options = window.WindowOptions;
|
||||
|
||||
// Set first launch size
|
||||
double scale = GetScaleForWindowHandle(options.Hwnd);
|
||||
double scale = options.GetWindowScale();
|
||||
SizeInt32 transformedSize = options.InitSize.Scale(scale);
|
||||
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)
|
||||
{
|
||||
DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Primary);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.Foundation;
|
||||
using WinRT.Interop;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
@@ -53,4 +54,37 @@ internal readonly struct WindowOptions
|
||||
InitSize = initSize;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,8 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
||||
{
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ internal static partial class EnumerableExtension
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
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})"/>
|
||||
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
|
||||
{
|
||||
Dictionary<TKey, TValue> dictionary = new();
|
||||
|
||||
foreach (TSource value in source)
|
||||
{
|
||||
dictionary[keySelector(value)] = valueSelector(value);
|
||||
dictionary[keySelector(value)] = elementSelector(value);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
|
||||
@@ -31,6 +31,7 @@ internal static partial class EnumerableExtension
|
||||
/// <typeparam name="TSource">源的类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>集合</returns>
|
||||
[Obsolete("Use C# 12 Collection Literal instead")]
|
||||
public static IEnumerable<TSource> Enumerate<TSource>(this TSource source)
|
||||
{
|
||||
yield return source;
|
||||
|
||||
@@ -16,13 +16,12 @@ internal static class ObjectExtension
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>数组</returns>
|
||||
[Obsolete("Use C# 12 Collection Literals")]
|
||||
[Obsolete("Use C# 12 Collection Literals when we migrate")]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] ToArray<T>(this T source)
|
||||
{
|
||||
// TODO: use C# 12 collection literals
|
||||
// [ source ]
|
||||
// and mark this as Obsolete
|
||||
return new[] { source };
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ internal interface IContentDialogFactory
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="content">内容</param>
|
||||
/// <returns>结果</returns>
|
||||
ValueTask<ContentDialogResult> ConfirmAsync(string title, string content);
|
||||
ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content);
|
||||
|
||||
/// <summary>
|
||||
/// 异步确认或取消
|
||||
@@ -26,7 +26,7 @@ internal interface IContentDialogFactory
|
||||
/// <param name="content">内容</param>
|
||||
/// <param name="defaultButton">默认按钮</param>
|
||||
/// <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>
|
||||
/// 异步创建一个新的内容对话框,用于提示未知的进度
|
||||
|
||||
@@ -26,18 +26,35 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
}
|
||||
|
||||
/// <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();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
Title = title,
|
||||
Content = content,
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
|
||||
};
|
||||
|
||||
return await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
Title = title,
|
||||
Content = content,
|
||||
DefaultButton = defaultButton,
|
||||
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
|
||||
CloseButtonText = SH.ContentDialogCancelCloseButtonText,
|
||||
};
|
||||
|
||||
return await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
@@ -54,35 +71,4 @@ internal sealed class ContentDialogFactory : IContentDialogFactory
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
|
||||
@@ -10,21 +10,14 @@ namespace Snap.Hutao.Factory;
|
||||
|
||||
/// <inheritdoc cref="IPickerFactory"/>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IPickerFactory))]
|
||||
internal class PickerFactory : IPickerFactory
|
||||
internal sealed partial class PickerFactory : IPickerFactory
|
||||
{
|
||||
private const string AnyType = "*";
|
||||
|
||||
private readonly MainWindow mainWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的文件选择器工厂
|
||||
/// </summary>
|
||||
/// <param name="mainWindow">主窗体的引用注入</param>
|
||||
public PickerFactory(MainWindow mainWindow)
|
||||
{
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
|
||||
@@ -40,7 +33,7 @@ internal class PickerFactory : IPickerFactory
|
||||
}
|
||||
|
||||
// below Windows 11
|
||||
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 13))
|
||||
if (!runtimeOptions.Windows11OrHigher)
|
||||
{
|
||||
// https://github.com/microsoft/WindowsAppSDK/issues/2931
|
||||
picker.FileTypeFilter.Add(AnyType);
|
||||
@@ -72,7 +65,7 @@ internal class PickerFactory : IPickerFactory
|
||||
FolderPicker picker = GetInitializedPicker<FolderPicker>();
|
||||
|
||||
// below Windows 11
|
||||
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 13))
|
||||
if (!runtimeOptions.Windows11OrHigher)
|
||||
{
|
||||
// https://github.com/microsoft/WindowsAppSDK/issues/2931
|
||||
picker.FileTypeFilter.Add(AnyType);
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
@@ -13,7 +15,11 @@ namespace Snap.Hutao.Model.Calculable;
|
||||
/// 可计算角色
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
internal sealed class CalculableAvatar
|
||||
: ObservableObject,
|
||||
ICalculableAvatar,
|
||||
IMappingFrom<CalculableAvatar, Avatar>,
|
||||
IMappingFrom<CalculableAvatar, AvatarView>
|
||||
{
|
||||
private uint levelCurrent;
|
||||
private uint levelTarget;
|
||||
@@ -22,7 +28,7 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
/// 构造一个新的可计算角色
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
public CalculableAvatar(Metadata.Avatar.Avatar avatar)
|
||||
private CalculableAvatar(Avatar avatar)
|
||||
{
|
||||
AvatarId = avatar.Id;
|
||||
LevelMin = 1;
|
||||
@@ -40,7 +46,7 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
/// 构造一个新的可计算角色
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
public CalculableAvatar(AvatarView avatar)
|
||||
private CalculableAvatar(AvatarView avatar)
|
||||
{
|
||||
AvatarId = avatar.Id;
|
||||
LevelMin = avatar.LevelNumber;
|
||||
@@ -80,4 +86,14 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,18 @@ namespace Snap.Hutao.Model.Calculable;
|
||||
/// <summary>
|
||||
/// 可计算物品选项
|
||||
/// </summary>
|
||||
internal sealed class CalculableOptions
|
||||
internal readonly struct CalculableOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public readonly ICalculableAvatar? Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public readonly ICalculableWeapon? Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的可计算物品选项
|
||||
/// </summary>
|
||||
@@ -18,14 +28,4 @@ internal sealed class CalculableOptions
|
||||
Avatar = avatar;
|
||||
Weapon = weapon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public ICalculableAvatar? Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public ICalculableWeapon? Weapon { get; }
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
@@ -14,7 +15,11 @@ namespace Snap.Hutao.Model.Calculable;
|
||||
/// 可计算的技能
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
|
||||
internal sealed class CalculableSkill
|
||||
: ObservableObject,
|
||||
ICalculableSkill,
|
||||
IMappingFrom<CalculableSkill, ProudableSkill>,
|
||||
IMappingFrom<CalculableSkill, SkillView>
|
||||
{
|
||||
private uint levelCurrent;
|
||||
private uint levelTarget;
|
||||
@@ -23,7 +28,7 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
|
||||
/// 构造一个新的可计算的技能
|
||||
/// </summary>
|
||||
/// <param name="skill">技能</param>
|
||||
public CalculableSkill(ProudableSkill skill)
|
||||
private CalculableSkill(ProudableSkill skill)
|
||||
{
|
||||
GroupId = skill.GroupId;
|
||||
LevelMin = 1;
|
||||
@@ -40,7 +45,7 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
|
||||
/// 构造一个新的可计算的技能
|
||||
/// </summary>
|
||||
/// <param name="skill">技能</param>
|
||||
public CalculableSkill(SkillView skill)
|
||||
private CalculableSkill(SkillView skill)
|
||||
{
|
||||
GroupId = skill.GroupId;
|
||||
LevelMin = skill.LevelNumber;
|
||||
@@ -76,4 +81,14 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
@@ -13,7 +15,11 @@ namespace Snap.Hutao.Model.Calculable;
|
||||
/// 可计算武器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal class CalculableWeapon : ObservableObject, ICalculableWeapon
|
||||
internal sealed class CalculableWeapon
|
||||
: ObservableObject,
|
||||
ICalculableWeapon,
|
||||
IMappingFrom<CalculableWeapon, Weapon>,
|
||||
IMappingFrom<CalculableWeapon, WeaponView>
|
||||
{
|
||||
private uint levelCurrent;
|
||||
private uint levelTarget;
|
||||
@@ -22,7 +28,7 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
|
||||
/// 构造一个新的可计算武器
|
||||
/// </summary>
|
||||
/// <param name="weapon">武器</param>
|
||||
public CalculableWeapon(Metadata.Weapon.Weapon weapon)
|
||||
private CalculableWeapon(Weapon weapon)
|
||||
{
|
||||
WeaponId = weapon.Id;
|
||||
LevelMin = 1;
|
||||
@@ -39,7 +45,7 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
|
||||
/// 构造一个新的可计算武器
|
||||
/// </summary>
|
||||
/// <param name="weapon">武器</param>
|
||||
public CalculableWeapon(WeaponView weapon)
|
||||
private CalculableWeapon(WeaponView weapon)
|
||||
{
|
||||
WeaponId = weapon.Id;
|
||||
LevelMin = weapon.LevelNumber;
|
||||
@@ -75,4 +81,14 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
@@ -14,7 +15,11 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("achievements")]
|
||||
internal sealed class Achievement : IEquatable<Achievement>
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class Achievement
|
||||
: IEquatable<Achievement>,
|
||||
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -57,14 +62,14 @@ internal sealed class Achievement : IEquatable<Achievement>
|
||||
/// <summary>
|
||||
/// 创建一个新的成就
|
||||
/// </summary>
|
||||
/// <param name="userId">对应的用户id</param>
|
||||
/// <param name="archiveId">对应的用户id</param>
|
||||
/// <param name="id">成就Id</param>
|
||||
/// <returns>新创建的成就</returns>
|
||||
public static Achievement Create(in Guid userId, in AchievementId id)
|
||||
public static Achievement From(in Guid archiveId, in AchievementId id)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ArchiveId = userId,
|
||||
ArchiveId = archiveId,
|
||||
Id = id,
|
||||
Current = 0,
|
||||
Time = DateTimeOffset.MinValue,
|
||||
@@ -77,7 +82,7 @@ internal sealed class Achievement : IEquatable<Achievement>
|
||||
/// <param name="userId">对应的用户id</param>
|
||||
/// <param name="uiaf">uiaf项</param>
|
||||
/// <returns>新创建的成就</returns>
|
||||
public static Achievement Create(in Guid userId, UIAFItem uiaf)
|
||||
public static Achievement From(in Guid userId, in UIAFItem uiaf)
|
||||
{
|
||||
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/>
|
||||
public bool Equals(Achievement? other)
|
||||
{
|
||||
@@ -121,6 +111,8 @@ internal sealed class Achievement : IEquatable<Achievement>
|
||||
}
|
||||
}
|
||||
|
||||
#region Object
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
@@ -132,4 +124,5 @@ internal sealed class Achievement : IEquatable<Achievement>
|
||||
{
|
||||
return HashCode.Combine(ArchiveId, Id, Current, Status, Time);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// 成就存档
|
||||
/// </summary>
|
||||
[Table("achievement_archives")]
|
||||
internal sealed class AchievementArchive : ISelectable
|
||||
internal sealed class AchievementArchive : ISelectable, IMappingFrom<AchievementArchive, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -35,7 +36,7 @@ internal sealed class AchievementArchive : ISelectable
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>新存档</returns>
|
||||
public static AchievementArchive Create(string name)
|
||||
public static AchievementArchive From(string name)
|
||||
{
|
||||
return new() { Name = name };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -11,10 +12,10 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("avatar_infos")]
|
||||
internal sealed class AvatarInfo
|
||||
internal sealed class AvatarInfo : IMappingFrom<AvatarInfo, string, Web.Enka.Model.AvatarInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// 内部 Id
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
@@ -36,7 +37,7 @@ internal sealed class AvatarInfo
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="info">角色信息</param>
|
||||
/// <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 };
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ internal sealed class AvatarInfoConfiguration : IEntityTypeConfiguration<AvatarI
|
||||
public void Configure(EntityTypeBuilder<AvatarInfo> builder)
|
||||
{
|
||||
builder.Property(e => e.Info)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion<JsonTextValueConverter<Web.Enka.Model.AvatarInfo>>();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ internal sealed class DailyNoteEntryConfiguration : IEntityTypeConfiguration<Dai
|
||||
public void Configure(EntityTypeBuilder<DailyNoteEntry> builder)
|
||||
{
|
||||
builder.Property(e => e.DailyNote)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote>>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ internal sealed class InventoryReliquaryConfiguration : IEntityTypeConfiguration
|
||||
public void Configure(EntityTypeBuilder<InventoryReliquary> builder)
|
||||
{
|
||||
builder.Property(e => e.AppendPropIdList)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion(
|
||||
list => list.ToString(','),
|
||||
text => text.Split(',', StringSplitOptions.None).Select(int.Parse).ToList());
|
||||
|
||||
@@ -16,7 +16,7 @@ internal sealed class SpiralAbyssEntryConfiguration : IEntityTypeConfiguration<S
|
||||
public void Configure(EntityTypeBuilder<SpiralAbyssEntry> builder)
|
||||
{
|
||||
builder.Property(e => e.SpiralAbyss)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss>>();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -17,15 +17,15 @@ internal sealed class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder.Property(e => e.CookieToken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
|
||||
builder.Property(e => e.LToken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
|
||||
builder.Property(e => e.SToken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnType(SqliteTypeNames.Text)
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@@ -12,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("cultivate_entries")]
|
||||
internal sealed class CultivateEntry
|
||||
internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry, CultivateType, uint>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -49,7 +50,7 @@ internal sealed class CultivateEntry
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="id">主Id</param>
|
||||
/// <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()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("cultivate_items")]
|
||||
internal sealed class CultivateItem
|
||||
internal sealed class CultivateItem : IDbMappingForeignKeyFrom<CultivateItem, Web.Hoyolab.Takumi.Event.Calculate.Item>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -50,16 +51,15 @@ internal sealed class CultivateItem
|
||||
/// 创建一个新的养成物品
|
||||
/// </summary>
|
||||
/// <param name="entryId">入口点 Id</param>
|
||||
/// <param name="itemId">物品 Id</param>
|
||||
/// <param name="count">个数</param>
|
||||
/// <param name="item">物品</param>
|
||||
/// <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()
|
||||
{
|
||||
EntryId = entryId,
|
||||
ItemId = itemId,
|
||||
Count = count,
|
||||
ItemId = item.Id,
|
||||
Count = item.Num,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@@ -12,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("cultivate_projects")]
|
||||
internal sealed class CultivateProject : ISelectable
|
||||
internal sealed class CultivateProject : ISelectable, IMappingFrom<CultivateProject, string, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -42,7 +43,7 @@ internal sealed class CultivateProject : ISelectable
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="attachedUid">绑定的Uid</param>
|
||||
/// <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 };
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
@@ -15,7 +16,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("daily_notes")]
|
||||
internal sealed class DailyNoteEntry : ObservableObject
|
||||
internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteEntry, UserAndUid>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -106,7 +107,7 @@ internal sealed class DailyNoteEntry : ObservableObject
|
||||
/// </summary>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <returns>新的实时便笺</returns>
|
||||
public static DailyNoteEntry Create(UserAndUid userAndUid)
|
||||
public static DailyNoteEntry From(UserAndUid userAndUid)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
@@ -17,7 +18,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("gacha_archives")]
|
||||
internal sealed class GachaArchive : ISelectable
|
||||
internal sealed class GachaArchive : ISelectable, IMappingFrom<GachaArchive, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -39,7 +40,7 @@ internal sealed class GachaArchive : ISelectable
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <returns>新的卡池存档</returns>
|
||||
public static GachaArchive Create(string uid)
|
||||
public static GachaArchive From(string uid)
|
||||
{
|
||||
return new() { Uid = uid };
|
||||
}
|
||||
@@ -69,7 +70,7 @@ internal sealed class GachaArchive : ISelectable
|
||||
|
||||
if (archive == null)
|
||||
{
|
||||
GachaArchive created = Create(context.Uid);
|
||||
GachaArchive created = From(context.Uid);
|
||||
context.GachaArchives.AddAndSave(created);
|
||||
context.TaskContext.InvokeOnMainThread(() => context.ArchiveCollection.Add(created));
|
||||
archive = created;
|
||||
|
||||
@@ -51,7 +51,7 @@ internal sealed class UIAFInfo
|
||||
/// <returns>专用 UIAF 信息</returns>
|
||||
public static UIAFInfo Create(IServiceProvider serviceProvider)
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
@@ -8,7 +9,7 @@ namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
/// <summary>
|
||||
/// UIAF 项
|
||||
/// </summary>
|
||||
internal sealed class UIAFItem
|
||||
internal sealed class UIAFItem : IMappingFrom<UIAFItem, Entity.Achievement>
|
||||
{
|
||||
/// <summary>
|
||||
/// 成就Id
|
||||
@@ -34,4 +35,15 @@ internal sealed class UIAFItem
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ internal sealed class UIGFInfo
|
||||
/// <returns>专用 UIGF 信息</returns>
|
||||
public static UIGFInfo Create(IServiceProvider serviceProvider, string uid)
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@ internal sealed class UIIFInfo
|
||||
/// <returns>专用 UIGF 信息</returns>
|
||||
public static UIIFInfo Create(IServiceProvider serviceProvider, string uid)
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IName
|
||||
/// <inheritdoc/>
|
||||
public ICalculableAvatar ToCalculable()
|
||||
{
|
||||
return new CalculableAvatar(this);
|
||||
return CalculableAvatar.From(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,6 +13,6 @@ internal sealed partial class ProudableSkill : ICalculableSource<ICalculableSkil
|
||||
/// <inheritdoc/>
|
||||
public ICalculableSkill ToCalculable()
|
||||
{
|
||||
return new CalculableSkill(this);
|
||||
return CalculableSkill.From(this);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
|
||||
/// <inheritdoc/>
|
||||
public ICalculableWeapon ToCalculable()
|
||||
{
|
||||
return new CalculableWeapon(this);
|
||||
return CalculableWeapon.From(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -63,7 +63,7 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
|
||||
if (entity is null && uiaf is not null)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf));
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
if (aggressive)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(entity);
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf));
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
update++;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
moveEntity = false;
|
||||
moveUIAF = true;
|
||||
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf));
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed partial class AchievementService
|
||||
case ImportStrategy.Overwrite:
|
||||
{
|
||||
IEnumerable<EntityAchievement> orederedUIAF = list
|
||||
.Select(uiaf => EntityAchievement.Create(archiveId, uiaf))
|
||||
.Select(uiaf => EntityAchievement.From(archiveId, uiaf))
|
||||
.OrderBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF);
|
||||
}
|
||||
@@ -58,7 +58,7 @@ internal sealed partial class AchievementService
|
||||
List<UIAFItem> list = appDbContext.Achievements
|
||||
.Where(i => i.ArchiveId == archive.InnerId)
|
||||
.AsEnumerable()
|
||||
.Select(i => i.ToUIAFItem())
|
||||
.Select(UIAFItem.From)
|
||||
.ToList();
|
||||
|
||||
return new()
|
||||
|
||||
@@ -52,7 +52,7 @@ internal sealed partial class AchievementService : IAchievementService
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
{
|
||||
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
|
||||
transformer.Transform(ref avatarInfo, source);
|
||||
entity = ModelAvatarInfo.Create(uid, avatarInfo);
|
||||
entity = ModelAvatarInfo.From(uid, avatarInfo);
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
}
|
||||
else
|
||||
@@ -221,7 +221,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
entity = ModelAvatarInfo.Create(uid, webInfo);
|
||||
entity = ModelAvatarInfo.From(uid, webInfo);
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -222,14 +222,14 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
entry = CultivateEntry.Create(projectId, type, itemId);
|
||||
entry = CultivateEntry.From(projectId, type, itemId);
|
||||
await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Guid entryId = entry.InnerId;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
|
||||
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
|
||||
.PickRequiredService<IGameRecordClient>(PlayerUid.IsOversea(roleUid))
|
||||
|
||||
@@ -76,7 +76,7 @@ internal sealed partial class HutaoCloudService : IHutaoCloudService
|
||||
{
|
||||
if (archive == null)
|
||||
{
|
||||
archive = Model.Entity.GachaArchive.Create(uid);
|
||||
archive = Model.Entity.GachaArchive.From(uid);
|
||||
await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ internal sealed partial class GameService : IGameService
|
||||
private readonly PackageConverter packageConverter;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly HutaoOptions hutaoOptions;
|
||||
private readonly RuntimeOptions hutaoOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
@@ -302,7 +302,7 @@ internal sealed partial class GameService : IGameService
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask DetectGameAccountAsync()
|
||||
{
|
||||
Must.NotNull(gameAccounts!);
|
||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||
|
||||
string? registrySdk = RegistryInterop.Get();
|
||||
if (!string.IsNullOrEmpty(registrySdk))
|
||||
@@ -350,7 +350,7 @@ internal sealed partial class GameService : IGameService
|
||||
/// <inheritdoc/>
|
||||
public GameAccount? DetectCurrentGameAccount()
|
||||
{
|
||||
Must.NotNull(gameAccounts!);
|
||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||
|
||||
string? registrySdk = RegistryInterop.Get();
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ internal sealed partial class PackageConverter
|
||||
}
|
||||
|
||||
// Cache folder
|
||||
Core.HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<Core.HutaoOptions>();
|
||||
Core.RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<Core.RuntimeOptions>();
|
||||
string cacheFolder = Path.Combine(hutaoOptions.DataFolder, "ServerCache");
|
||||
|
||||
// 执行下载与移动操作
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
|
||||
|
||||
private readonly AppOptions appOptions;
|
||||
private readonly HutaoOptions hutaoOptions;
|
||||
private readonly RuntimeOptions hutaoOptions;
|
||||
|
||||
private string? localeName;
|
||||
private string? fallbackDataFolder;
|
||||
|
||||
@@ -43,7 +43,8 @@ internal sealed partial class MetadataService
|
||||
|
||||
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);
|
||||
|
||||
@@ -167,7 +167,8 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
|
||||
|
||||
if (memoryCache.TryGetValue(cacheKey, out object? value))
|
||||
{
|
||||
return Must.NotNull((T)value!);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json");
|
||||
@@ -176,7 +177,8 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
|
||||
using (Stream fileStream = File.OpenRead(path))
|
||||
{
|
||||
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
|
||||
@@ -193,11 +195,12 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
|
||||
|
||||
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);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryOverride(keySelector);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items
|
||||
return memoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
@@ -208,11 +211,12 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,8 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
|
||||
navigationView.PaneOpened -= OnPaneStateChanged;
|
||||
}
|
||||
|
||||
navigationView = Must.NotNull(value!);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
navigationView = value;
|
||||
|
||||
// add new listener
|
||||
if (navigationView != null)
|
||||
|
||||
@@ -42,7 +42,7 @@ internal sealed partial class CultivateProjectDialog : ContentDialog
|
||||
? Ioc.Default.GetRequiredService<IUserService>().Current?.SelectedUserGameRole?.GameUid
|
||||
: null;
|
||||
|
||||
return new(true, CultivateProject.Create(text, uid));
|
||||
return new(true, CultivateProject.From(text, uid));
|
||||
}
|
||||
|
||||
return new(false, null!);
|
||||
|
||||
@@ -28,7 +28,7 @@ internal sealed partial class TitleView : UserControl
|
||||
{
|
||||
get
|
||||
{
|
||||
Core.HutaoOptions hutaoOptions = Ioc.Default.GetRequiredService<Core.HutaoOptions>();
|
||||
Core.RuntimeOptions hutaoOptions = Ioc.Default.GetRequiredService<Core.RuntimeOptions>();
|
||||
|
||||
string format =
|
||||
#if DEBUG
|
||||
|
||||
@@ -202,7 +202,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ArchiveAddResult result = await achievementService.TryAddArchiveAsync(EntityAchievementArchive.Create(name)).ConfigureAwait(false);
|
||||
ArchiveAddResult result = await achievementService.TryAddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
@@ -230,7 +230,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
if (Archives != null && SelectedArchive != null)
|
||||
{
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
.ConfirmCancelAsync(
|
||||
.CreateForConfirmCancelAsync(
|
||||
string.Format(SH.ViewModelAchievementRemoveArchiveTitle, SelectedArchive.Name),
|
||||
SH.ViewModelAchievementRemoveArchiveContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -106,6 +106,6 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
|
||||
/// <inheritdoc/>
|
||||
public ICalculableAvatar ToCalculable()
|
||||
{
|
||||
return new CalculableAvatar(this);
|
||||
return CalculableAvatar.From(this);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,6 @@ internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalcul
|
||||
/// <inheritdoc/>
|
||||
public ICalculableSkill ToCalculable()
|
||||
{
|
||||
return new CalculableSkill(this);
|
||||
return CalculableSkill.From(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
|
||||
/// <inheritdoc/>
|
||||
public ICalculableWeapon ToCalculable()
|
||||
{
|
||||
return new CalculableWeapon(this);
|
||||
return CalculableWeapon.From(this);
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,14 @@ internal sealed partial class ExperimentalFeaturesViewModel : ObservableObject
|
||||
[Command("OpenCacheFolderCommand")]
|
||||
private Task OpenCacheFolderAsync()
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
return Launcher.LaunchFolderPathAsync(hutaoOptions.LocalCache).AsTask();
|
||||
}
|
||||
|
||||
[Command("OpenDataFolderCommand")]
|
||||
private Task OpenDataFolderAsync()
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
return Launcher.LaunchFolderPathAsync(hutaoOptions.DataFolder).AsTask();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ internal sealed partial class ExperimentalFeaturesViewModel : ObservableObject
|
||||
{
|
||||
ContentDialogResult result = await scope.ServiceProvider
|
||||
.GetRequiredService<IContentDialogFactory>()
|
||||
.ConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
|
||||
.CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
|
||||
@@ -223,7 +223,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
}
|
||||
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)
|
||||
{
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
.ConfirmCancelAsync(string.Format(SH.ViewModelGachaLogRemoveArchiveTitle, SelectedArchive.Uid), SH.ViewModelGachaLogRemoveArchiveDescription)
|
||||
.CreateForConfirmCancelAsync(string.Format(SH.ViewModelGachaLogRemoveArchiveTitle, SelectedArchive.Uid), SH.ViewModelGachaLogRemoveArchiveDescription)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly HutaoOptions hutaoOptions;
|
||||
private readonly RuntimeOptions hutaoOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IGameService gameService;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
@@ -86,7 +86,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
/// <summary>
|
||||
/// 胡桃选项
|
||||
/// </summary>
|
||||
public HutaoOptions HutaoOptions { get => hutaoOptions; }
|
||||
public RuntimeOptions HutaoOptions { get => hutaoOptions; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用选项
|
||||
|
||||
@@ -39,7 +39,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
private readonly IGameService gameService;
|
||||
private readonly ILogger<SettingViewModel> logger;
|
||||
private readonly AppOptions options;
|
||||
private readonly HutaoOptions hutaoOptions;
|
||||
private readonly RuntimeOptions hutaoOptions;
|
||||
private readonly HutaoUserOptions hutaoUserOptions;
|
||||
private readonly ExperimentalFeaturesViewModel experimental;
|
||||
|
||||
@@ -54,7 +54,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
/// <summary>
|
||||
/// 胡桃选项
|
||||
/// </summary>
|
||||
public HutaoOptions HutaoOptions { get => hutaoOptions; }
|
||||
public RuntimeOptions HutaoOptions { get => hutaoOptions; }
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃用户选项
|
||||
|
||||
@@ -25,7 +25,7 @@ internal sealed partial class UserViewModel : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly Core.HutaoOptions hutaoOptions;
|
||||
private readonly Core.RuntimeOptions hutaoOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IUserService userService;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
|
||||
{
|
||||
Dictionary<string, DownloadSummary> downloadSummaries = new();
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV1Contract))
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V1Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("Bg", new(serviceProvider, "Bg"));
|
||||
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
|
||||
@@ -83,7 +83,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
|
||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV2Contract))
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V2Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("AchievementIcon", new(serviceProvider, "AchievementIcon"));
|
||||
downloadSummaries.TryAdd("ItemIcon", new(serviceProvider, "ItemIcon"));
|
||||
@@ -91,18 +91,18 @@ internal sealed partial class WelcomeViewModel : ObservableObject
|
||||
downloadSummaries.TryAdd("RelicIcon", new(serviceProvider, "RelicIcon"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV3Contract))
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V3Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill"));
|
||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV4Contract))
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V4Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV5Contract))
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V5Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("MonsterIcon", new(serviceProvider, "MonsterIcon"));
|
||||
}
|
||||
@@ -134,7 +134,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
|
||||
{
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
httpClient = serviceProvider.GetRequiredService<HttpClient>();
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
@@ -45,7 +45,7 @@ internal sealed class HomaLogUploadClient
|
||||
|
||||
private static HutaoLog BuildFromException(IServiceProvider serviceProvider, Exception exception)
|
||||
{
|
||||
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
return new()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user