mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee4197a18a | ||
|
|
c90e1ab8b8 | ||
|
|
de40947a7f | ||
|
|
542a0a4622 | ||
|
|
a718ba16e2 | ||
|
|
c0d670c5b6 | ||
|
|
efcf0620d7 | ||
|
|
a8bc0cf9b2 | ||
|
|
f1dda029e8 | ||
|
|
d8b77369aa | ||
|
|
0be84a2585 | ||
|
|
e29e12c9fe | ||
|
|
283df388bb | ||
|
|
971f319b76 | ||
|
|
58b34ea60a | ||
|
|
a150c4a04c | ||
|
|
9205d51cd5 | ||
|
|
a66a0b8a23 | ||
|
|
17c53dce4c | ||
|
|
5049aa9cb6 | ||
|
|
aac1e62fd2 | ||
|
|
5c1f861956 | ||
|
|
9667917559 | ||
|
|
9a3183e917 |
65
.github/ISSUE_TEMPLATE/artifact-rating-rules.yml
vendored
Normal file
65
.github/ISSUE_TEMPLATE/artifact-rating-rules.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Artifact Rating Rules
|
||||
description: 圣遗物评分细则建议
|
||||
title: "[Artifact Rating] 请在这里填写角色名称"
|
||||
labels: area-AvatarInfo
|
||||
assignees: Lightczx
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
请按下方的要求填写完整的问题表单
|
||||
|
||||
- type: textarea
|
||||
id: your-suggested-rule
|
||||
attributes:
|
||||
label: 评分细则
|
||||
description: |
|
||||
请修改下方表格中的**角色名称**和**各属性权重**,并在表格后添加合适的说明
|
||||
你可以点击预览按钮(preview)来查看表格最终会显示出的内容
|
||||
value: |
|
||||
|项目|评分权重(0-100)|
|
||||
|-----|-----|
|
||||
|角色名称| 旅行者 |
|
||||
|生命值| 10 |
|
||||
|攻击力| 10 |
|
||||
|防御力| 10 |
|
||||
|暴击率| 10 |
|
||||
|暴击伤害| 10 |
|
||||
|元素精通| 10 |
|
||||
|充能效率| 10 |
|
||||
|治疗加成| 10 |
|
||||
|元素伤害| 10 |
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: no-duplicated-dropdown
|
||||
attributes:
|
||||
label: 我确认当前没有其它的该角色的圣遗物评分细则建议
|
||||
description: 如果有,你应该在已有的工单内回复以提出你的建议
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: title-filled-dropdown
|
||||
attributes:
|
||||
label: 我确认已设置合适的标题
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: all-filled-dropdown
|
||||
attributes:
|
||||
label: 我确认已完整填写表格
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
22
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
22
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -54,18 +54,18 @@ body:
|
||||
如果应用程序崩溃了,请将`log.db` 文件上传,文件包含了敏感信息,谨慎上传
|
||||
如果这个表单是关于导入祈愿记录的问题,请包含你导入的`Json`文件
|
||||
**务必不要上传`user.db`文件,该文件包含你的帐号敏感信息**
|
||||
render: shell
|
||||
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: confirm-issue
|
||||
attributes:
|
||||
label: 我确认已在表单中附上了充足的补充说明以帮助开发人员确定问题
|
||||
description: 补充说明包括但不限于:日志文件、抛出的错误信息、截图和录屏
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: confirm-no-duplicated-issue
|
||||
attributes:
|
||||
label: 我确认没有他人提出相同或类似的问题
|
||||
@@ -75,15 +75,17 @@ body:
|
||||
你应该在原始 Issue 中通过回复添加有助于解决问题的信息,而不是创建重复的问题;
|
||||
**没有帮助的重复问题可能会被直接关闭**
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: confirm-docs
|
||||
attributes:
|
||||
label: 我确认该问题没有在文档中解释
|
||||
description: Snap Hutao 官方文档:[https://hut.ao](https://hut.ao)
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/workflows/PublishDistribution.yml
vendored
6
.github/workflows/PublishDistribution.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
|
||||
# Download Publish.zip
|
||||
- name: Download Release
|
||||
timeout-minutes: 5
|
||||
uses: robinraju/release-downloader@v1.5
|
||||
with:
|
||||
repository: "DGP-Studio/Snap.Hutao"
|
||||
@@ -25,8 +26,9 @@ jobs:
|
||||
fileName: "*.zip"
|
||||
out-file-path: ./release-download
|
||||
|
||||
# Upload to OD21 (Testing)
|
||||
- name: Upload OD21
|
||||
# Upload to Drive
|
||||
- name: Upload Drive
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
RCCONF: ${{ secrets.RCCONF }}
|
||||
run: |
|
||||
|
||||
6
desktop.ini
Normal file
6
desktop.ini
Normal file
@@ -0,0 +1,6 @@
|
||||
[.ShellClassInfo]
|
||||
IconResource=D:\Develop\Projects\Snap.Hutao\src\Snap.Hutao\Snap.Hutao\Assets\Logo.ico,0
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
@@ -102,8 +102,10 @@ dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_diagnostic.SA1629.severity = silent
|
||||
dotnet_diagnostic.SA1642.severity = silent
|
||||
dotnet_diagnostic.SA1629.severity = none
|
||||
dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
|
||||
# SA1208: System using directives should be placed before other using directives
|
||||
dotnet_diagnostic.SA1208.severity = none
|
||||
@@ -160,6 +162,7 @@ dotnet_diagnostic.CA1805.severity = suggestion
|
||||
# VSTHRD111: Use ConfigureAwait(bool)
|
||||
dotnet_diagnostic.VSTHRD111.severity = suggestion
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-windows10.0.17763.0</TargetFrameworks>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<TargetFrameworks>net7.0-windows10.0.18362.0</TargetFrameworks>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>SettingsUI</RootNamespace>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
|
||||
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.4" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,6 +13,7 @@ internal class Program
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
_ = args;
|
||||
string ps1File = Path.Combine(AppContext.BaseDirectory, "Install.ps1");
|
||||
|
||||
if (!File.Exists(ps1File))
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<DebugType>embedded</DebugType>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -22,8 +22,11 @@ public class HttpClientGenerator : ISourceGenerator
|
||||
{
|
||||
private const string DefaultName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.Default";
|
||||
private const string XRpcName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.XRpc";
|
||||
private const string XRpc2Name = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.XRpc2";
|
||||
|
||||
private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute";
|
||||
private const string DynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute";
|
||||
private const string IgnoreSetCookieAttributeName = "Snap.Hutao.Web.Hoyolab.Annotation.IgnoreSetCookieAttribute";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
@@ -44,12 +47,15 @@ public class HttpClientGenerator : ISourceGenerator
|
||||
string toolName = this.GetGeneratorType().FullName;
|
||||
|
||||
StringBuilder sourceCodeBuilder = new();
|
||||
|
||||
sourceCodeBuilder.Append($@"// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// This class is generated by Snap.Hutao.SourceGeneration
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
@@ -100,6 +106,9 @@ internal static partial class IocHttpClientConfiguration
|
||||
case XRpcName:
|
||||
lineBuilder.Append(@"XRpcConfiguration)");
|
||||
break;
|
||||
case XRpc2Name:
|
||||
lineBuilder.Append(@"XRpc2Configuration)");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"非法的HttpClientConfigration值: [{injectAsName}]");
|
||||
}
|
||||
@@ -125,6 +134,16 @@ internal static partial class IocHttpClientConfiguration
|
||||
lineBuilder.Append(" })");
|
||||
}
|
||||
|
||||
if (classSymbol.GetAttributes().Any(attr => attr.AttributeClass!.ToDisplayString() == DynamicSecretAttributeName))
|
||||
{
|
||||
lineBuilder.Append(".AddHttpMessageHandler<DynamicSecretHandler>()");
|
||||
}
|
||||
|
||||
if (classSymbol.GetAttributes().Any(attr => attr.AttributeClass!.ToDisplayString() == IgnoreSetCookieAttributeName))
|
||||
{
|
||||
lineBuilder.Append(".AddHttpMessageHandler<IgnoreSetCookieHandler>()");
|
||||
}
|
||||
|
||||
lineBuilder.Append(";");
|
||||
|
||||
lines.Add(lineBuilder.ToString());
|
||||
|
||||
@@ -52,7 +52,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
internal static partial class ServiceCollectionExtensions
|
||||
internal static partial class ServiceCollectionExtension
|
||||
{{
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{toolName}"",""1.0.0.0"")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
@@ -65,7 +65,7 @@ internal static partial class ServiceCollectionExtensions
|
||||
}
|
||||
}");
|
||||
|
||||
context.AddSource("ServiceCollectionExtensions.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
|
||||
context.AddSource("ServiceCollectionExtension.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
|
||||
}
|
||||
|
||||
private static void FillWithInjectionServices(InjectionSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
|
||||
|
||||
@@ -14,7 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\Se
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Installer", "Snap.Hutao.Installer\Snap.Hutao.Installer.csproj", "{CEC01691-F65E-4874-9AE2-F571369A7631}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Installer", "Snap.Hutao.Installer\Snap.Hutao.Installer.csproj", "{CEC01691-F65E-4874-9AE2-F571369A7631}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -88,8 +88,8 @@ Global
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.Build.0 = Debug|x64
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/.filenesting.json
Normal file
31
src/Snap.Hutao/Snap.Hutao/.filenesting.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"help": "https://go.microsoft.com/fwlink/?linkid=866610",
|
||||
"dependentFileProviders": {
|
||||
"add": {
|
||||
"extensionToExtension": {
|
||||
"add": {
|
||||
".json": [ ".txt" ]
|
||||
}
|
||||
},
|
||||
"pathSegment": {
|
||||
"add": {
|
||||
".*": [ ".cs" ]
|
||||
}
|
||||
},
|
||||
"fileSuffixToExtension": {
|
||||
"add": {
|
||||
"DesignTimeFactory.cs": [".cs"]
|
||||
}
|
||||
},
|
||||
"fileToFile": {
|
||||
"add": {
|
||||
"app.manifest": [ "App.xaml.cs" ],
|
||||
"Package.appxmanifest": [ "App.xaml.cs" ],
|
||||
"GlobalUsing.cs": [ "Program.cs" ],
|
||||
".filenesting.json": [ "Program.cs" ],
|
||||
".editorconfig": [ "Program.cs" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Exception;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage;
|
||||
|
||||
@@ -48,17 +47,12 @@ public partial class App : Application
|
||||
// manually invoke
|
||||
Activation.Activate(firstInstance, activatedEventArgs);
|
||||
firstInstance.Activated += Activation.Activate;
|
||||
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Snap Hutao : {version}", CoreEnvironment.Version);
|
||||
logger.LogInformation(EventIds.CommonLog, "Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
|
||||
|
||||
JumpListHelper.ConfigAsync().SafeForget(logger);
|
||||
|
||||
Ioc.Default
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.ImplictAs<IMetadataInitializer>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget(logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
BIN
src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -61,6 +61,11 @@ public class AppDbContext : DbContext
|
||||
/// </summary>
|
||||
public DbSet<GameAccount> GameAccounts { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺
|
||||
/// </summary>
|
||||
public DbSet<DailyNoteEntry> DailyNotes { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的应用程序数据库上下文
|
||||
/// </summary>
|
||||
@@ -76,6 +81,7 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
modelBuilder
|
||||
.ApplyConfiguration(new AvatarInfoConfiguration())
|
||||
.ApplyConfiguration(new UserConfiguration());
|
||||
.ApplyConfiguration(new UserConfiguration())
|
||||
.ApplyConfiguration(new DailyNoteEntryConfiguration());
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
|
||||
namespace Snap.Hutao.Control.Extension;
|
||||
|
||||
@@ -34,10 +33,12 @@ internal static class ContentDialogExtensions
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
contentDialog.ShowAsync().AsTask().SafeForget();
|
||||
|
||||
// E_ASYNC_OPERATION_NOT_STARTED 0x80000019
|
||||
return new ContentDialogHider(contentDialog);
|
||||
}
|
||||
|
||||
private struct ContentDialogHider : IAsyncDisposable
|
||||
private readonly struct ContentDialogHider : IAsyncDisposable
|
||||
{
|
||||
private readonly ContentDialog contentDialog;
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using System.Net.Http;
|
||||
@@ -67,7 +65,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
/// <returns>加载的图像表面</returns>
|
||||
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||
{
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token).ConfigureAwait(true))
|
||||
{
|
||||
return LoadedImageSurface.StartLoadFromStream(imageStream);
|
||||
}
|
||||
@@ -119,7 +117,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
|
||||
private async Task ApplyImageInternalAsync(Uri? uri, CancellationToken token)
|
||||
{
|
||||
await HideAsync(token);
|
||||
await HideAsync(token).ConfigureAwait(true);
|
||||
|
||||
LoadedImageSurface? imageSurface = null;
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
@@ -132,15 +130,15 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
}
|
||||
else
|
||||
{
|
||||
StorageFile storageFile = await imageCache.GetFileFromCacheAsync(uri);
|
||||
StorageFile storageFile = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true);
|
||||
|
||||
try
|
||||
{
|
||||
imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
imageSurface = await LoadImageSurfaceAsync(storageFile, token).ConfigureAwait(true);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
await imageCache.RemoveAsync(uri.Enumerate());
|
||||
await imageCache.RemoveAsync(uri.Enumerate()).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +148,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
OnUpdateVisual(spriteVisual);
|
||||
|
||||
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
|
||||
await ShowAsync(token);
|
||||
await ShowAsync(token).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,7 +157,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
if (!isShow)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
|
||||
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token).ConfigureAwait(true);
|
||||
isShow = true;
|
||||
}
|
||||
}
|
||||
@@ -168,7 +166,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
if (isShow)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token);
|
||||
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token).ConfigureAwait(true);
|
||||
isShow = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ public class Gradient : CompositionImage
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||
{
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token).ConfigureAwait(true))
|
||||
{
|
||||
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream).AsTask(token);
|
||||
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream).AsTask(token).ConfigureAwait(true);
|
||||
imageAspectRatio = decoder.PixelWidth / (double)decoder.PixelHeight;
|
||||
|
||||
return LoadedImageSurface.StartLoadFromStream(imageStream);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Markup;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
/// <summary>
|
||||
/// 快速创建 <see cref="TOwner"/> 的 <see cref="DependencyProperty"/>
|
||||
@@ -11,10 +11,8 @@ namespace Snap.Hutao.Control;
|
||||
/// <summary>
|
||||
/// 表示支持取消加载的异步页面
|
||||
/// 在被导航到其他页面前触发取消异步通知
|
||||
/// <para/>
|
||||
/// InitializeWith{T}();
|
||||
/// InitializeComponent();
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public class ScopedPage : Page
|
||||
{
|
||||
private readonly CancellationTokenSource viewLoadingCancellationTokenSource = new();
|
||||
@@ -26,6 +24,7 @@ public class ScopedPage : Page
|
||||
public ScopedPage()
|
||||
{
|
||||
serviceScope = Ioc.Default.CreateScope();
|
||||
serviceScope.Track();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,6 +63,7 @@ public class ScopedPage : Page
|
||||
|
||||
// Try dispose scope when page is not presented
|
||||
serviceScope.Dispose();
|
||||
viewLoadingCancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods and tools to cache files in a folder
|
||||
/// 经过简化
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Generic type as supplied by consumer of the class</typeparam>
|
||||
public abstract class CacheBase<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private StorageFolder? baseFolder;
|
||||
private string? cacheFolderName;
|
||||
private StorageFolder? cacheFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CacheBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
protected CacheBase(ILogger logger, HttpClient httpClient)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.httpClient = httpClient;
|
||||
|
||||
CacheDuration = TimeSpan.FromDays(30);
|
||||
RetryCount = 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the life duration of every cache entry.
|
||||
/// </summary>
|
||||
public TimeSpan CacheDuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of retries trying to ensure the file is cached.
|
||||
/// </summary>
|
||||
public uint RetryCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all files in the cache
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task ClearAsync()
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
await RemoveAsync(files).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cached files that have expired
|
||||
/// </summary>
|
||||
/// <param name="duration">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref="CacheDuration"/> is used.</param>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task RemoveExpiredAsync(TimeSpan? duration = null)
|
||||
{
|
||||
TimeSpan expiryDuration = duration ?? CacheDuration;
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removed items based on uri list passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
||||
/// <returns>awaitable Task</returns>
|
||||
public async Task RemoveAsync(IEnumerable<Uri> uriForCachedItems)
|
||||
{
|
||||
if (uriForCachedItems == null || !uriForCachedItems.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
Dictionary<string, StorageFile> cachedFiles = files.ToDictionary(file => file.Name);
|
||||
|
||||
foreach (Uri uri in uriForCachedItems)
|
||||
{
|
||||
string fileName = GetCacheFileName(uri);
|
||||
if (cachedFiles.TryGetValue(fileName, out StorageFile? file))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StorageFile containing cached item for given Uri
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri of the item.</param>
|
||||
/// <returns>a StorageFile</returns>
|
||||
public async Task<StorageFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
|
||||
string fileName = GetCacheFileName(uri);
|
||||
|
||||
IStorageItem? item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
|
||||
if (item == null || (await item.GetBasicPropertiesAsync()).Size == 0)
|
||||
{
|
||||
StorageFile baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false);
|
||||
await DownloadFileAsync(uri, baseFile).ConfigureAwait(false);
|
||||
item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull((item as StorageFile)!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override-able method that checks whether file is valid or not.
|
||||
/// </summary>
|
||||
/// <param name="file">storage file</param>
|
||||
/// <param name="duration">cache duration</param>
|
||||
/// <param name="treatNullFileAsOutOfDate">option to mark uninitialized file as expired</param>
|
||||
/// <returns>bool indicate whether file has expired or not</returns>
|
||||
protected virtual async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return treatNullFileAsOutOfDate;
|
||||
}
|
||||
|
||||
BasicProperties? properties = await file.GetBasicPropertiesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
return properties.Size == 0 || DateTime.Now.Subtract(properties.DateModified.DateTime) > duration;
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
byte[] chars = Encoding.UTF8.GetBytes(url);
|
||||
byte[] hash = SHA1.HashData(chars);
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, StorageFile baseFile)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
|
||||
|
||||
using (Stream httpStream = await httpClient.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile.Path))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with default values if user has not initialized explicitly
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
private async Task InitializeInternalAsync()
|
||||
{
|
||||
if (cacheFolder != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
baseFolder ??= ApplicationData.Current.TemporaryFolder;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cacheFolderName))
|
||||
{
|
||||
cacheFolderName = GetType().Name;
|
||||
}
|
||||
|
||||
cacheFolder = await baseFolder
|
||||
.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
|
||||
.AsTask()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<StorageFolder> GetCacheFolderAsync()
|
||||
{
|
||||
if (cacheFolder == null)
|
||||
{
|
||||
await InitializeInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull(cacheFolder!);
|
||||
}
|
||||
|
||||
private async Task RemoveAsync(IEnumerable<StorageFile> files)
|
||||
{
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file.Path);
|
||||
await file.DeleteAsync().AsTask().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError(EventIds.CacheException, "Failed to delete file: {file}", file.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
@@ -16,20 +20,159 @@ namespace Snap.Hutao.Core.Caching;
|
||||
[Injection(InjectAs.Singleton, typeof(IImageCache))]
|
||||
[HttpClient(HttpClientConfigration.Default)]
|
||||
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 16)]
|
||||
public class ImageCache : CacheBase<BitmapImage>, IImageCache
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public class ImageCache : IImageCache
|
||||
{
|
||||
private const string DateAccessedProperty = "System.DateAccessed";
|
||||
|
||||
private static readonly ImmutableDictionary<int, TimeSpan> RetryCountToDelay = new Dictionary<int, TimeSpan>()
|
||||
{
|
||||
[0] = TimeSpan.FromSeconds(4),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
[2] = TimeSpan.FromSeconds(64),
|
||||
[3] = TimeSpan.FromSeconds(4),
|
||||
[4] = TimeSpan.FromSeconds(16),
|
||||
[5] = TimeSpan.FromSeconds(64),
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private readonly List<string> extendedPropertyNames = new() { DateAccessedProperty };
|
||||
|
||||
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private StorageFolder? baseFolder;
|
||||
private string? cacheFolderName;
|
||||
private StorageFolder? cacheFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCache"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="httpClientFactory">http客户端工厂</param>
|
||||
public ImageCache(ILogger<ImageCache> logger, IHttpClientFactory httpClientFactory)
|
||||
: base(logger, httpClientFactory.CreateClient(nameof(ImageCache)))
|
||||
{
|
||||
this.logger = logger;
|
||||
httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
|
||||
CacheDuration = TimeSpan.FromDays(30);
|
||||
RetryCount = 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the life duration of every cache entry.
|
||||
/// </summary>
|
||||
public TimeSpan CacheDuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of retries trying to ensure the file is cached.
|
||||
/// </summary>
|
||||
public uint RetryCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all files in the cache
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task ClearAsync()
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
await RemoveAsync(files).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cached files that have expired
|
||||
/// </summary>
|
||||
/// <param name="duration">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref="CacheDuration"/> is used.</param>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task RemoveExpiredAsync(TimeSpan? duration = null)
|
||||
{
|
||||
TimeSpan expiryDuration = duration ?? CacheDuration;
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removed items based on uri list passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
||||
/// <returns>awaitable Task</returns>
|
||||
public async Task RemoveAsync(IEnumerable<Uri> uriForCachedItems)
|
||||
{
|
||||
if (uriForCachedItems == null || !uriForCachedItems.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
Dictionary<string, StorageFile> cachedFiles = files.ToDictionary(file => file.Name);
|
||||
|
||||
foreach (Uri uri in uriForCachedItems)
|
||||
{
|
||||
string fileName = GetCacheFileName(uri);
|
||||
if (cachedFiles.TryGetValue(fileName, out StorageFile? file))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StorageFile containing cached item for given Uri
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri of the item.</param>
|
||||
/// <returns>a StorageFile</returns>
|
||||
public async Task<StorageFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
|
||||
string fileName = GetCacheFileName(uri);
|
||||
|
||||
IStorageItem? item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
|
||||
if (item == null || (await item.GetBasicPropertiesAsync()).Size == 0)
|
||||
{
|
||||
StorageFile baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false);
|
||||
await DownloadFileAsync(uri, baseFile).ConfigureAwait(false);
|
||||
item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull((item as StorageFile)!);
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
byte[] chars = Encoding.UTF8.GetBytes(url);
|
||||
byte[] hash = SHA1.HashData(chars);
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,7 +182,7 @@ public class ImageCache : CacheBase<BitmapImage>, IImageCache
|
||||
/// <param name="duration">cache duration</param>
|
||||
/// <param name="treatNullFileAsOutOfDate">option to mark uninitialized file as expired</param>
|
||||
/// <returns>bool indicate whether file has expired or not</returns>
|
||||
protected override async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
|
||||
private async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
@@ -72,4 +215,92 @@ public class ImageCache : CacheBase<BitmapImage>, IImageCache
|
||||
|
||||
return properties.Size == 0 || DateTime.Now.Subtract(properties.DateModified.DateTime) > duration;
|
||||
}
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, StorageFile baseFile)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 6)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile.Path))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (retryCount == 3)
|
||||
{
|
||||
uri = new UriBuilder(uri) { Host = "static.hut.ao", }.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with default values if user has not initialized explicitly
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
private async Task InitializeInternalAsync()
|
||||
{
|
||||
if (cacheFolder != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
baseFolder ??= ApplicationData.Current.TemporaryFolder;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cacheFolderName))
|
||||
{
|
||||
cacheFolderName = GetType().Name;
|
||||
}
|
||||
|
||||
cacheFolder = await baseFolder
|
||||
.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
|
||||
.AsTask()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<StorageFolder> GetCacheFolderAsync()
|
||||
{
|
||||
if (cacheFolder == null)
|
||||
{
|
||||
await InitializeInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull(cacheFolder!);
|
||||
}
|
||||
|
||||
private async Task RemoveAsync(IEnumerable<StorageFile> files)
|
||||
{
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file.Path);
|
||||
await file.DeleteAsync().AsTask().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError(EventIds.CacheException, "Failed to delete file: {file}", file.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Snap.Hutao/Snap.Hutao/Core/Convert/CastTo.cs
Normal file
38
src/Snap.Hutao/Snap.Hutao/Core/Convert/CastTo.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Snap.Hutao.Core.Convert;
|
||||
|
||||
/// <summary>
|
||||
/// Class to cast to type <see cref="TTo"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TTo">Target type</typeparam>
|
||||
public static class CastTo<TTo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Casts <see cref="s"/> to <see cref="TTo"/>.
|
||||
/// This does not cause boxing for value types.
|
||||
/// Useful in generic methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">Source type to cast from. Usually a generic type.</typeparam>
|
||||
/// <param name="from">from value</param>
|
||||
/// <returns>target value</returns>
|
||||
public static TTo From<TFrom>(TFrom from)
|
||||
{
|
||||
return Cache<TFrom>.Caster(from);
|
||||
}
|
||||
|
||||
private static class Cache<TCachedFrom>
|
||||
{
|
||||
public static readonly Func<TCachedFrom, TTo> Caster = Get();
|
||||
|
||||
private static Func<TCachedFrom, TTo> Get()
|
||||
{
|
||||
ParameterExpression param = Expression.Parameter(typeof(TCachedFrom));
|
||||
UnaryExpression convert = Expression.ConvertChecked(param, typeof(TTo));
|
||||
return Expression.Lambda<Func<TCachedFrom, TTo>>(convert, param).Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,4 +21,4 @@ internal abstract class Md5Convert
|
||||
byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(source));
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Convert;
|
||||
using Snap.Hutao.Core.Json;
|
||||
using Snap.Hutao.Extension;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
@@ -15,18 +15,6 @@ namespace Snap.Hutao.Core;
|
||||
/// </summary>
|
||||
internal static class CoreEnvironment
|
||||
{
|
||||
// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
|
||||
|
||||
/// <summary>
|
||||
/// 动态密钥1的盐
|
||||
/// </summary>
|
||||
public const string DynamicSecret1Salt = "yUZ3s0Sna1IrSNfk29Vo6vRapdOyqyhB";
|
||||
|
||||
/// <summary>
|
||||
/// 动态密钥2的盐
|
||||
/// </summary>
|
||||
public const string DynamicSecret2Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社请求UA
|
||||
/// </summary>
|
||||
@@ -35,7 +23,7 @@ internal static class CoreEnvironment
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
/// </summary>
|
||||
public const string HoyolabXrpcVersion = "2.38.1";
|
||||
public const string HoyolabXrpcVersion = "2.41.0";
|
||||
|
||||
/// <summary>
|
||||
/// 标准UA
|
||||
@@ -57,15 +45,27 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public static readonly string HutaoDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 包家族名称
|
||||
/// </summary>
|
||||
public static readonly string FamilyName;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的Json序列化选项
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Encoder = new JsonTextEncoder(),
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
|
||||
{
|
||||
Modifiers =
|
||||
{
|
||||
JsonTypeInfoResolvers.ResolveEnumType,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
|
||||
@@ -74,6 +74,7 @@ internal static class CoreEnvironment
|
||||
static CoreEnvironment()
|
||||
{
|
||||
Version = Package.Current.Id.Version.ToVersion();
|
||||
FamilyName = Package.Current.Id.FamilyName;
|
||||
CommonUA = $"Snap Hutao/{Version}";
|
||||
|
||||
// simply assign a random guid
|
||||
@@ -85,8 +86,6 @@ internal static class CoreEnvironment
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
|
||||
byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}");
|
||||
byte[] hash = MD5.Create().ComputeHash(bytes);
|
||||
return System.Convert.ToHexString(hash);
|
||||
return Md5Convert.ToHexString($"{userName}{machineGuid}");
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
|
||||
@@ -17,4 +17,9 @@ public enum HttpClientConfigration
|
||||
/// 米游社请求配置
|
||||
/// </summary>
|
||||
XRpc,
|
||||
|
||||
/// <summary>
|
||||
/// 米游社登录请求配置
|
||||
/// </summary>
|
||||
XRpc2,
|
||||
}
|
||||
@@ -46,6 +46,6 @@ internal static class IocConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
return services.AddDbContextPool<AppDbContext>(builder => builder.UseSqlite(sqlConnectionString));
|
||||
return services.AddDbContext<AppDbContext>(builder => builder.UseSqlite(sqlConnectionString));
|
||||
}
|
||||
}
|
||||
@@ -41,4 +41,22 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对于需要添加动态密钥的客户端使用此配置
|
||||
/// </summary>
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
private static void XRpc2Configuration(HttpClient client)
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-aigis", string.Empty);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_id", "bll8iq97cem8");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "2");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-game_biz", "bbs_cn");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-sdk_version", "1.3.1.2");
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// 服务管理器
|
||||
/// 依赖注入的核心管理类
|
||||
/// </summary>
|
||||
internal static partial class ServiceCollectionExtensions
|
||||
internal static partial class ServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 向容器注册服务
|
||||
@@ -17,4 +17,4 @@ internal static partial class ServiceCollectionExtensions
|
||||
/// <param name="services">容器</param>
|
||||
/// <returns>可继续操作的服务集合</returns>
|
||||
public static partial IServiceCollection AddInjections(this IServiceCollection services);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 服务范围扩展
|
||||
/// </summary>
|
||||
public static class ServiceScopeExtension
|
||||
{
|
||||
private static IServiceScope? scopeReference;
|
||||
|
||||
/// <summary>
|
||||
/// 追踪服务范围
|
||||
/// </summary>
|
||||
/// <param name="scope">范围</param>
|
||||
public static void Track(this IServiceScope scope)
|
||||
{
|
||||
DisposeLast();
|
||||
scopeReference = scope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放上个范围
|
||||
/// </summary>
|
||||
public static void DisposeLast()
|
||||
{
|
||||
scopeReference?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Diagnostics;
|
||||
/// <summary>
|
||||
/// 值类型的<see cref="Stopwatch"/>
|
||||
/// </summary>
|
||||
internal struct ValueStopwatch
|
||||
internal readonly struct ValueStopwatch
|
||||
{
|
||||
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.DataTransfer;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using System.IO;
|
||||
using Windows.Storage;
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// Json 枚举类型
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
internal class JsonEnumAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Json枚举声明
|
||||
/// </summary>
|
||||
/// <param name="readAs">读取</param>
|
||||
/// <param name="writeAs">写入</param>
|
||||
public JsonEnumAttribute(JsonSerializeType readAs, JsonSerializeType writeAs)
|
||||
{
|
||||
ReadAs = readAs;
|
||||
WriteAs = writeAs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取形式
|
||||
/// </summary>
|
||||
public JsonSerializeType ReadAs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入形式
|
||||
/// </summary>
|
||||
public JsonSerializeType WriteAs { get; init; }
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Achievement;
|
||||
namespace Snap.Hutao.Core.Json.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 成就触发器类型
|
||||
/// Json 文本字符串序列化类型
|
||||
/// </summary>
|
||||
public enum AchievementTriggerType
|
||||
public enum JsonSerializeType
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务
|
||||
/// 数字
|
||||
/// </summary>
|
||||
Quest = 1,
|
||||
Int32,
|
||||
|
||||
/// <summary>
|
||||
/// 子任务
|
||||
/// 字符串包裹的数字
|
||||
/// </summary>
|
||||
SubQuest = 2,
|
||||
Int32AsString,
|
||||
|
||||
/// <summary>
|
||||
/// 日常任务
|
||||
/// 名称字符串
|
||||
/// </summary>
|
||||
DailyTask = 3,
|
||||
String,
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Convert;
|
||||
using Snap.Hutao.Core.Json.Annotation;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举转换器
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||
internal class ConfigurableEnumConverter<TEnum> : JsonConverter<TEnum>
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
private readonly JsonSerializeType readAs;
|
||||
private readonly JsonSerializeType writeAs;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的枚举转换器
|
||||
/// </summary>
|
||||
/// <param name="readAs">读取</param>
|
||||
/// <param name="writeAs">写入</param>
|
||||
public ConfigurableEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
|
||||
{
|
||||
this.readAs = readAs;
|
||||
this.writeAs = writeAs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (readAs == JsonSerializeType.Int32)
|
||||
{
|
||||
return CastTo<TEnum>.From(reader.GetInt32());
|
||||
}
|
||||
|
||||
if (reader.GetString() is string str)
|
||||
{
|
||||
return Enum.Parse<TEnum>(str);
|
||||
}
|
||||
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
switch (writeAs)
|
||||
{
|
||||
case JsonSerializeType.Int32:
|
||||
writer.WriteNumberValue(CastTo<int>.From(value));
|
||||
break;
|
||||
case JsonSerializeType.Int32AsString:
|
||||
writer.WriteStringValue(value.ToString("D"));
|
||||
break;
|
||||
default:
|
||||
writer.WriteStringValue(value.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/Snap.Hutao/Snap.Hutao/Core/Json/JsonTextEncoder.cs
Normal file
43
src/Snap.Hutao/Snap.Hutao/Core/Json/JsonTextEncoder.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Snap.Hutao.Core.Json;
|
||||
|
||||
/// <summary>
|
||||
/// 替换 =
|
||||
/// </summary>
|
||||
internal class JsonTextEncoder : JavaScriptEncoder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int MaxOutputCharactersPerInputCharacter { get => 6; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
|
||||
{
|
||||
Span<char> textSpan = new(text, textLength);
|
||||
return textSpan.IndexOf('=');
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
|
||||
{
|
||||
// " => \"
|
||||
if (unicodeScalar == '"')
|
||||
{
|
||||
numberOfCharactersWritten = 2;
|
||||
return "\\\"".AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
}
|
||||
|
||||
string encoded = $"\\u{(uint)unicodeScalar:x4}";
|
||||
numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0;
|
||||
return encoded.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool WillEncode(int unicodeScalar)
|
||||
{
|
||||
return unicodeScalar == '=';
|
||||
}
|
||||
}
|
||||
41
src/Snap.Hutao/Snap.Hutao/Core/Json/JsonTypeInfoResolvers.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Core/Json/JsonTypeInfoResolvers.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Json.Annotation;
|
||||
using Snap.Hutao.Core.Json.Converter;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Core.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Json 类型信息解析器
|
||||
/// </summary>
|
||||
internal static class JsonTypeInfoResolvers
|
||||
{
|
||||
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
|
||||
private static readonly Type ConfigurableEnumConverterType = typeof(ConfigurableEnumConverter<>);
|
||||
|
||||
/// <summary>
|
||||
/// 解析枚举类型
|
||||
/// </summary>
|
||||
/// <param name="ti">Json 类型信息</param>
|
||||
public static void ResolveEnumType(JsonTypeInfo ti)
|
||||
{
|
||||
if (ti.Kind != JsonTypeInfoKind.Object)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<JsonPropertyInfo> enumProperties = ti.Properties
|
||||
.Where(p => p.PropertyType.IsEnum && (p.AttributeProvider?.IsDefined(JsonEnumAttributeType, false) ?? false));
|
||||
|
||||
foreach (JsonPropertyInfo enumProperty in enumProperties)
|
||||
{
|
||||
JsonEnumAttribute attr = enumProperty.PropertyType.GetCustomAttribute<JsonEnumAttribute>()!;
|
||||
Type converterType = ConfigurableEnumConverterType.MakeGenericType(enumProperty.PropertyType);
|
||||
|
||||
enumProperty.CustomConverter = (JsonConverter)Activator.CreateInstance(converterType, attr.ReadAs, attr.WriteAs)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using System.Security.Principal;
|
||||
|
||||
@@ -43,7 +46,29 @@ internal static class Activation
|
||||
public static void Activate(object? sender, AppActivationArguments args)
|
||||
{
|
||||
_ = sender;
|
||||
HandleActivationAsync(args).SafeForget();
|
||||
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
|
||||
{
|
||||
HandleActivationAsync(args).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 响应通知激活事件
|
||||
/// </summary>
|
||||
/// <param name="args">参数</param>
|
||||
public static void NotificationActivate(ToastNotificationActivatedEventArgsCompat args)
|
||||
{
|
||||
ToastArguments toastArgs = ToastArguments.Parse(args.Argument);
|
||||
_ = toastArgs;
|
||||
|
||||
if (toastArgs.TryGetValue("Action", out string? action))
|
||||
{
|
||||
if (action == LaunchGame)
|
||||
{
|
||||
_ = toastArgs.TryGetValue("Uid", out string? uid);
|
||||
HandleLaunchGameActionAsync(uid).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,43 +88,6 @@ internal static class Activation
|
||||
|
||||
private static async Task HandleActivationCoreAsync(AppActivationArguments args)
|
||||
{
|
||||
string argument = string.Empty;
|
||||
|
||||
if (args.Kind == ExtendedActivationKind.Launch)
|
||||
{
|
||||
if (args.TryGetLaunchActivatedArgument(out string? arguments))
|
||||
{
|
||||
argument = arguments;
|
||||
}
|
||||
}
|
||||
|
||||
switch (argument)
|
||||
{
|
||||
case "":
|
||||
{
|
||||
_ = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
await Ioc.Default.GetRequiredService<IInfoBarService>().WaitInitializationAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case LaunchGame:
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
if (!MainWindow.IsPresent)
|
||||
{
|
||||
_ = Ioc.Default.GetRequiredService<LaunchGameWindow>();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Ioc.Default
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Kind == ExtendedActivationKind.Protocol)
|
||||
{
|
||||
if (args.TryGetProtocolActivatedUri(out Uri? uri))
|
||||
@@ -108,22 +96,61 @@ internal static class Activation
|
||||
await HandleUrlActivationAsync(uri).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (args.Kind == ExtendedActivationKind.Launch)
|
||||
{
|
||||
if (args.TryGetLaunchActivatedArgument(out string? arguments))
|
||||
{
|
||||
switch (arguments)
|
||||
{
|
||||
case "":
|
||||
{
|
||||
await WaitMainWindowAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case LaunchGame:
|
||||
{
|
||||
await HandleLaunchGameActionAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitMainWindowAsync()
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
_ = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
await Ioc.Default.GetRequiredService<IInfoBarService>().WaitInitializationAsync().ConfigureAwait(false);
|
||||
|
||||
Ioc.Default
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.ImplictAs<IMetadataInitializer>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget();
|
||||
}
|
||||
|
||||
private static async Task HandleUrlActivationAsync(Uri uri)
|
||||
{
|
||||
UriBuilder builder = new(uri);
|
||||
Must.Argument(builder.Scheme == "hutao", "uri 的协议不正确");
|
||||
|
||||
string category = builder.Host.ToLowerInvariant();
|
||||
string action = builder.Path.ToLowerInvariant();
|
||||
string rawParameter = builder.Query.ToLowerInvariant();
|
||||
string parameter = builder.Query.ToLowerInvariant();
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case "achievement":
|
||||
{
|
||||
await HandleAchievementActionAsync(action, rawParameter).ConfigureAwait(false);
|
||||
await WaitMainWindowAsync().ConfigureAwait(false);
|
||||
await HandleAchievementActionAsync(action, parameter).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case "dailynote":
|
||||
{
|
||||
await HandleDailyNoteActionAsync(action, parameter).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -131,6 +158,7 @@ internal static class Activation
|
||||
|
||||
private static async Task HandleAchievementActionAsync(string action, string parameter)
|
||||
{
|
||||
_ = parameter;
|
||||
switch (action)
|
||||
{
|
||||
case "/import":
|
||||
@@ -146,4 +174,37 @@ internal static class Activation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleDailyNoteActionAsync(string action, string parameter)
|
||||
{
|
||||
_ = parameter;
|
||||
switch (action)
|
||||
{
|
||||
case "/refresh":
|
||||
{
|
||||
await Ioc.Default
|
||||
.GetRequiredService<IDailyNoteService>()
|
||||
.RefreshDailyNotesAsync(true)
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleLaunchGameActionAsync(string? uid = null)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
// TODO auto switch to account
|
||||
if (!MainWindow.IsPresent)
|
||||
{
|
||||
_ = Ioc.Default.GetRequiredService<LaunchGameWindow>();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Ioc.Default
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ internal sealed partial class DatebaseLogger : ILogger
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
where TState : notnull
|
||||
{
|
||||
return new NullScope();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Context.FileSystem;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 进程帮助类
|
||||
/// </summary>
|
||||
public static class ProcessHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动进程
|
||||
/// </summary>
|
||||
/// <param name="path">路径</param>
|
||||
/// <param name="useShellExecute">使用shell</param>
|
||||
/// <returns>进程</returns>
|
||||
public static Process? Start(string path, bool useShellExecute = true)
|
||||
{
|
||||
ProcessStartInfo processInfo = new(path)
|
||||
{
|
||||
UseShellExecute = useShellExecute,
|
||||
};
|
||||
return Process.Start(processInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动进程
|
||||
/// </summary>
|
||||
/// <param name="path">路径</param>
|
||||
/// <param name="arguments">命令行参数</param>
|
||||
/// <param name="useShellExecute">使用shell</param>
|
||||
/// <returns>进程</returns>
|
||||
public static Process? Start(string path, string arguments, bool useShellExecute = true)
|
||||
{
|
||||
ProcessStartInfo processInfo = new(path)
|
||||
{
|
||||
UseShellExecute = useShellExecute,
|
||||
Arguments = arguments,
|
||||
};
|
||||
return Process.Start(processInfo);
|
||||
}
|
||||
}
|
||||
43
src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
Normal file
43
src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32.TaskScheduler;
|
||||
using SchedulerTask = Microsoft.Win32.TaskScheduler.Task;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 任务计划器服务
|
||||
/// </summary>
|
||||
internal static class TaskSchedulerHelper
|
||||
{
|
||||
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
|
||||
|
||||
/// <summary>
|
||||
/// 注册实时便笺刷新任务
|
||||
/// </summary>
|
||||
/// <param name="interval">间隔(秒)</param>
|
||||
/// <returns>是否注册或修改成功</returns>
|
||||
public static bool RegisterForDailyNoteRefresh(int interval)
|
||||
{
|
||||
try
|
||||
{
|
||||
SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName);
|
||||
if (targetTask != null)
|
||||
{
|
||||
TaskService.Instance.RootFolder.DeleteTask(DailyNoteRefreshTaskName);
|
||||
}
|
||||
|
||||
TaskDefinition task = TaskService.Instance.NewTask();
|
||||
task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。";
|
||||
task.Triggers.Add(new TimeTrigger() { Repetition = new(TimeSpan.FromSeconds(interval), TimeSpan.Zero), });
|
||||
task.Actions.Add("explorer", "hutao://DailyNote/Refresh");
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
return true;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public sealed class CancellationTokenTaskCompletionSource : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
TaskCompletionSource tcs = new();
|
||||
registration = cancellationToken.Register(() => tcs.TrySetResult(), useSynchronizationContext: false);
|
||||
Task = tcs.Task;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,4 @@ internal class ThreadAccessAttribute : Attribute
|
||||
public ThreadAccessAttribute(ThreadAccessState enter)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示方法的进入退出线程访问状态
|
||||
/// </summary>
|
||||
/// <param name="enter">进入状态</param>
|
||||
/// <param name="leave">离开状态</param>
|
||||
public ThreadAccessAttribute(ThreadAccessState enter, ThreadAccessState leave)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// <summary>
|
||||
/// 调度器队列切换操作
|
||||
/// </summary>
|
||||
public struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
|
||||
public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
|
||||
{
|
||||
private readonly DispatcherQueue dispatherQueue;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public static class SemaphoreSlimExtensions
|
||||
return new SemaphoreSlimReleaser(semaphoreSlim);
|
||||
}
|
||||
|
||||
private struct SemaphoreSlimReleaser : IDisposable
|
||||
private readonly struct SemaphoreSlimReleaser : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim semaphoreSlim;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public static class TaskExtensions
|
||||
{
|
||||
try
|
||||
{
|
||||
await task;
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
@@ -37,7 +37,7 @@ public static class TaskExtensions
|
||||
{
|
||||
try
|
||||
{
|
||||
await task;
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
@@ -59,7 +59,7 @@ public static class TaskExtensions
|
||||
{
|
||||
try
|
||||
{
|
||||
await task;
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
@@ -83,7 +83,7 @@ public static class TaskExtensions
|
||||
{
|
||||
try
|
||||
{
|
||||
await task;
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
|
||||
@@ -54,11 +54,12 @@ public static class Must
|
||||
/// <summary>
|
||||
/// Unconditionally throws an <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">上下文</param>
|
||||
/// <returns>Nothing. This method always throws.</returns>
|
||||
[DoesNotReturn]
|
||||
public static System.Exception NeverHappen()
|
||||
public static System.Exception NeverHappen(string? context = null)
|
||||
{
|
||||
throw new NotSupportedException("该行为不应发生,请联系开发者进一步确认");
|
||||
throw new NotSupportedException(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Windowing;
|
||||
using Snap.Hutao.Win32;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
@@ -18,9 +19,6 @@ public static class AppWindowExtensions
|
||||
/// <returns>呈现矩形</returns>
|
||||
public static RectInt32 GetRect(this AppWindow appWindow)
|
||||
{
|
||||
PointInt32 postion = appWindow.Position;
|
||||
SizeInt32 size = appWindow.Size;
|
||||
|
||||
return new RectInt32(postion.X, postion.Y, size.Width, size.Height);
|
||||
return StructMarshal.RectInt32(appWindow.Position, appWindow.Size);
|
||||
}
|
||||
}
|
||||
30
src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs
Normal file
30
src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public enum BackdropType
|
||||
{
|
||||
/// <summary>
|
||||
/// 无
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 亚克力
|
||||
/// </summary>
|
||||
Acrylic,
|
||||
|
||||
/// <summary>
|
||||
/// 云母
|
||||
/// </summary>
|
||||
Mica,
|
||||
|
||||
/// <summary>
|
||||
/// 变种云母
|
||||
/// </summary>
|
||||
MicaAlt,
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.Win32;
|
||||
using System.IO;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Graphics;
|
||||
using Windows.UI;
|
||||
using Windows.Win32.Foundation;
|
||||
@@ -15,11 +19,11 @@ using WinRT.Interop;
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
/// <summary>
|
||||
/// 窗口管理器
|
||||
/// 主要包含了针对窗体的 P/Inoke 逻辑
|
||||
/// 扩展窗口
|
||||
/// </summary>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal sealed class ExtendedWindow<TWindow>
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMessage>
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
{
|
||||
private readonly HWND handle;
|
||||
@@ -33,8 +37,10 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
|
||||
private readonly bool useLegacyDragBar;
|
||||
|
||||
private SystemBackdrop? systemBackdrop;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的窗口状态管理器
|
||||
/// 构造一个新的扩展窗口
|
||||
/// </summary>
|
||||
/// <param name="window">窗口</param>
|
||||
/// <param name="titleBar">充当标题栏的元素</param>
|
||||
@@ -65,6 +71,17 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
return new(window, window.TitleBar);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Receive(BackdropTypeChangedMessage message)
|
||||
{
|
||||
if (systemBackdrop != null)
|
||||
{
|
||||
systemBackdrop.BackdropType = message.BackdropType;
|
||||
bool micaApplied = systemBackdrop.TryApply();
|
||||
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
|
||||
{
|
||||
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
@@ -102,7 +119,7 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
private void InitializeWindow()
|
||||
{
|
||||
appWindow.Title = "胡桃";
|
||||
|
||||
appWindow.SetIcon(Path.Combine(Package.Current.InstalledLocation.Path, "Assets/Logos/Logo.ico"));
|
||||
ExtendsContentIntoTitleBar();
|
||||
|
||||
Persistence.RecoverOrInit(appWindow, window.PersistSize, window.InitSize);
|
||||
@@ -113,12 +130,14 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
|
||||
appWindow.Show(true);
|
||||
|
||||
bool micaApplied = new SystemBackdrop(window).TryApply();
|
||||
systemBackdrop = new(window);
|
||||
bool micaApplied = systemBackdrop.TryApply();
|
||||
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
|
||||
|
||||
bool subClassApplied = subclassManager.TrySetWindowSubclass();
|
||||
logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager<TWindow>), subClassApplied ? "succeed" : "failed");
|
||||
|
||||
Ioc.Default.GetRequiredService<IMessenger>().Register(this);
|
||||
window.Closed += OnWindowClosed;
|
||||
}
|
||||
|
||||
@@ -153,16 +172,17 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDragRectangles(AppWindowTitleBar appTitleBar)
|
||||
private unsafe void UpdateDragRectangles(AppWindowTitleBar appTitleBar)
|
||||
{
|
||||
double scale = Persistence.GetScaleForWindow(handle);
|
||||
|
||||
// 48 is the navigation button leftInset
|
||||
RectInt32 dragRect = new RectInt32(48, 0, (int)titleBar.ActualWidth, (int)titleBar.ActualHeight).Scale(scale);
|
||||
RectInt32 dragRect = StructMarshal.RectInt32(new(48, 0), titleBar.ActualSize).Scale(scale);
|
||||
appTitleBar.SetDragRectangles(dragRect.Enumerate().ToArray());
|
||||
|
||||
// workaround for https://github.com/microsoft/WindowsAppSDK/issues/2976
|
||||
// add this to set the same window size after every time drag rectangles are set
|
||||
appWindow.ResizeClient(appWindow.ClientSize);
|
||||
SizeInt32 size = appWindow.ClientSize;
|
||||
size.Height -= (int)(31 * scale);
|
||||
appWindow.ResizeClient(size);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,11 @@ internal static class Persistence
|
||||
}
|
||||
}
|
||||
|
||||
TransformToCenterScreen(ref rect);
|
||||
unsafe
|
||||
{
|
||||
TransformToCenterScreen(&rect);
|
||||
}
|
||||
|
||||
appWindow.MoveAndResize(rect);
|
||||
}
|
||||
|
||||
@@ -69,13 +73,13 @@ internal static class Persistence
|
||||
return new((int)(size.Width * scale), (int)(size.Height * scale));
|
||||
}
|
||||
|
||||
private static void TransformToCenterScreen(ref RectInt32 rect)
|
||||
private static unsafe void TransformToCenterScreen(RectInt32* rect)
|
||||
{
|
||||
DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Primary);
|
||||
DisplayArea displayArea = DisplayArea.GetFromRect(*rect, DisplayAreaFallback.Primary);
|
||||
RectInt32 workAreaRect = displayArea.WorkArea;
|
||||
|
||||
rect.X = workAreaRect.X + ((workAreaRect.Width - rect.Width) / 2);
|
||||
rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2);
|
||||
rect->X = workAreaRect.X + ((workAreaRect.Width - rect->Width) / 2);
|
||||
rect->Y = workAreaRect.Y + ((workAreaRect.Height - rect->Height) / 2);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.System;
|
||||
using WinRT;
|
||||
@@ -18,7 +22,7 @@ public class SystemBackdrop
|
||||
private readonly Window window;
|
||||
|
||||
private DispatcherQueueHelper? dispatcherQueueHelper;
|
||||
private MicaController? backdropController;
|
||||
private ISystemBackdropControllerWithTargets? backdropController;
|
||||
private SystemBackdropConfiguration? configuration;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,38 +32,68 @@ public class SystemBackdrop
|
||||
public SystemBackdrop(Window window)
|
||||
{
|
||||
this.window = window;
|
||||
using (IServiceScope scope = Ioc.Default.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString());
|
||||
BackdropType = Enum.Parse<BackdropType>(entry.Value!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public BackdropType BackdropType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尝试设置背景
|
||||
/// </summary>
|
||||
/// <returns>是否设置成功</returns>
|
||||
public bool TryApply()
|
||||
{
|
||||
if (!MicaController.IsSupported())
|
||||
bool isSupport = BackdropType switch
|
||||
{
|
||||
BackdropType.Acrylic => DesktopAcrylicController.IsSupported(),
|
||||
BackdropType.Mica or BackdropType.MicaAlt => MicaController.IsSupported(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!isSupport)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcherQueueHelper = new();
|
||||
dispatcherQueueHelper.Ensure();
|
||||
// Previous one
|
||||
if (backdropController != null)
|
||||
{
|
||||
backdropController.RemoveAllSystemBackdropTargets();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcherQueueHelper = new();
|
||||
dispatcherQueueHelper.Ensure();
|
||||
}
|
||||
|
||||
// Hooking up the policy object
|
||||
configuration = new();
|
||||
configuration = new()
|
||||
{
|
||||
IsInputActive = true, // Initial configuration state.
|
||||
};
|
||||
SetConfigurationSourceTheme(configuration);
|
||||
|
||||
window.Activated += OnWindowActivated;
|
||||
window.Closed += OnWindowClosed;
|
||||
((FrameworkElement)window.Content).ActualThemeChanged += OnWindowThemeChanged;
|
||||
|
||||
// Initial configuration state.
|
||||
configuration.IsInputActive = true;
|
||||
SetConfigurationSourceTheme(configuration);
|
||||
|
||||
backdropController = new()
|
||||
backdropController = BackdropType switch
|
||||
{
|
||||
// Mica Alt
|
||||
Kind = MicaKind.BaseAlt,
|
||||
BackdropType.Acrylic => new DesktopAcrylicController(),
|
||||
BackdropType.Mica => new MicaController() { Kind = MicaKind.Base },
|
||||
BackdropType.MicaAlt => new MicaController() { Kind = MicaKind.BaseAlt },
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
|
||||
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
|
||||
backdropController.SetSystemBackdropConfiguration(configuration);
|
||||
|
||||
@@ -69,7 +103,7 @@ public class SystemBackdrop
|
||||
|
||||
private void OnWindowActivated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
Must.NotNull(configuration!).IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
|
||||
configuration!.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
|
||||
}
|
||||
|
||||
private void OnWindowClosed(object sender, WindowEventArgs args)
|
||||
|
||||
@@ -52,9 +52,10 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
|
||||
bool titleBarHooked = true;
|
||||
|
||||
// only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar
|
||||
// only hook up drag bar proc when not use legacy Window.ExtendsContentIntoTitleBar
|
||||
if (isLegacyDragBar)
|
||||
{
|
||||
titleBarHooked = false;
|
||||
hwndDragBar = FindWindowEx(hwnd, default, "DRAG_BAR_WINDOW_CLASS", string.Empty);
|
||||
|
||||
if (!hwndDragBar.IsNull)
|
||||
@@ -90,6 +91,12 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
{
|
||||
return new(0);
|
||||
}
|
||||
}
|
||||
|
||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Collection{T}"/> 部分
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 移除集合中满足条件的项
|
||||
/// </summary>
|
||||
/// <typeparam name="T">集合项类型</typeparam>
|
||||
/// <param name="collection">集合</param>
|
||||
/// <param name="shouldRemovePredicate">是否应当移除</param>
|
||||
/// <returns>移除的个数</returns>
|
||||
public static int RemoveWhere<T>(this Collection<T> collection, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (T item in collection.ToList())
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(item))
|
||||
{
|
||||
collection.Remove(item);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Dictionary{TKey, TValue}"/> 部分
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dictionary">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault2<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">增加的值</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key, int value)
|
||||
where TKey : notnull
|
||||
{
|
||||
// ref the value, so that we can manipulate it outside the dict.
|
||||
ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
current += value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
|
||||
if (!Unsafe.IsNullRef(ref value))
|
||||
{
|
||||
++value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="List{T}"/> 部分
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
||||
public static double AverageNoThrow(this List<int> source)
|
||||
{
|
||||
Span<int> span = CollectionsMarshal.AsSpan(source);
|
||||
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
sum += span[i];
|
||||
}
|
||||
|
||||
return (double)sum / span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果传入列表不为空则原路返回,
|
||||
/// 如果传入列表为空返回一个空的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>源列表或空列表</returns>
|
||||
public static List<TSource> EmptyIfNull<TSource>(this List<TSource>? source)
|
||||
{
|
||||
return source ?? new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除表中首个满足条件的项
|
||||
/// </summary>
|
||||
/// <typeparam name="T">项的类型</typeparam>
|
||||
/// <param name="list">表</param>
|
||||
/// <param name="shouldRemovePredicate">是否应当移除</param>
|
||||
/// <returns>是否移除了元素</returns>
|
||||
public static bool RemoveFirstWhere<T>(this IList<T> list, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(list[i]))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
@@ -11,26 +8,6 @@ namespace Snap.Hutao.Extension;
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
||||
public static double AverageNoThrow(this List<int> source)
|
||||
{
|
||||
Span<int> span = CollectionsMarshal.AsSpan(source);
|
||||
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
sum += span[i];
|
||||
}
|
||||
|
||||
return (double)sum / span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计数
|
||||
/// </summary>
|
||||
@@ -63,18 +40,6 @@ public static partial class EnumerableExtension
|
||||
return source ?? Enumerable.Empty<TSource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果传入列表不为空则原路返回,
|
||||
/// 如果传入列表为空返回一个空的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>源列表或空列表</returns>
|
||||
public static List<TSource> EmptyIfNull<TSource>(this List<TSource>? source)
|
||||
{
|
||||
return source ?? new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将源转换为仅包含单个元素的枚举
|
||||
/// </summary>
|
||||
@@ -99,94 +64,6 @@ public static partial class EnumerableExtension
|
||||
return source.FirstOrDefault(predicate) ?? source.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dictionary">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault2<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">增加的值</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key, int value)
|
||||
where TKey : notnull
|
||||
{
|
||||
// ref the value, so that we can manipulate it outside the dict.
|
||||
ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
current += value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
|
||||
if (!Unsafe.IsNullRef(ref value))
|
||||
{
|
||||
++value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除表中首个满足条件的项
|
||||
/// </summary>
|
||||
/// <typeparam name="T">项的类型</typeparam>
|
||||
/// <param name="list">表</param>
|
||||
/// <param name="shouldRemovePredicate">是否应当移除</param>
|
||||
/// <returns>是否移除了元素</returns>
|
||||
public static bool RemoveFirstWhere<T>(this IList<T> list, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(list[i]))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
where TKey : notnull
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="StringBuilder"/> 扩展方法
|
||||
/// </summary>
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串建造器</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="value">附加的字符</param>
|
||||
/// <returns>同一个字符串建造器</returns>
|
||||
public static StringBuilder AppendIf(this StringBuilder sb, bool condition, char? value)
|
||||
{
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串建造器</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="value">附加的字符串</param>
|
||||
/// <returns>同一个字符串建造器</returns>
|
||||
public static StringBuilder AppendIf(this StringBuilder sb, bool condition, string? value)
|
||||
{
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串建造器</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="trueValue">条件符合时附加的字符串</param>
|
||||
/// <param name="falseValue">条件不符合时附加的字符串</param>
|
||||
/// <returns>同一个字符串建造器</returns>
|
||||
public static StringBuilder AppendIfElse(this StringBuilder sb, bool condition, string? trueValue, string? falseValue)
|
||||
{
|
||||
return condition ? sb.Append(trueValue) : sb.Append(falseValue);
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,11 @@ internal interface IPickerFactory
|
||||
/// <summary>
|
||||
/// 获取 经过初始化的 <see cref="FileOpenPicker"/>
|
||||
/// </summary>
|
||||
/// <param name="location">初始位置</param>
|
||||
/// <param name="commitButton">提交按钮文本</param>
|
||||
/// <param name="fileTypes">文件类型</param>
|
||||
/// <returns>经过初始化的 <see cref="FileOpenPicker"/></returns>
|
||||
FileOpenPicker GetFileOpenPicker();
|
||||
FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes);
|
||||
|
||||
/// <summary>
|
||||
/// 获取 经过初始化的 <see cref="FileSavePicker"/>
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Snap.Hutao.Factory;
|
||||
[Injection(InjectAs.Transient, typeof(IPickerFactory))]
|
||||
internal class PickerFactory : IPickerFactory
|
||||
{
|
||||
private const string AnyType = "*";
|
||||
|
||||
private readonly MainWindow mainWindow;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,9 +25,21 @@ internal class PickerFactory : IPickerFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileOpenPicker GetFileOpenPicker()
|
||||
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
|
||||
{
|
||||
return GetInitializedPicker<FileOpenPicker>();
|
||||
FileOpenPicker picker = GetInitializedPicker<FileOpenPicker>();
|
||||
|
||||
picker.SuggestedStartLocation = location;
|
||||
picker.CommitButtonText = commitButton;
|
||||
|
||||
foreach (string type in fileTypes)
|
||||
{
|
||||
picker.FileTypeFilter.Add(type);
|
||||
}
|
||||
|
||||
picker.FileTypeFilter.Add(AnyType);
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -35,6 +35,7 @@ public sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedWin
|
||||
|
||||
scope = scopeFactory.CreateScope();
|
||||
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService<LaunchGameViewModel>();
|
||||
Closed += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型改变消息
|
||||
/// </summary>
|
||||
internal class BackdropTypeChangedMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的背景类型改变消息
|
||||
/// </summary>
|
||||
/// <param name="backdropType">背景类型</param>
|
||||
public BackdropTypeChangedMessage(BackdropType backdropType)
|
||||
{
|
||||
BackdropType = backdropType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public BackdropType BackdropType { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
|
||||
26
src/Snap.Hutao/Snap.Hutao/Message/UserRemovedMessage.cs
Normal file
26
src/Snap.Hutao/Snap.Hutao/Message/UserRemovedMessage.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
/// <summary>
|
||||
/// 用户删除消息
|
||||
/// </summary>
|
||||
internal class UserRemovedMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户删除消息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
public UserRemovedMessage(User user)
|
||||
{
|
||||
RemovedUserId = user.InnerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除的用户Id
|
||||
/// </summary>
|
||||
public Guid RemovedUserId { get; }
|
||||
}
|
||||
282
src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs
generated
Normal file
282
src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs
generated
Normal file
@@ -0,0 +1,282 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221108081525_DailyNoteEntry")]
|
||||
partial class DailyNoteEntry
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cookie")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
public partial class DailyNoteEntry : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "daily_notes",
|
||||
columns: table => new
|
||||
{
|
||||
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Uid = table.Column<string>(type: "TEXT", nullable: false),
|
||||
DailyNote = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ResinNotifyThreshold = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ResinNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
HomeCoinNotifyThreshold = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
HomeCoinNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
TransformerNotify = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
TransformerNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DailyTaskNotify = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DailyTaskNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpeditionNotify = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpeditionNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ShowInHomeWidget = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_daily_notes", x => x.InnerId);
|
||||
table.ForeignKey(
|
||||
name: "FK_daily_notes_users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "users",
|
||||
principalColumn: "InnerId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_daily_notes_UserId",
|
||||
table: "daily_notes",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "daily_notes");
|
||||
}
|
||||
}
|
||||
}
|
||||
286
src/Snap.Hutao/Snap.Hutao/Migrations/20221118095755_SplitStoken.Designer.cs
generated
Normal file
286
src/Snap.Hutao/Snap.Hutao/Migrations/20221118095755_SplitStoken.Designer.cs
generated
Normal file
@@ -0,0 +1,286 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221118095755_SplitStoken")]
|
||||
partial class SplitStoken
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cookie")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SplitStoken : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Stoken",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Stoken",
|
||||
table: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
292
src/Snap.Hutao/Snap.Hutao/Migrations/20221118124745_AddAidMid.Designer.cs
generated
Normal file
292
src/Snap.Hutao/Snap.Hutao/Migrations/20221118124745_AddAidMid.Designer.cs
generated
Normal file
@@ -0,0 +1,292 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221118124745_AddAidMid")]
|
||||
partial class AddAidMid
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cookie")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAidMid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Aid",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Mid",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Aid",
|
||||
table: "users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Mid",
|
||||
table: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
292
src/Snap.Hutao/Snap.Hutao/Migrations/20221123060511_RenameCookieToLtoken.Designer.cs
generated
Normal file
292
src/Snap.Hutao/Snap.Hutao/Migrations/20221123060511_RenameCookieToLtoken.Designer.cs
generated
Normal file
@@ -0,0 +1,292 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221123060511_RenameCookieToLtoken")]
|
||||
partial class RenameCookieToLtoken
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameCookieToLtoken : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Cookie",
|
||||
table: "users",
|
||||
newName: "Ltoken");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Ltoken",
|
||||
table: "users",
|
||||
newName: "Cookie");
|
||||
}
|
||||
}
|
||||
}
|
||||
295
src/Snap.Hutao/Snap.Hutao/Migrations/20221123110240_AddCookieToken.Designer.cs
generated
Normal file
295
src/Snap.Hutao/Snap.Hutao/Migrations/20221123110240_AddCookieToken.Designer.cs
generated
Normal file
@@ -0,0 +1,295 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221123110240_AddCookieToken")]
|
||||
partial class AddCookieToken
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCookieToken : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CookieToken",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CookieToken",
|
||||
table: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
@@ -82,6 +82,62 @@ namespace Snap.Hutao.Migrations
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
@@ -175,12 +231,24 @@ namespace Snap.Hutao.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cookie")
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
@@ -197,6 +265,17 @@ namespace Snap.Hutao.Migrations
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
/// <summary>
|
||||
/// 词条评分
|
||||
/// </summary>
|
||||
public struct AffixScore
|
||||
public readonly struct AffixScore
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的圣遗物评分
|
||||
|
||||
@@ -11,5 +11,5 @@ public class Constellation : NameIconDescription
|
||||
/// <summary>
|
||||
/// 是否激活
|
||||
/// </summary>
|
||||
public bool IsActiviated { get; set; }
|
||||
public bool IsActivated { get; set; }
|
||||
}
|
||||
@@ -18,11 +18,13 @@ public class LaunchScheme
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="cps">通道描述字符串</param>
|
||||
/// <param name="subChannel">子通道</param>
|
||||
public LaunchScheme(string name, string channel, string subChannel)
|
||||
/// <param name="launcherId">启动器Id</param>
|
||||
public LaunchScheme(string name, string channel, string subChannel, string launcherId)
|
||||
{
|
||||
Name = name;
|
||||
Channel = channel;
|
||||
SubChannel = subChannel;
|
||||
LauncherId = launcherId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,4 +41,9 @@ public class LaunchScheme
|
||||
/// 子通道
|
||||
/// </summary>
|
||||
public string SubChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动器Id
|
||||
/// </summary>
|
||||
public string LauncherId { get; set; }
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
|
||||
/// <summary>
|
||||
/// 用于视图绑定的用户
|
||||
/// </summary>
|
||||
public class User : ObservableObject
|
||||
{
|
||||
private readonly EntityUser inner;
|
||||
|
||||
private UserGameRole? selectedUserGameRole;
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的绑定视图用户
|
||||
/// </summary>
|
||||
/// <param name="user">用户实体</param>
|
||||
private User(EntityUser user)
|
||||
{
|
||||
inner = user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public UserInfo? UserInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public List<UserGameRole> UserGameRoles { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public UserGameRole? SelectedUserGameRole
|
||||
{
|
||||
get => selectedUserGameRole;
|
||||
private set => SetProperty(ref selectedUserGameRole, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.IsSelected"/>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => inner.IsSelected;
|
||||
set => inner.IsSelected = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Cookie"/>
|
||||
public Cookie? Cookie
|
||||
{
|
||||
get => inner.Cookie;
|
||||
set
|
||||
{
|
||||
inner.Cookie = value;
|
||||
OnPropertyChanged(nameof(HasSToken));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否拥有 SToken
|
||||
/// </summary>
|
||||
public bool HasSToken
|
||||
{
|
||||
get => inner.Cookie!.ContainsSToken();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部的用户实体
|
||||
/// </summary>
|
||||
public EntityUser Entity { get => inner; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否初始化完成
|
||||
/// </summary>
|
||||
public bool IsInitialized { get => isInitialized; }
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库恢复用户
|
||||
/// </summary>
|
||||
/// <param name="inner">数据库实体</param>
|
||||
/// <param name="userClient">用户客户端</param>
|
||||
/// <param name="userGameRoleClient">角色客户端</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="false"/> </returns>
|
||||
internal static async Task<User?> ResumeAsync(EntityUser inner, UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
|
||||
{
|
||||
User user = new(inner);
|
||||
bool successful = await user.InitializeCoreAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
return successful ? user : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并初始化用户
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie</param>
|
||||
/// <param name="userClient">用户客户端</param>
|
||||
/// <param name="userGameRoleClient">角色客户端</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="null"/> </returns>
|
||||
internal static async Task<User?> CreateAsync(Cookie cookie, UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
|
||||
{
|
||||
User user = new(EntityUser.Create(cookie));
|
||||
bool successful = await user.InitializeCoreAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
return successful ? user : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新SToken
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="cookie">cookie</param>
|
||||
internal void UpdateSToken(string uid, Cookie cookie)
|
||||
{
|
||||
Cookie!.InsertSToken(uid, cookie);
|
||||
OnPropertyChanged(nameof(HasSToken));
|
||||
}
|
||||
|
||||
private async Task<bool> InitializeCoreAsync(UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
|
||||
{
|
||||
if (isInitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UserInfo = await userClient
|
||||
.GetUserFullInfoAsync(this, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
UserGameRoles = await userGameRoleClient
|
||||
.GetUserGameRolesAsync(this, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
return UserInfo != null && UserGameRoles.Any();
|
||||
}
|
||||
}
|
||||
196
src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs
Normal file
196
src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.User;
|
||||
|
||||
/// <summary>
|
||||
/// 用于视图绑定的用户
|
||||
/// </summary>
|
||||
public class User : ObservableObject
|
||||
{
|
||||
private readonly EntityUser inner;
|
||||
|
||||
private UserGameRole? selectedUserGameRole;
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的绑定视图用户
|
||||
/// </summary>
|
||||
/// <param name="user">用户实体</param>
|
||||
private User(EntityUser user)
|
||||
{
|
||||
inner = user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public UserInfo? UserInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public List<UserGameRole> UserGameRoles { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息, 请勿访问set
|
||||
/// </summary>
|
||||
public UserGameRole? SelectedUserGameRole
|
||||
{
|
||||
get => selectedUserGameRole;
|
||||
set => SetProperty(ref selectedUserGameRole, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.IsSelected"/>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => inner.IsSelected;
|
||||
set => inner.IsSelected = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.CookieToken"/>
|
||||
public Cookie? CookieToken
|
||||
{
|
||||
get => inner.CookieToken;
|
||||
set => inner.CookieToken = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Ltoken"/>
|
||||
public Cookie? Ltoken
|
||||
{
|
||||
get => inner.Ltoken;
|
||||
set => inner.Ltoken = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Stoken"/>
|
||||
public Cookie? Stoken
|
||||
{
|
||||
get => inner.Stoken;
|
||||
set
|
||||
{
|
||||
inner.Stoken = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部的用户实体
|
||||
/// </summary>
|
||||
public EntityUser Entity { get => inner; }
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库恢复用户
|
||||
/// </summary>
|
||||
/// <param name="inner">数据库实体</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="false"/> </returns>
|
||||
internal static async Task<User?> ResumeAsync(EntityUser inner, CancellationToken token = default)
|
||||
{
|
||||
User user = new(inner);
|
||||
bool successful = await user.InitializeCoreAsync(token).ConfigureAwait(false);
|
||||
return successful ? user : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并初始化用户
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="null"/> </returns>
|
||||
internal static async Task<User?> CreateAsync(Cookie cookie, CancellationToken token = default)
|
||||
{
|
||||
// 这里只负责创建实体用户,稍后在用户服务中保存到数据库
|
||||
EntityUser entity = EntityUser.Create(cookie);
|
||||
|
||||
entity.Aid = cookie.GetValueOrDefault(Cookie.STUID);
|
||||
entity.Mid = cookie.GetValueOrDefault(Cookie.MID);
|
||||
|
||||
if (entity.Aid != null && entity.Mid != null)
|
||||
{
|
||||
User user = new(entity);
|
||||
bool initialized = await user.InitializeCoreAsync(token).ConfigureAwait(false);
|
||||
|
||||
return initialized ? user : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> InitializeCoreAsync(CancellationToken token = default)
|
||||
{
|
||||
if (isInitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Stoken == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = Ioc.Default.CreateScope())
|
||||
{
|
||||
UserInfo = await scope.ServiceProvider
|
||||
.GetRequiredService<UserClient2>()
|
||||
.GetUserFullInfoAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// 自动填充 Ltoken
|
||||
if (Ltoken == null)
|
||||
{
|
||||
string? ltoken = await scope.ServiceProvider
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.GetLtokenBySTokenAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (ltoken != null)
|
||||
{
|
||||
Cookie ltokenCookie = Cookie.Parse($"ltuid={Entity.Aid};ltoken={ltoken}");
|
||||
Entity.Ltoken = ltokenCookie;
|
||||
}
|
||||
}
|
||||
|
||||
string? actionTicket = await scope.ServiceProvider
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetActionTicketByStokenAsync("game_role", Entity)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
UserGameRoles = await scope.ServiceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByActionTicketAsync(actionTicket!, Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// 自动填充 CookieToken
|
||||
if (CookieToken == null)
|
||||
{
|
||||
string? cookieToken = await scope.ServiceProvider
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.GetCookieAccountInfoBySTokenAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (cookieToken != null)
|
||||
{
|
||||
Cookie cookieTokenCookie = Cookie.Parse($"acount_id={Entity.Aid};cookie_token={cookieToken}");
|
||||
Entity.CookieToken = cookieTokenCookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
return UserInfo != null && UserGameRoles.Any();
|
||||
}
|
||||
}
|
||||
34
src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs
Normal file
34
src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.User;
|
||||
|
||||
/// <summary>
|
||||
/// 角色与实体用户
|
||||
/// </summary>
|
||||
public class UserAndRole
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的实体用户与角色
|
||||
/// </summary>
|
||||
/// <param name="user">实体用户</param>
|
||||
/// <param name="role">角色</param>
|
||||
public UserAndRole(EntityUser user, UserGameRole role)
|
||||
{
|
||||
User = user;
|
||||
Role = role;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体用户
|
||||
/// </summary>
|
||||
public EntityUser User { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public UserGameRole Role { get; private set; }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
@@ -84,7 +83,22 @@ public class Achievement : IEquatable<Achievement>
|
||||
Id = uiaf.Id,
|
||||
Current = uiaf.Current,
|
||||
Status = uiaf.Status, // Hot fix | 1.0.30 | Status not set when create database entity
|
||||
Time = DateTimeOffset.FromUnixTimeSeconds(uiaf.Timestamp).ToLocalTime(true),
|
||||
Time = DateTimeOffset.FromUnixTimeSeconds(uiaf.Timestamp).ToLocalTime(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到UIAF物品
|
||||
/// </summary>
|
||||
/// <returns>UIAF物品</returns>
|
||||
public UIAFItem ToUIAFItem()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = Id,
|
||||
Current = Current,
|
||||
Status = Status,
|
||||
Timestamp = Time.ToUniversalTime().ToUnixTimeSeconds(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺入口配置
|
||||
/// </summary>
|
||||
internal class DailyNoteEntryConfiguration : IEntityTypeConfiguration<DailyNoteEntry>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<DailyNoteEntry> builder)
|
||||
{
|
||||
builder.Property(e => e.DailyNote)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote>>();
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,16 @@ internal class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder.Property(e => e.Cookie)
|
||||
builder.Property(e => e.CookieToken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(
|
||||
e => e == null ? string.Empty : e.ToString(),
|
||||
e => Cookie.Parse(e));
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
|
||||
builder.Property(e => e.Ltoken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
|
||||
builder.Property(e => e.Stoken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
}
|
||||
}
|
||||
135
src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs
Normal file
135
src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺入口
|
||||
/// </summary>
|
||||
[Table("daily_notes")]
|
||||
public class DailyNoteEntry : INotifyPropertyChanged
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户
|
||||
/// </summary>
|
||||
public User User { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Uid
|
||||
/// </summary>
|
||||
public string Uid { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家角色
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public UserGameRole? UserGameRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Json!!! 实时便笺
|
||||
/// </summary>
|
||||
public DailyNote? DailyNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 树脂提醒阈值
|
||||
/// </summary>
|
||||
public int ResinNotifyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断树脂是否继续提醒
|
||||
/// </summary>
|
||||
public bool ResinNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 洞天宝钱提醒阈值
|
||||
/// </summary>
|
||||
public int HomeCoinNotifyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断洞天宝钱是否继续提醒
|
||||
/// </summary>
|
||||
public bool HomeCoinNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参量质变仪提醒
|
||||
/// </summary>
|
||||
public bool TransformerNotify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断参量质变仪是否继续提醒
|
||||
/// </summary>
|
||||
public bool TransformerNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每日委托提醒
|
||||
/// </summary>
|
||||
public bool DailyTaskNotify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断每日委托是否继续提醒
|
||||
/// </summary>
|
||||
public bool DailyTaskNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 探索派遣提醒
|
||||
/// </summary>
|
||||
public bool ExpeditionNotify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断探索派遣是否继续提醒
|
||||
/// </summary>
|
||||
public bool ExpeditionNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否在主页显示小组件
|
||||
/// </summary>
|
||||
public bool ShowInHomeWidget { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的实时便笺
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">用户与角色</param>
|
||||
/// <returns>新的实时便笺</returns>
|
||||
public static DailyNoteEntry Create(UserAndRole userAndRole)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
UserId = userAndRole.User.InnerId,
|
||||
Uid = userAndRole.Role.GameUid,
|
||||
ResinNotifyThreshold = 160,
|
||||
HomeCoinNotifyThreshold = 2400,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实时便笺
|
||||
/// </summary>
|
||||
/// <param name="dailyNote">新的值</param>
|
||||
public void UpdateDailyNote(DailyNote? dailyNote)
|
||||
{
|
||||
DailyNote = dailyNote;
|
||||
PropertyChanged?.Invoke(this, new(nameof(DailyNote)));
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public class GachaItem
|
||||
/// <summary>
|
||||
/// 存档
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(ArchiveId))]
|
||||
public GachaArchive Archive { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,8 +10,11 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// 设置入口
|
||||
/// </summary>
|
||||
[Table("settings")]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
public class SettingEntry
|
||||
{
|
||||
#region EntryNames
|
||||
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
@@ -22,6 +25,21 @@ public class SettingEntry
|
||||
/// </summary>
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
/// <summary>
|
||||
/// 窗口背景类型
|
||||
/// </summary>
|
||||
public const string SystemBackdropType = "SystemBackdropType";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺刷新时间
|
||||
/// </summary>
|
||||
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺提醒式通知
|
||||
/// </summary>
|
||||
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 全屏
|
||||
/// </summary>
|
||||
@@ -51,6 +69,7 @@ public class SettingEntry
|
||||
/// 启动游戏 目标帧率
|
||||
/// </summary>
|
||||
public const string LaunchTargetFps = "Launch.TargetFps";
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的设置入口
|
||||
|
||||
@@ -21,15 +21,35 @@ public class User : ISelectable
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 米游社账号 Id
|
||||
/// </summary>
|
||||
public string? Aid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 米哈游 Id
|
||||
/// </summary>
|
||||
public string? Mid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否被选中
|
||||
/// </summary>
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户的Cookie
|
||||
/// 用户的 Cookie Token
|
||||
/// </summary>
|
||||
public Cookie? Cookie { get; set; }
|
||||
public Cookie? CookieToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户的 Ltoken
|
||||
/// </summary>
|
||||
public Cookie? Ltoken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户的 Stoken V2
|
||||
/// </summary>
|
||||
public Cookie? Stoken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的用户
|
||||
@@ -38,6 +58,9 @@ public class User : ISelectable
|
||||
/// <returns>新创建的用户</returns>
|
||||
public static User Create(Cookie cookie)
|
||||
{
|
||||
return new() { Cookie = cookie };
|
||||
_ = cookie.TryGetAsStoken(out Cookie? stoken);
|
||||
_ = cookie.TryGetAsLtoken(out Cookie? ltoken);
|
||||
|
||||
return new() { Stoken = stoken, Ltoken = ltoken };
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,14 @@ namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
/// </summary>
|
||||
public class UIAF
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前发行的版本
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v1.1";
|
||||
|
||||
private static readonly List<string> SupportedVersion = new()
|
||||
{
|
||||
"v1.1",
|
||||
CurrentVersion,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Extension;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
@@ -25,6 +26,7 @@ public class UIAFInfo
|
||||
/// <summary>
|
||||
/// 导出时间
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
// Hot fix | 1.0.31 | UIAF.Info.ExportTimestamp can be milliseconds
|
||||
@@ -42,4 +44,20 @@ public class UIAFInfo
|
||||
/// </summary>
|
||||
[JsonPropertyName("uiaf_version")]
|
||||
public string? UIAFVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的专用 UIAF 信息
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <returns>专用 UIAF 信息</returns>
|
||||
public static UIAFInfo Create()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
ExportApp = "胡桃",
|
||||
ExportAppVersion = CoreEnvironment.Version.ToString(),
|
||||
UIAFVersion = UIAF.CurrentVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user