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()
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
logger.LogInformation("FamilyName: {name}", hutaoOptions.FamilyName);
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
{
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>>();

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!;
}
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache;
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
cacheFolder = info.FullName;

View File

@@ -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}";

View File

@@ -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);

View File

@@ -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)

View File

@@ -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()
{

View File

@@ -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; }

View File

@@ -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
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

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
{
private readonly SemaphoreSlim semaphoreSlim;

View File

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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 };
}
}

View File

@@ -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>
/// 异步创建一个新的内容对话框,用于提示未知的进度

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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; }
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

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.
// 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
}

View File

@@ -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 };
}

View File

@@ -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 };
}

View File

@@ -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>>();
}
}

View File

@@ -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>>();
}
}

View File

@@ -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());

View File

@@ -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>>();
}
}

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)
{
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));
}
}

View File

@@ -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()
{

View File

@@ -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,
};
}
}

View File

@@ -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 };
}

View File

@@ -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()
{

View File

@@ -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;

View File

@@ -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()
{

View File

@@ -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(),
};
}
}

View File

@@ -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()
{

View File

@@ -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()
{

View File

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

View File

@@ -13,6 +13,6 @@ internal sealed partial class ProudableSkill : ICalculableSource<ICalculableSkil
/// <inheritdoc/>
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/>
public ICalculableWeapon ToCalculable()
{
return new CalculableWeapon(this);
return CalculableWeapon.From(this);
}
/// <summary>

View File

@@ -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++;
}
}

View File

@@ -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()

View File

@@ -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);
});
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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))

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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");
// 执行下载与移动操作

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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!);

View File

@@ -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

View File

@@ -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);

View File

@@ -106,6 +106,6 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <inheritdoc/>
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/>
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/>
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")]
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)

View File

@@ -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)

View File

@@ -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>
/// 应用选项

View File

@@ -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>
/// 胡桃用户选项

View File

@@ -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;

View File

@@ -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;

View File

@@ -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()
{