mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77158fc708 | ||
|
|
f2d63e69ea | ||
|
|
9e344f56e0 | ||
|
|
848392f8d4 | ||
|
|
62d0fb5d05 | ||
|
|
7a99c44b29 | ||
|
|
792a701183 | ||
|
|
fa19f7e817 | ||
|
|
bf5fcb70f8 | ||
|
|
fda642b72f | ||
|
|
fa650a95c5 | ||
|
|
02fae69d1e | ||
|
|
76800de6ee | ||
|
|
72b660119f | ||
|
|
67a1d5dc74 | ||
|
|
6e6d125814 | ||
|
|
55f16a6357 | ||
|
|
560068ca20 | ||
|
|
2f1981108e | ||
|
|
b545c0d09b | ||
|
|
009feced08 | ||
|
|
fa7fcbc9cc | ||
|
|
d28624dea1 | ||
|
|
d802d1af15 | ||
|
|
43e3df9cba | ||
|
|
8e5e59ad0d | ||
|
|
ed7d55ddd5 | ||
|
|
94ef94a621 | ||
|
|
fd35213741 | ||
|
|
0f752129b7 |
44
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
44
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -24,8 +24,8 @@ body:
|
||||
id: shver
|
||||
attributes:
|
||||
label: Snap Hutao 版本
|
||||
description: 在应用程序的设置界面中靠下的位置可以找到
|
||||
placeholder: 例:1.0.30.0
|
||||
description: 在应用标题,应用程序的设置界面中靠下的位置可以找到
|
||||
placeholder: 例:1.1.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -48,6 +48,42 @@ body:
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 相关的崩溃日志 位于 %HOMEPATH%/Documents/Hutao/Log.db
|
||||
description: 如果应用程序崩溃了,可以将崩溃日志复制后粘贴在此处,文件包含了敏感信息,谨慎上传
|
||||
label: 相关的崩溃日志 位于 `%HOMEPATH%/Documents/Hutao/Log.db`
|
||||
description: |
|
||||
在资源管理器中直接输入`%HOMEPATH%/Documents/Hutao`即可进入文件夹
|
||||
如果应用程序崩溃了,请将`log.db` 文件上传,文件包含了敏感信息,谨慎上传
|
||||
如果这个表单是关于导入祈愿记录的问题,请包含你导入的`Json`文件
|
||||
**务必不要上传`user.db`文件,该文件包含你的帐号敏感信息**
|
||||
render: shell
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm-issue
|
||||
attributes:
|
||||
label: 我确认已在表单中附上了充足的补充说明以帮助开发人员确定问题
|
||||
description: 补充说明包括但不限于:日志文件、抛出的错误信息、截图和录屏
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm-no-duplicated-issue
|
||||
attributes:
|
||||
label: 我确认没有他人提出相同或类似的问题
|
||||
description: |
|
||||
请先通过 Issue 搜索功能确认这不是相同的问题;
|
||||
[BUG Issues](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aissue+is%3Aopen+label%3ABUG)
|
||||
你应该在原始 Issue 中通过回复添加有助于解决问题的信息,而不是创建重复的问题;
|
||||
**没有帮助的重复问题可能会被直接关闭**
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm-docs
|
||||
attributes:
|
||||
label: 我确认该问题没有在文档中解释
|
||||
description: Snap Hutao 官方文档:[https://hut.ao](https://hut.ao)
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
|
||||
|
||||
39
.github/workflows/PublishDistribution.yml
vendored
Normal file
39
.github/workflows/PublishDistribution.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: PublishDistribution
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Download Publish.zip
|
||||
- name: Download Release
|
||||
uses: robinraju/release-downloader@v1.5
|
||||
with:
|
||||
repository: "DGP-Studio/Snap.Hutao"
|
||||
latest: true
|
||||
fileName: "*.zip"
|
||||
out-file-path: ./release-download
|
||||
|
||||
# Upload to OD21 (Testing)
|
||||
- name: Upload OD21
|
||||
env:
|
||||
RCCONF: ${{ secrets.RCCONF }}
|
||||
run: |
|
||||
curl https://rclone.org/install.sh | sudo bash
|
||||
mkdir -p ~/.config/rclone/
|
||||
cat << EOF > ~/.config/rclone/rclone.conf
|
||||
$RCCONF
|
||||
EOF
|
||||
|
||||
rclone copy ./release-download/* dgpODCN:/snaphutao/Releases/
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,10 @@ src/Snap.Hutao/Snap.Hutao/bin/
|
||||
src/Snap.Hutao/Snap.Hutao/obj/
|
||||
src/Snap.Hutao/Snap.Hutao/Snap.Hutao_TemporaryKey.pfx
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/obj/
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/Properties/PublishProfiles/FolderProfile.pubxml.user
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/
|
||||
|
||||
|
||||
11
README.md
11
README.md
@@ -3,9 +3,15 @@
|
||||
|
||||

|
||||
|
||||
## 项目首页(文档)
|
||||
|
||||
[](https://github.com/DGP-Studio/Snap.Hutao.Docs/actions/workflows/deploy-docs.yml)
|
||||
|
||||
[HUT.AO](https://hut.ao)
|
||||
|
||||
## 安装
|
||||
|
||||
* 前往 [下载页面](https://go.hut.ao/archive) 下载最新版本的 `胡桃` 安装包
|
||||
* 前往 [下载页面](https://go.hut.ao/down) 下载最新版本的 `胡桃` 安装包
|
||||
* (曾启用的可以跳过此步骤)在系统设置中打开 **开发者选项** 界面,勾选 `开发人员模式` 和 `允许 PowerShell 脚本`
|
||||
* 完全解压后,右键使用 powershell 运行 `install.ps1` 文件
|
||||
* 安装完成后可以关闭 `允许 PowerShell 脚本`
|
||||
@@ -30,5 +36,4 @@
|
||||
* [microsoft/vs-threading](https://github.com/microsoft/vs-threading)
|
||||
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
|
||||
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
* [MiniExcel/MiniExcel](https://github.com/MiniExcel/MiniExcel)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
@@ -159,6 +159,7 @@ dotnet_diagnostic.CA1805.severity = suggestion
|
||||
|
||||
# VSTHRD111: Use ConfigureAwait(bool)
|
||||
dotnet_diagnostic.VSTHRD111.severity = suggestion
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
72
src/Snap.Hutao/Snap.Hutao.Installer/Program.cs
Normal file
72
src/Snap.Hutao/Snap.Hutao.Installer/Program.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Installer;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private const string AppxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Appx";
|
||||
private const string ValueName = "AllowDevelopmentWithoutDevLicense";
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
string ps1File = Path.Combine(AppContext.BaseDirectory, "Install.ps1");
|
||||
|
||||
if (!File.Exists(ps1File))
|
||||
{
|
||||
Console.WriteLine("未检测到 Install.ps1 文件");
|
||||
Console.WriteLine("请勿移动该安装程序,按下任意键退出...");
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//以管理策略打开开发者模式
|
||||
Registry.SetValue(AppxKey, ValueName, 1, RegistryValueKind.DWord);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("开发者模式未开启,请手动开启,参阅下方链接");
|
||||
Console.WriteLine("https://learn.microsoft.com/zh-CN/windows/apps/get-started/developer-mode-features-and-debugging");
|
||||
}
|
||||
|
||||
await InstallAsync(ps1File).ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("官方文档与使用教程");
|
||||
Console.WriteLine("https://hut.ao");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("在开始菜单中启动 Snap.Hutao ,按下任意键退出...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
private static async Task InstallAsync(string ps1File)
|
||||
{
|
||||
Console.WriteLine("请注意 PowerShell 中的提示");
|
||||
Process ps = new()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = $"-ExecutionPolicy Unrestricted \"{ps1File}\"",
|
||||
UseShellExecute = true,
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ps.Start();
|
||||
await ps.WaitForExitAsync();
|
||||
Console.WriteLine("安装脚本运行完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
79
src/Snap.Hutao/Snap.Hutao.Installer/app.manifest
Normal file
79
src/Snap.Hutao/Snap.Hutao.Installer/app.manifest
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC 清单选项
|
||||
如果想要更改 Windows 用户帐户控制级别,请使用
|
||||
以下节点之一替换 requestedExecutionLevel 节点。
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
|
||||
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
|
||||
元素。
|
||||
-->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
|
||||
Windows 版本的列表。取消评论适当的元素,
|
||||
Windows 将自动选择最兼容的环境。 -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
|
||||
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
|
||||
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
|
||||
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
|
||||
|
||||
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
@@ -22,6 +22,7 @@ public class InjectionGenerator : ISourceGenerator
|
||||
{
|
||||
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
|
||||
private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
|
||||
private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
@@ -97,8 +98,11 @@ internal static partial class ServiceCollectionExtensions
|
||||
case InjectAsTransientName:
|
||||
lineBuilder.Append(@" services.AddTransient(");
|
||||
break;
|
||||
case InjectAsScopedName:
|
||||
lineBuilder.Append(@" services.AddScoped(");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]");
|
||||
throw new InvalidOperationException($"非法的 InjectAs 值: [{injectAsName}]");
|
||||
}
|
||||
|
||||
if (arguments.Length == 2)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -14,6 +14,8 @@ 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}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -82,6 +84,22 @@ Global
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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|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
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
x:Class="Snap.Hutao.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls">
|
||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -13,21 +16,54 @@
|
||||
<!--Modify Window title bar color-->
|
||||
<StaticResource x:Key="WindowCaptionBackground" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
|
||||
<!--Page Transparent Background-->
|
||||
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
|
||||
<!--IconFont-->
|
||||
<FontFamily x:Key="SymbolThemeFontFamily">ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
|
||||
|
||||
<!--InfoBar Resource-->
|
||||
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
|
||||
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
|
||||
|
||||
<!--Pivot Resource-->
|
||||
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
|
||||
|
||||
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
<!--CornerRadius-->
|
||||
<CornerRadius x:Key="CompatCornerRadius">6</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusTop">6,6,0,0</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
|
||||
<!--Converters-->
|
||||
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
||||
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
|
||||
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
|
||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||
<shmmc:GachaAvatarImgConverter x:Key="GachaAvatarImgConverter"/>
|
||||
<shmmc:GachaAvatarIconConverter x:Key="GachaAvatarIconConverter"/>
|
||||
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
|
||||
<shmmc:PropertyInfoDescriptor x:Key="PropertyDescriptor"/>
|
||||
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
|
||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
|
||||
<!--Styles-->
|
||||
<Style
|
||||
x:Key="LargeGridViewItemStyle"
|
||||
TargetType="GridViewItem"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
<!--ItemsPanelTemplate-->
|
||||
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
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.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
@@ -26,6 +26,7 @@ public partial class App : Application
|
||||
/// Initializes the singleton application object.
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public App(ILogger<App> logger)
|
||||
{
|
||||
// load app resource
|
||||
@@ -48,8 +49,11 @@ public partial class App : Application
|
||||
Activation.Activate(firstInstance, activatedEventArgs);
|
||||
firstInstance.Activated += Activation.Activate;
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Snap Hutao : {version}", CoreEnvironment.Version);
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
|
||||
|
||||
JumpListHelper.ConfigAsync().SafeForget(logger);
|
||||
|
||||
Ioc.Default
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.ImplictAs<IMetadataInitializer>()?
|
||||
|
||||
@@ -56,6 +56,11 @@ public class AppDbContext : DbContext
|
||||
/// </summary>
|
||||
public DbSet<AvatarInfo> AvatarInfos { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏内账号
|
||||
/// </summary>
|
||||
public DbSet<GameAccount> GameAccounts { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的应用程序数据库上下文
|
||||
/// </summary>
|
||||
@@ -69,6 +74,8 @@ public class AppDbContext : DbContext
|
||||
/// <inheritdoc/>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfiguration(new AvatarInfoConfiguration());
|
||||
modelBuilder
|
||||
.ApplyConfiguration(new AvatarInfoConfiguration())
|
||||
.ApplyConfiguration(new UserConfiguration());
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,4 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// 按给定比例自动调整高度的行为
|
||||
/// </summary>
|
||||
internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetWidth), 320D);
|
||||
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetHeight), 1024D);
|
||||
|
||||
/// <summary>
|
||||
/// 目标宽度
|
||||
/// </summary>
|
||||
public double TargetWidth
|
||||
{
|
||||
get => (double)GetValue(TargetWidthProperty);
|
||||
set => SetValue(TargetWidthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 目标高度
|
||||
/// </summary>
|
||||
public double TargetHeight
|
||||
{
|
||||
get => (double)GetValue(TargetHeightProperty);
|
||||
set => SetValue(TargetHeightProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
UpdateElementWidth();
|
||||
AssociatedObject.SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.SizeChanged -= OnSizeChanged;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateElementWidth();
|
||||
}
|
||||
|
||||
private void UpdateElementWidth()
|
||||
{
|
||||
AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// Make ContentDialog's SmokeLayerBackground dsiplay over custom titleBar
|
||||
/// </summary>
|
||||
public class ContentDialogBehavior : BehaviorBase<ContentDialog>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
DependencyObject parent = VisualTreeHelper.GetParent(AssociatedObject);
|
||||
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
DependencyObject current = VisualTreeHelper.GetChild(parent, i);
|
||||
if (current is Rectangle { Name: "SmokeLayerBackground" } background)
|
||||
{
|
||||
background.ClearValue(FrameworkElement.MarginProperty);
|
||||
background.RegisterPropertyChangedCallback(FrameworkElement.MarginProperty, OnMarginChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnMarginChanged(DependencyObject sender, DependencyProperty property)
|
||||
{
|
||||
if (property == FrameworkElement.MarginProperty)
|
||||
{
|
||||
sender.ClearValue(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
using Snap.Hutao.Control.Behavior;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
|
||||
namespace Snap.Hutao.Control.Extension;
|
||||
@@ -23,7 +21,6 @@ internal static class ContentDialogExtensions
|
||||
public static ContentDialog InitializeWithWindow(this ContentDialog contentDialog, Window window)
|
||||
{
|
||||
contentDialog.XamlRoot = window.Content.XamlRoot;
|
||||
Interaction.SetBehaviors(contentDialog, new() { new ContentDialogBehavior() });
|
||||
|
||||
return contentDialog;
|
||||
}
|
||||
|
||||
@@ -121,19 +121,27 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
await HideAsync(token);
|
||||
|
||||
LoadedImageSurface? imageSurface = null;
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
if (uri != null)
|
||||
{
|
||||
StorageFile storageFile = await imageCache.GetFileFromCacheAsync(uri);
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
LoadedImageSurface? imageSurface = null;
|
||||
try
|
||||
if (uri.Scheme == "ms-appx")
|
||||
{
|
||||
imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
imageSurface = LoadedImageSurface.StartLoadFromUri(uri);
|
||||
}
|
||||
catch (COMException)
|
||||
else
|
||||
{
|
||||
await imageCache.RemoveAsync(uri.Enumerate());
|
||||
StorageFile storageFile = await imageCache.GetFileFromCacheAsync(uri);
|
||||
|
||||
try
|
||||
{
|
||||
imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
await imageCache.RemoveAsync(uri.Enumerate());
|
||||
}
|
||||
}
|
||||
|
||||
if (imageSurface != null)
|
||||
|
||||
@@ -29,7 +29,6 @@ public class MonoChrome : CompositionImage
|
||||
{
|
||||
CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black);
|
||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
||||
|
||||
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay);
|
||||
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ internal class I18NExtension : MarkupExtension
|
||||
static I18NExtension()
|
||||
{
|
||||
string currentName = CultureInfo.CurrentUICulture.Name;
|
||||
Type languageType = EnumerableExtensions.GetValueOrDefault(TranslationMap, currentName, typeof(LanguagezhCN));
|
||||
Translation = (ITranslation)Activator.CreateInstance(languageType)!;
|
||||
Type? languageType = TranslationMap.GetValueOrDefault2(currentName, typeof(LanguagezhCN));
|
||||
Translation = (ITranslation)Activator.CreateInstance(languageType!)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/Control/Markup/UriExtension.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Control/Markup/UriExtension.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
|
||||
namespace Snap.Hutao.Control.Markup;
|
||||
|
||||
/// <summary>
|
||||
/// Uri扩展
|
||||
/// </summary>
|
||||
[MarkupExtensionReturnType(ReturnType = typeof(Uri))]
|
||||
public sealed class UriExtension : MarkupExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Uri扩展
|
||||
/// </summary>
|
||||
public UriExtension()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 地址
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
return new Uri(Value ?? string.Empty);
|
||||
}
|
||||
}
|
||||
29
src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml
Normal file
29
src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml
Normal file
@@ -0,0 +1,29 @@
|
||||
<UserControl
|
||||
x:Class="Snap.Hutao.Control.Panel.PanelSelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<SplitButton Padding="0,6" Click="SplitButtonClick" Loaded="SplitButtonLoaded">
|
||||
<SplitButton.Content>
|
||||
<FontIcon Name="IconPresenter" Glyph=""/>
|
||||
</SplitButton.Content>
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<RadioMenuFlyoutItem
|
||||
Tag="List"
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Text="列表"/>
|
||||
<RadioMenuFlyoutItem
|
||||
Tag="Grid"
|
||||
Click="RadioMenuFlyoutItemClick"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Text="网格"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
/// <summary>
|
||||
/// 面板选择器
|
||||
/// </summary>
|
||||
public sealed partial class PanelSelector : UserControl
|
||||
{
|
||||
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), "List");
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的面板选择器
|
||||
/// </summary>
|
||||
public PanelSelector()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前选择
|
||||
/// </summary>
|
||||
public string Current
|
||||
{
|
||||
get => (string)GetValue(CurrentProperty);
|
||||
set => SetValue(CurrentProperty, value);
|
||||
}
|
||||
|
||||
private void SplitButtonLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MenuFlyout menuFlyout = (MenuFlyout)((SplitButton)sender).Flyout;
|
||||
((RadioMenuFlyoutItem)menuFlyout.Items[0]).IsChecked = true;
|
||||
}
|
||||
|
||||
private void SplitButtonClick(SplitButton sender, SplitButtonClickEventArgs args)
|
||||
{
|
||||
MenuFlyout menuFlyout = (MenuFlyout)sender.Flyout;
|
||||
int i = 0;
|
||||
for (; i < menuFlyout.Items.Count; i++)
|
||||
{
|
||||
RadioMenuFlyoutItem current = (RadioMenuFlyoutItem)menuFlyout.Items[i];
|
||||
if (current.IsChecked)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
if (i > menuFlyout.Items.Count)
|
||||
{
|
||||
i = 1;
|
||||
}
|
||||
|
||||
if (i == menuFlyout.Items.Count)
|
||||
{
|
||||
i = 0;
|
||||
}
|
||||
|
||||
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)menuFlyout.Items[i];
|
||||
item.IsChecked = true;
|
||||
UpdateState(item);
|
||||
}
|
||||
|
||||
private void RadioMenuFlyoutItemClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)sender;
|
||||
UpdateState(item);
|
||||
}
|
||||
|
||||
private void UpdateState(RadioMenuFlyoutItem item)
|
||||
{
|
||||
Current = (string)item.Tag;
|
||||
IconPresenter.Glyph = ((FontIcon)item.Icon).Glyph;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ namespace Snap.Hutao.Control;
|
||||
/// <summary>
|
||||
/// 表示支持取消加载的异步页面
|
||||
/// 在被导航到其他页面前触发取消异步通知
|
||||
/// <para/>
|
||||
/// InitializeWith{T}();
|
||||
/// InitializeComponent();
|
||||
/// </summary>
|
||||
public class ScopedPage : Page
|
||||
{
|
||||
@@ -25,17 +28,15 @@ public class ScopedPage : Page
|
||||
serviceScope = Ioc.Default.CreateScope();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IServiceScope.ServiceProvider"/>
|
||||
public IServiceProvider ServiceProvider { get => serviceScope.ServiceProvider; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// 应当在 InitializeComponent() 前调用
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">视图模型类型</typeparam>
|
||||
public void InitializeWith<TViewModel>()
|
||||
where TViewModel : class, ISupportCancellation
|
||||
{
|
||||
ISupportCancellation viewModel = ServiceProvider.GetRequiredService<TViewModel>();
|
||||
ISupportCancellation viewModel = serviceScope.ServiceProvider.GetRequiredService<TViewModel>();
|
||||
viewModel.CancellationToken = viewLoadingCancellationTokenSource.Token;
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class DescriptionTextBlock : ContentControl
|
||||
|
||||
if (i == description.Length - 1)
|
||||
{
|
||||
AppendText(text, description[last..i]);
|
||||
AppendText(text, description[last..(i + 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,5 @@ internal interface ISupportAsyncInitialization
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>初始化任务</returns>
|
||||
ValueTask<bool> InitializeAsync(CancellationToken token = default);
|
||||
ValueTask<bool> InitializeAsync();
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
@@ -23,6 +23,8 @@ public abstract class CacheBase<T>
|
||||
{
|
||||
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private StorageFolder? baseFolder;
|
||||
@@ -139,7 +141,7 @@ public abstract class CacheBase<T>
|
||||
|
||||
IStorageItem? item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
|
||||
if (item == null)
|
||||
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);
|
||||
|
||||
63
src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs
Normal file
63
src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 命令行建造器
|
||||
/// </summary>
|
||||
public class CommandLineBuilder
|
||||
{
|
||||
private const char WhiteSpace = ' ';
|
||||
private readonly Dictionary<string, string?> options = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当符合条件时添加参数
|
||||
/// </summary>
|
||||
/// <param name="name">参数名称</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>命令行建造器</returns>
|
||||
public CommandLineBuilder AppendIf(string name, bool condition, object? value = null)
|
||||
{
|
||||
return condition ? Append(name, value) : this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加参数
|
||||
/// </summary>
|
||||
/// <param name="name">参数名称</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>命令行建造器</returns>
|
||||
public CommandLineBuilder Append(string name, object? value = null)
|
||||
{
|
||||
options.Add(name, value?.ToString());
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ToString"/>
|
||||
public string Build()
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder s = new();
|
||||
foreach ((string key, string? value) in options)
|
||||
{
|
||||
s.Append(WhiteSpace);
|
||||
s.Append(key);
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
s.Append(WhiteSpace);
|
||||
s.Append(value);
|
||||
}
|
||||
}
|
||||
|
||||
return s.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Extension;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
@@ -17,12 +20,12 @@ internal static class CoreEnvironment
|
||||
/// <summary>
|
||||
/// 动态密钥1的盐
|
||||
/// </summary>
|
||||
public const string DynamicSecret1Salt = "Qqx8cyv7kuyD8fTw11SmvXSFHp7iZD29";
|
||||
public const string DynamicSecret1Salt = "yUZ3s0Sna1IrSNfk29Vo6vRapdOyqyhB";
|
||||
|
||||
/// <summary>
|
||||
/// 动态密钥2的盐
|
||||
/// </summary>
|
||||
public const string DynamicSecret2Salt = "YVEIkzDFNHLeKXLxzqCA9TzxCpWwbIbk";
|
||||
public const string DynamicSecret2Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社请求UA
|
||||
@@ -32,7 +35,7 @@ internal static class CoreEnvironment
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
/// </summary>
|
||||
public const string HoyolabXrpcVersion = "2.37.1";
|
||||
public const string HoyolabXrpcVersion = "2.38.1";
|
||||
|
||||
/// <summary>
|
||||
/// 标准UA
|
||||
@@ -49,6 +52,11 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public static readonly string HoyolabDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃设备Id
|
||||
/// </summary>
|
||||
public static readonly string HutaoDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的Json序列化选项
|
||||
/// </summary>
|
||||
@@ -60,6 +68,9 @@ internal static class CoreEnvironment
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
|
||||
private const string MachineGuidValue = "MachineGuid";
|
||||
|
||||
static CoreEnvironment()
|
||||
{
|
||||
Version = Package.Current.Id.Version.ToVersion();
|
||||
@@ -67,5 +78,15 @@ internal static class CoreEnvironment
|
||||
|
||||
// simply assign a random guid
|
||||
HoyolabDeviceId = Guid.NewGuid().ToString();
|
||||
HutaoDeviceId = GetUniqueUserID();
|
||||
}
|
||||
|
||||
private static string GetUniqueUserID()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,8 @@ namespace Snap.Hutao.Core.Database;
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal class DbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
{
|
||||
private readonly DbContext dbContext;
|
||||
private readonly DbSet<TEntity> dbSet;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
@@ -25,12 +24,10 @@ internal class DbCurrent<TEntity, TMessage>
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文</param>
|
||||
/// <param name="dbSet">数据集</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, IMessenger messenger)
|
||||
public DbCurrent(DbSet<TEntity> dbSet, IMessenger messenger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.dbSet = dbSet;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
@@ -55,19 +52,18 @@ internal class DbCurrent<TEntity, TMessage>
|
||||
if (current != null)
|
||||
{
|
||||
current.IsSelected = false;
|
||||
dbSet.Update(current);
|
||||
dbContext.SaveChanges();
|
||||
dbSet.UpdateAndSave(current);
|
||||
}
|
||||
}
|
||||
|
||||
TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
|
||||
TMessage message = new() { OldValue = current, NewValue = value };
|
||||
|
||||
current = value;
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
current.IsSelected = true;
|
||||
dbSet.Update(current);
|
||||
dbContext.SaveChanges();
|
||||
dbSet.UpdateAndSave(current);
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
|
||||
95
src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
Normal file
95
src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库集合上下文
|
||||
/// </summary>
|
||||
public static class DbSetExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取对应的数据库上下文
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <returns>对应的数据库上下文</returns>
|
||||
public static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
return dbSet.GetService<ICurrentDbContext>().Context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或添加一个对应的实体
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="predicate">谓词</param>
|
||||
/// <param name="entityFactory">实体工厂</param>
|
||||
/// <param name="added">是否添加</param>
|
||||
/// <returns>实体</returns>
|
||||
public static TEntity SingleOrAdd<TEntity>(this DbSet<TEntity> dbSet, Func<TEntity, bool> predicate, Func<TEntity> entityFactory, out bool added)
|
||||
where TEntity : class
|
||||
{
|
||||
added = false;
|
||||
TEntity? entry = dbSet.SingleOrDefault(predicate);
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
entry = entityFactory();
|
||||
dbSet.Add(entry);
|
||||
dbSet.Context().SaveChanges();
|
||||
|
||||
added = true;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int AddAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Add(entity);
|
||||
return dbSet.Context().SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int RemoveAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Remove(entity);
|
||||
return dbSet.Context().SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int UpdateAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Update(entity);
|
||||
return dbSet.Context().SaveChanges();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 设置帮助类
|
||||
/// </summary>
|
||||
public static class SettingEntryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或添加一个对应的设置
|
||||
/// </summary>
|
||||
/// <param name="dbSet">设置集</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>设置</returns>
|
||||
public static SettingEntry SingleOrAdd(this DbSet<SettingEntry> dbSet, string key, string value)
|
||||
{
|
||||
SettingEntry? entry = dbSet.SingleOrDefault(entry => key == entry.Key);
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
entry = new(key, value);
|
||||
dbSet.Add(entry);
|
||||
dbSet.Context().SaveChanges();
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或添加一个对应的设置
|
||||
/// </summary>
|
||||
/// <param name="dbSet">设置集</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="valueFactory">值工厂</param>
|
||||
/// <returns>设置</returns>
|
||||
public static SettingEntry SingleOrAdd(this DbSet<SettingEntry> dbSet, string key, Func<string> valueFactory)
|
||||
{
|
||||
SettingEntry? entry = dbSet.SingleOrDefault(entry => key == entry.Key);
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
entry = new(key, valueFactory());
|
||||
dbSet.Add(entry);
|
||||
dbSet.Context().SaveChanges();
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Boolean 值
|
||||
/// </summary>
|
||||
/// <param name="entry">设置</param>
|
||||
/// <returns>值</returns>
|
||||
public static bool GetBoolean(this SettingEntry entry)
|
||||
{
|
||||
return bool.Parse(entry.Value!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Boolean 值
|
||||
/// </summary>
|
||||
/// <param name="entry">设置</param>
|
||||
/// <param name="value">值</param>
|
||||
public static void SetBoolean(this SettingEntry entry, bool value)
|
||||
{
|
||||
entry.Value = value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Int32 值
|
||||
/// </summary>
|
||||
/// <param name="entry">设置</param>
|
||||
/// <returns>值</returns>
|
||||
public static int GetInt32(this SettingEntry entry)
|
||||
{
|
||||
return int.Parse(entry.Value!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Int32 值
|
||||
/// </summary>
|
||||
/// <param name="entry">设置</param>
|
||||
/// <param name="value">值</param>
|
||||
public static void SetInt32(this SettingEntry entry, int value)
|
||||
{
|
||||
entry.Value = value.ToString();
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,9 @@ public enum InjectAs
|
||||
/// 指示应注册为短期对象
|
||||
/// </summary>
|
||||
Transient,
|
||||
|
||||
/// <summary>
|
||||
/// 指示应注册为范围对象
|
||||
/// </summary>
|
||||
Scoped,
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
|
||||
namespace Snap.Hutao.Core.Exception;
|
||||
|
||||
@@ -26,22 +27,19 @@ internal class ExceptionRecorder
|
||||
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当应用程序未经处理的异常引发时调用
|
||||
/// </summary>
|
||||
/// <param name="sender">实例</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
public void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常: [HResult:{code}]", e.Exception.HResult);
|
||||
Ioc.Default.GetRequiredService<HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
|
||||
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())
|
||||
{
|
||||
provider.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Xaml 绑定失败时触发
|
||||
/// </summary>
|
||||
/// <param name="sender">实例</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
public void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||
{
|
||||
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
|
||||
}
|
||||
|
||||
@@ -25,4 +25,16 @@ internal static class Clipboard
|
||||
string json = await view.GetTextAsync();
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文本
|
||||
/// </summary>
|
||||
/// <param name="text">文本</param>
|
||||
public static void SetText(string text)
|
||||
{
|
||||
DataPackage content = new();
|
||||
content.SetText(text);
|
||||
Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(content);
|
||||
Windows.ApplicationModel.DataTransfer.Clipboard.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
30
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniComment.cs
Normal file
30
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniComment.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 注释
|
||||
/// </summary>
|
||||
internal class IniComment : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 Ini 注释
|
||||
/// </summary>
|
||||
/// <param name="comment">注释</param>
|
||||
public IniComment(string comment)
|
||||
{
|
||||
Comment = comment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注释
|
||||
/// </summary>
|
||||
public string Comment { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $";{Comment}";
|
||||
}
|
||||
}
|
||||
16
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniElement.cs
Normal file
16
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniElement.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 元素
|
||||
/// </summary>
|
||||
internal abstract class IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 将当前元素转换到等价的字符串表示
|
||||
/// </summary>
|
||||
/// <returns>字符串</returns>
|
||||
public new abstract string ToString();
|
||||
}
|
||||
37
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs
Normal file
37
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 参数
|
||||
/// </summary>
|
||||
internal class IniParameter : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Ini 参数
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
public IniParameter(string key, string value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Key}={Value}";
|
||||
}
|
||||
}
|
||||
31
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSection.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSection.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 节
|
||||
/// </summary>
|
||||
internal class IniSection : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Ini 节
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="elements">元素</param>
|
||||
public IniSection(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Name}]";
|
||||
}
|
||||
}
|
||||
63
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
Normal file
63
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 序列化器
|
||||
/// </summary>
|
||||
internal static class IniSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// 反序列化
|
||||
/// </summary>
|
||||
/// <param name="fileStream">文件流</param>
|
||||
/// <returns>Ini 元素集合</returns>
|
||||
public static IEnumerable<IniElement> Deserialize(FileStream fileStream)
|
||||
{
|
||||
using (TextReader reader = new StreamReader(fileStream))
|
||||
{
|
||||
while (reader.ReadLine() is string line)
|
||||
{
|
||||
if (line.Length > 0)
|
||||
{
|
||||
if (line[0] == '[')
|
||||
{
|
||||
yield return new IniSection(line[1..^1]);
|
||||
}
|
||||
|
||||
if (line[0] == ';')
|
||||
{
|
||||
yield return new IniComment(line[1..]);
|
||||
}
|
||||
|
||||
if (line.IndexOf('=') > 0)
|
||||
{
|
||||
string[] parameters = line.Split('=', 2);
|
||||
yield return new IniParameter(parameters[0], parameters[1]);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
/// <param name="fileStream">写入的流</param>
|
||||
/// <param name="elements">元素</param>
|
||||
public static void Serialize(FileStream fileStream, IEnumerable<IniElement> elements)
|
||||
{
|
||||
using (TextWriter writer = new StreamWriter(fileStream))
|
||||
{
|
||||
foreach (IniElement element in elements)
|
||||
{
|
||||
writer.WriteLine(element.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,4 +21,4 @@ internal class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerabl
|
||||
{
|
||||
writer.WriteStringValue(string.Join(',', value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +31,7 @@ public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
Type valueType = type.GetGenericArguments()[1];
|
||||
|
||||
Type innerConverterType = typeof(StringEnumDictionaryConverterInner<,>).MakeGenericType(keyType, valueType);
|
||||
JsonConverter converter = (JsonConverter)Activator.CreateInstance(innerConverterType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { options }, null)!;
|
||||
JsonConverter converter = (JsonConverter)Activator.CreateInstance(innerConverterType)!;
|
||||
return converter;
|
||||
}
|
||||
|
||||
@@ -41,13 +39,11 @@ public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
where TKey : struct, Enum
|
||||
{
|
||||
private readonly Type keyType;
|
||||
private readonly Type valueType;
|
||||
|
||||
public StringEnumDictionaryConverterInner(JsonSerializerOptions options)
|
||||
public StringEnumDictionaryConverterInner()
|
||||
{
|
||||
// Cache the key and value types.
|
||||
keyType = typeof(TKey);
|
||||
valueType = typeof(TValue);
|
||||
}
|
||||
|
||||
public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
|
||||
35
src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs
Normal file
35
src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Windows.UI.StartScreen;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 跳转列表帮助类
|
||||
/// </summary>
|
||||
public static class JumpListHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步配置跳转列表
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public static async Task ConfigAsync()
|
||||
{
|
||||
if (JumpList.IsSupported())
|
||||
{
|
||||
JumpList list = await JumpList.LoadCurrentAsync();
|
||||
|
||||
list.Items.Clear();
|
||||
|
||||
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, "启动游戏");
|
||||
launchGameItem.GroupName = "快捷操作";
|
||||
launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png");
|
||||
|
||||
list.Items.Add(launchGameItem);
|
||||
|
||||
await list.SaveAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
@@ -14,8 +14,26 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
/// </summary>
|
||||
internal static class Activation
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动游戏启动参数
|
||||
/// </summary>
|
||||
public const string LaunchGame = "LaunchGame";
|
||||
|
||||
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否提升了权限
|
||||
/// </summary>
|
||||
/// <returns>是否提升了权限</returns>
|
||||
public static bool GetElevated()
|
||||
{
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 响应激活事件
|
||||
/// 激活事件一般不会在UI线程上触发
|
||||
@@ -45,16 +63,48 @@ internal static class Activation
|
||||
|
||||
private static async Task HandleActivationCoreAsync(AppActivationArguments args)
|
||||
{
|
||||
_ = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
string argument = string.Empty;
|
||||
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
await infoBarService.WaitInitializationAsync().ConfigureAwait(false);
|
||||
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))
|
||||
{
|
||||
infoBarService.Information(uri.ToString());
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
|
||||
await HandleUrlActivationAsync(uri).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,22 @@ public static class AppActivationArgumentsExtensions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取启动的参数
|
||||
/// </summary>
|
||||
/// <param name="activatedEventArgs">应用程序激活参数</param>
|
||||
/// <param name="arguments">参数</param>
|
||||
/// <returns>是否存在参数</returns>
|
||||
public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
|
||||
{
|
||||
arguments = null;
|
||||
if (activatedEventArgs.Data is ILaunchActivatedEventArgs launchArgs)
|
||||
{
|
||||
arguments = launchArgs.Arguments;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,16 @@ internal static class EventIds
|
||||
/// 成就
|
||||
/// </summary>
|
||||
public static readonly EventId Achievement = 100130;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿统计生成
|
||||
/// </summary>
|
||||
public static readonly EventId GachaStatisticGeneration = 100140;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿统计生成
|
||||
/// </summary>
|
||||
public static readonly EventId AvatarInfoGeneration = 100150;
|
||||
#endregion
|
||||
|
||||
#region 杂项
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class LogEntryQueue : IDisposable
|
||||
private readonly TaskCompletionSource writeDbCompletionSource = new();
|
||||
private readonly LogDbContext logDbContext;
|
||||
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的日志队列
|
||||
/// </summary>
|
||||
@@ -27,7 +29,7 @@ public sealed class LogEntryQueue : IDisposable
|
||||
{
|
||||
logDbContext = InitializeDbContext();
|
||||
|
||||
Task.Run(async () => await WritePendingLogsAsync(disposeTokenSource.Token)).SafeForget();
|
||||
Task.Run(() => WritePendingLogsAsync(disposeTokenSource.Token)).SafeForget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,6 +45,11 @@ public sealed class LogEntryQueue : IDisposable
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// notify the write task to complete.
|
||||
disposeTokenSource.Cancel();
|
||||
|
||||
@@ -50,6 +57,7 @@ public sealed class LogEntryQueue : IDisposable
|
||||
writeDbCompletionSource.Task.GetAwaiter().GetResult();
|
||||
|
||||
logDbContext.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private static LogDbContext InitializeDbContext()
|
||||
@@ -62,9 +70,8 @@ public sealed class LogEntryQueue : IDisposable
|
||||
logDbContext.Database.Migrate();
|
||||
}
|
||||
|
||||
logDbContext.Logs.RemoveRange(logDbContext.Logs);
|
||||
logDbContext.SaveChanges();
|
||||
|
||||
// only raw sql can pass
|
||||
logDbContext.Database.ExecuteSqlRaw("DELETE FROM logs WHERE Exception IS NULL");
|
||||
return logDbContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,4 +41,4 @@ public static class ProcessHelper
|
||||
};
|
||||
return Process.Start(processInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,6 @@ namespace Snap.Hutao.Core.Setting;
|
||||
/// </summary>
|
||||
internal static class LocalSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// 由于 <see cref="Windows.Foundation.Collections.IPropertySet"/> 没有 nullable context,
|
||||
/// 在处理引用类型时需要格外小心
|
||||
/// </summary>
|
||||
private static readonly ApplicationDataContainer Container;
|
||||
|
||||
static LocalSetting()
|
||||
@@ -21,6 +17,198 @@ internal static class LocalSetting
|
||||
Container = ApplicationData.Current.LocalSettings;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static byte Get(string key, byte defaultValue)
|
||||
{
|
||||
return Get<byte>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static short Get(string key, short defaultValue)
|
||||
{
|
||||
return Get<short>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static ushort Get(string key, ushort defaultValue)
|
||||
{
|
||||
return Get<ushort>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static int Get(string key, int defaultValue)
|
||||
{
|
||||
return Get<int>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static uint Get(string key, uint defaultValue)
|
||||
{
|
||||
return Get<uint>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static ulong Get(string key, ulong defaultValue)
|
||||
{
|
||||
return Get<ulong>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static float Get(string key, float defaultValue)
|
||||
{
|
||||
return Get<float>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static double Get(string key, double defaultValue)
|
||||
{
|
||||
return Get<double>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static bool Get(string key, bool defaultValue)
|
||||
{
|
||||
return Get<bool>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static char Get(string key, char defaultValue)
|
||||
{
|
||||
return Get<char>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static DateTimeOffset Get(string key, DateTimeOffset defaultValue)
|
||||
{
|
||||
return Get<DateTimeOffset>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static TimeSpan Get(string key, TimeSpan defaultValue)
|
||||
{
|
||||
return Get<TimeSpan>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static Guid Get(string key, Guid defaultValue)
|
||||
{
|
||||
return Get<Guid>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static Windows.Foundation.Point Get(string key, Windows.Foundation.Point defaultValue)
|
||||
{
|
||||
return Get<Windows.Foundation.Point>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static Windows.Foundation.Size Get(string key, Windows.Foundation.Size defaultValue)
|
||||
{
|
||||
return Get<Windows.Foundation.Size>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get{T}(string, T)"/>
|
||||
public static Windows.Foundation.Rect Get(string key, Windows.Foundation.Rect defaultValue)
|
||||
{
|
||||
return Get<Windows.Foundation.Rect>(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, byte value)
|
||||
{
|
||||
Set<byte>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, short value)
|
||||
{
|
||||
Set<short>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, ushort value)
|
||||
{
|
||||
Set<ushort>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, int value)
|
||||
{
|
||||
Set<int>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, uint value)
|
||||
{
|
||||
Set<uint>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, ulong value)
|
||||
{
|
||||
Set<ulong>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, float value)
|
||||
{
|
||||
Set<float>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, double value)
|
||||
{
|
||||
Set<double>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, bool value)
|
||||
{
|
||||
Set<bool>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, char value)
|
||||
{
|
||||
Set<char>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, DateTimeOffset value)
|
||||
{
|
||||
Set<DateTimeOffset>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, TimeSpan value)
|
||||
{
|
||||
Set<TimeSpan>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, Guid value)
|
||||
{
|
||||
Set<Guid>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, Windows.Foundation.Point value)
|
||||
{
|
||||
Set<Windows.Foundation.Point>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, Windows.Foundation.Size value)
|
||||
{
|
||||
Set<Windows.Foundation.Size>(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Set{T}(string, T)"/>
|
||||
public static void Set(string key, Windows.Foundation.Rect value)
|
||||
{
|
||||
Set<Windows.Foundation.Rect>(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设置项的值
|
||||
/// </summary>
|
||||
@@ -28,8 +216,8 @@ internal static class LocalSetting
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>获取的值</returns>
|
||||
[return: MaybeNull]
|
||||
public static T Get<T>(string key, [AllowNull] T defaultValue = default)
|
||||
private static T Get<T>(string key, T defaultValue = default)
|
||||
where T : struct
|
||||
{
|
||||
if (Container.Values.TryGetValue(key, out object? value))
|
||||
{
|
||||
@@ -49,9 +237,9 @@ internal static class LocalSetting
|
||||
/// <typeparam name="T">设置项的类型</typeparam>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>设置的值</returns>
|
||||
public static object? Set<T>(string key, T value)
|
||||
private static void Set<T>(string key, T value)
|
||||
where T : struct
|
||||
{
|
||||
return Container.Values[key] = value;
|
||||
Container.Values[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,6 @@ namespace Snap.Hutao.Core.Setting;
|
||||
/// </summary>
|
||||
internal static class SettingKeys
|
||||
{
|
||||
/// <summary>
|
||||
/// 上次打开时App的版本
|
||||
/// </summary>
|
||||
public const string LastAppVersion = "LastAppVersion";
|
||||
|
||||
/// <summary>
|
||||
/// 窗体左侧
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the task for a cancellation token, as well as the token registration. The registration is disposed when this instance is disposed.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">包装类型</typeparam>
|
||||
public sealed class CancellationTokenTaskCompletionSource : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The cancellation token registration, if any. This is <c>null</c> if the registration was not necessary.
|
||||
/// </summary>
|
||||
private readonly IDisposable? registration;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a task for the specified cancellation token, registering with the token if necessary.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token to observe.</param>
|
||||
public CancellationTokenTaskCompletionSource(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Task = Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
registration = cancellationToken.Register(() => tcs.TrySetResult(), useSynchronizationContext: false);
|
||||
Task = tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task for the source cancellation token.
|
||||
/// </summary>
|
||||
public Task Task { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the cancellation token registration, if any. Note that this may cause <see cref="Task"/> to never complete.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
registration?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// 信号量扩展
|
||||
@@ -15,7 +15,7 @@ public static class SemaphoreSlimExtensions
|
||||
/// <returns>可释放的对象,用于释放信号量</returns>
|
||||
public static async Task<IDisposable> EnterAsync(this SemaphoreSlim semaphoreSlim)
|
||||
{
|
||||
await semaphoreSlim.WaitAsync();
|
||||
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||
return new SemaphoreSlimReleaser(semaphoreSlim);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ namespace Snap.Hutao.Core.Threading;
|
||||
internal static class ThreadHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步切换到主线程
|
||||
/// 使用此静态方法以 异步切换到 主线程
|
||||
/// </summary>
|
||||
/// <remarks>使用 <see cref="Task.Yield"/> 异步切换到 后台线程</remarks>
|
||||
/// <returns>等待体</returns>
|
||||
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
|
||||
{
|
||||
|
||||
@@ -38,18 +38,6 @@ public static class Must
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务异常
|
||||
/// </summary>
|
||||
/// <param name="message">异常消息</param>
|
||||
/// <returns>异常的任务</returns>
|
||||
[SuppressMessage("", "VSTHRD200")]
|
||||
public static Task Fault(string message)
|
||||
{
|
||||
InvalidOperationException exception = new(message);
|
||||
return Task.FromException(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务异常
|
||||
/// </summary>
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Snap.Hutao.Core;
|
||||
/// </summary>
|
||||
internal abstract class WebView2Helper
|
||||
{
|
||||
private static bool hasEverDetected = false;
|
||||
private static bool isSupported = false;
|
||||
private static bool hasEverDetected;
|
||||
private static bool isSupported;
|
||||
private static string version = "未检测到 WebView2 运行时";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,8 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Win32;
|
||||
using Windows.Graphics;
|
||||
using Windows.UI;
|
||||
using Windows.Win32.Foundation;
|
||||
@@ -16,16 +18,18 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// 窗口管理器
|
||||
/// 主要包含了针对窗体的 P/Inoke 逻辑
|
||||
/// </summary>
|
||||
internal sealed class ExtendedWindow
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal sealed class ExtendedWindow<TWindow>
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
{
|
||||
private readonly HWND handle;
|
||||
private readonly AppWindow appWindow;
|
||||
|
||||
private readonly Window window;
|
||||
private readonly TWindow window;
|
||||
private readonly FrameworkElement titleBar;
|
||||
|
||||
private readonly ILogger<ExtendedWindow> logger;
|
||||
private readonly WindowSubclassManager subclassManager;
|
||||
private readonly ILogger<ExtendedWindow<TWindow>> logger;
|
||||
private readonly WindowSubclassManager<TWindow> subclassManager;
|
||||
|
||||
private readonly bool useLegacyDragBar;
|
||||
|
||||
@@ -34,11 +38,11 @@ internal sealed class ExtendedWindow
|
||||
/// </summary>
|
||||
/// <param name="window">窗口</param>
|
||||
/// <param name="titleBar">充当标题栏的元素</param>
|
||||
private ExtendedWindow(Window window, FrameworkElement titleBar)
|
||||
private ExtendedWindow(TWindow window, FrameworkElement titleBar)
|
||||
{
|
||||
this.window = window;
|
||||
this.titleBar = titleBar;
|
||||
logger = Ioc.Default.GetRequiredService<ILogger<ExtendedWindow>>();
|
||||
logger = Ioc.Default.GetRequiredService<ILogger<ExtendedWindow<TWindow>>>();
|
||||
|
||||
handle = (HWND)WindowNative.GetWindowHandle(window);
|
||||
|
||||
@@ -46,7 +50,7 @@ internal sealed class ExtendedWindow
|
||||
appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
|
||||
useLegacyDragBar = !AppWindowTitleBar.IsCustomizationSupported();
|
||||
subclassManager = new(handle, useLegacyDragBar);
|
||||
subclassManager = new(window, handle, useLegacyDragBar);
|
||||
|
||||
InitializeWindow();
|
||||
}
|
||||
@@ -55,11 +59,10 @@ internal sealed class ExtendedWindow
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="window">窗口</param>
|
||||
/// <param name="titleBar">标题栏</param>
|
||||
/// <returns>实例</returns>
|
||||
public static ExtendedWindow Initialize(Window window, FrameworkElement titleBar)
|
||||
public static ExtendedWindow<TWindow> Initialize(TWindow window)
|
||||
{
|
||||
return new(window, titleBar);
|
||||
return new(window, window.TitleBar);
|
||||
}
|
||||
|
||||
private static void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
|
||||
@@ -101,7 +104,8 @@ internal sealed class ExtendedWindow
|
||||
appWindow.Title = "胡桃";
|
||||
|
||||
ExtendsContentIntoTitleBar();
|
||||
Persistence.RecoverOrInit(appWindow);
|
||||
|
||||
Persistence.RecoverOrInit(appWindow, window.PersistSize, window.InitSize);
|
||||
|
||||
// Log basic window state here.
|
||||
(string pos, string size) = GetPostionAndSize(appWindow);
|
||||
@@ -113,14 +117,18 @@ internal sealed class ExtendedWindow
|
||||
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), subClassApplied ? "succeed" : "failed");
|
||||
logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager<TWindow>), subClassApplied ? "succeed" : "failed");
|
||||
|
||||
window.Closed += OnWindowClosed;
|
||||
}
|
||||
|
||||
private void OnWindowClosed(object sender, WindowEventArgs args)
|
||||
{
|
||||
Persistence.Save(appWindow);
|
||||
if (window.PersistSize)
|
||||
{
|
||||
Persistence.Save(appWindow);
|
||||
}
|
||||
|
||||
subclassManager?.Dispose();
|
||||
}
|
||||
|
||||
@@ -149,14 +157,8 @@ internal sealed class ExtendedWindow
|
||||
{
|
||||
double scale = Persistence.GetScaleForWindow(handle);
|
||||
|
||||
List<RectInt32> dragRectsList = new();
|
||||
|
||||
// 48 is the navigation button leftInset
|
||||
RectInt32 dragRect = new((int)(48 * scale), 0, (int)(titleBar.ActualWidth * scale), (int)(titleBar.ActualHeight * scale));
|
||||
dragRectsList.Add(dragRect);
|
||||
|
||||
RectInt32[] dragRects = dragRectsList.ToArray();
|
||||
|
||||
appTitleBar.SetDragRectangles(dragRects);
|
||||
RectInt32 dragRect = new RectInt32(48, 0, (int)titleBar.ActualWidth, (int)titleBar.ActualHeight).Scale(scale);
|
||||
appTitleBar.SetDragRectangles(dragRect.Enumerate().ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
/// <summary>
|
||||
/// 为扩展窗体提供必要的选项
|
||||
/// </summary>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal interface IExtendedWindowSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供的标题栏
|
||||
/// </summary>
|
||||
FrameworkElement TitleBar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否持久化尺寸
|
||||
/// </summary>
|
||||
bool PersistSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始大小
|
||||
/// </summary>
|
||||
SizeInt32 InitSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理最大最小信息
|
||||
/// </summary>
|
||||
/// <param name="pInfo">信息指针</param>
|
||||
/// <param name="scalingFactor">缩放比</param>
|
||||
unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor);
|
||||
}
|
||||
@@ -21,21 +21,26 @@ internal static class Persistence
|
||||
/// 设置窗体位置
|
||||
/// </summary>
|
||||
/// <param name="appWindow">应用窗体</param>
|
||||
public static void RecoverOrInit(AppWindow appWindow)
|
||||
/// <param name="persistSize">持久化尺寸</param>
|
||||
/// <param name="size">初始尺寸</param>
|
||||
public static void RecoverOrInit(AppWindow appWindow, bool persistSize, SizeInt32 size)
|
||||
{
|
||||
// Set first launch size.
|
||||
HWND hwnd = (HWND)Win32Interop.GetWindowFromWindowId(appWindow.Id);
|
||||
SizeInt32 size = TransformSizeForWindow(new(1200, 741), hwnd);
|
||||
RectInt32 rect = StructMarshal.RectInt32(size);
|
||||
SizeInt32 transformedSize = TransformSizeForWindow(size, hwnd);
|
||||
RectInt32 rect = StructMarshal.RectInt32(transformedSize);
|
||||
|
||||
RectInt32 target = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (ulong)(CompactRect)rect);
|
||||
if (target.Width * target.Height < 848 * 524)
|
||||
if (persistSize)
|
||||
{
|
||||
target = rect;
|
||||
RectInt32 persistedSize = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (ulong)(CompactRect)rect);
|
||||
if (persistedSize.Width * persistedSize.Height > 848 * 524)
|
||||
{
|
||||
rect = persistedSize;
|
||||
}
|
||||
}
|
||||
|
||||
TransformToCenterScreen(ref target);
|
||||
appWindow.MoveAndResize(target);
|
||||
TransformToCenterScreen(ref rect);
|
||||
appWindow.MoveAndResize(rect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +49,7 @@ internal static class Persistence
|
||||
/// <param name="appWindow">应用窗体</param>
|
||||
public static void Save(AppWindow appWindow)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.WindowRect, (ulong)(CompactRect)appWindow.GetRect());
|
||||
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)appWindow.GetRect());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +129,7 @@ internal static class Persistence
|
||||
return new(rect.X, rect.Y, rect.Width, rect.Height);
|
||||
}
|
||||
|
||||
public static explicit operator ulong(CompactRect rect)
|
||||
public static implicit operator ulong(CompactRect rect)
|
||||
{
|
||||
return rect.Value;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class SystemBackdrop
|
||||
|
||||
private class DispatcherQueueHelper
|
||||
{
|
||||
private object? dispatcherQueueController = null;
|
||||
private object? dispatcherQueueController;
|
||||
|
||||
/// <summary>
|
||||
/// 确保系统调度队列控制器存在
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
@@ -11,14 +12,14 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// <summary>
|
||||
/// 窗体子类管理器
|
||||
/// </summary>
|
||||
internal class WindowSubclassManager : IDisposable
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
{
|
||||
private const int WindowSubclassId = 101;
|
||||
private const int DragBarSubclassId = 102;
|
||||
|
||||
private const int MinWidth = 848;
|
||||
private const int MinHeight = 524;
|
||||
|
||||
private readonly TWindow window;
|
||||
private readonly HWND hwnd;
|
||||
private readonly bool isLegacyDragBar;
|
||||
private HWND hwndDragBar;
|
||||
@@ -30,12 +31,13 @@ internal class WindowSubclassManager : IDisposable
|
||||
/// <summary>
|
||||
/// 构造一个新的窗体子类管理器
|
||||
/// </summary>
|
||||
/// <param name="window">窗体实例</param>
|
||||
/// <param name="hwnd">窗体句柄</param>
|
||||
/// <param name="isLegacyDragBar">是否为经典标题栏区域</param>
|
||||
public WindowSubclassManager(HWND hwnd, bool isLegacyDragBar)
|
||||
public WindowSubclassManager(TWindow window, HWND hwnd, bool isLegacyDragBar)
|
||||
{
|
||||
Must.NotNull(hwnd);
|
||||
this.hwnd = hwnd;
|
||||
this.window = window;
|
||||
this.hwnd = Must.NotNull(hwnd);
|
||||
this.isLegacyDragBar = isLegacyDragBar;
|
||||
}
|
||||
|
||||
@@ -85,9 +87,7 @@ internal class WindowSubclassManager : IDisposable
|
||||
case WM_GETMINMAXINFO:
|
||||
{
|
||||
double scalingFactor = Persistence.GetScaleForWindow(hwnd);
|
||||
MINMAXINFO* info = (MINMAXINFO*)lParam.Value;
|
||||
info->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, info->ptMinTrackSize.X);
|
||||
info->ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, info->ptMinTrackSize.Y);
|
||||
window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// <see cref="BinaryReader"/> 扩展
|
||||
/// </summary>
|
||||
public static class BinaryReaderExtensions
|
||||
public static class BinaryReaderExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否处于流的结尾
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// <see cref="DateTimeOffset"/> 扩展
|
||||
/// </summary>
|
||||
public static class DateTimeOffsetExtensions
|
||||
public static class DateTimeOffsetExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the current <see cref="DateTimeOffset"/> to a <see cref="DateTimeOffset"/> that represents the local time.
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// 枚举拓展
|
||||
/// </summary>
|
||||
public static class EnumExtensions
|
||||
public static class EnumExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取枚举的描述
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
@@ -8,7 +9,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable{T}"/> 扩展
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtensions
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
||||
public static double AverageNoThrow(this List<int> source)
|
||||
@@ -107,7 +108,7 @@ public static partial class EnumerableExtensions
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
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))
|
||||
@@ -118,6 +119,53 @@ public static partial class EnumerableExtensions
|
||||
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>
|
||||
@@ -153,6 +201,20 @@ public static partial class EnumerableExtensions
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey, TElement}(IEnumerable{TSource}, Func{TSource, TKey}, Func{TSource, TElement})"/>
|
||||
public static Dictionary<TKey, TValue> ToDictionaryOverride<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> valueSelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
Dictionary<TKey, TValue> dictionary = new();
|
||||
|
||||
foreach (TSource value in source)
|
||||
{
|
||||
dictionary[keySelector(value)] = valueSelector(value);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个对 <see cref="TItem"/> 类型的计数器
|
||||
/// </summary>
|
||||
@@ -6,8 +6,19 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// 数高性能扩展
|
||||
/// </summary>
|
||||
public static class NumberExtensions
|
||||
public static class NumberExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取从右向左某位上的数字
|
||||
/// </summary>
|
||||
/// <param name="x">源</param>
|
||||
/// <param name="place">位</param>
|
||||
/// <returns>数字</returns>
|
||||
public static int AtPlace(this int x, int place)
|
||||
{
|
||||
return (int)(x / Math.Pow(10, place - 1)) % 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算给定整数的位数
|
||||
/// </summary>
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// 对象扩展
|
||||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
public static class ObjectExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// <see langword="as"/> 的链式调用扩展
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <summary>
|
||||
/// 包版本扩展
|
||||
/// </summary>
|
||||
public static class PackageVersionExtensions
|
||||
public static class PackageVersionExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 将包版本转换为版本
|
||||
@@ -4,6 +4,7 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
|
||||
namespace Snap.Hutao.Factory;
|
||||
|
||||
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Factory;
|
||||
[Injection(InjectAs.Transient, typeof(IAsyncRelayCommandFactory))]
|
||||
internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly ILogger<AsyncRelayCommandFactory> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的异步命令工厂
|
||||
@@ -82,6 +83,7 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
return command;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
private void ReportException(IAsyncRelayCommand command)
|
||||
{
|
||||
command.PropertyChanged += (sender, args) =>
|
||||
@@ -93,7 +95,8 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
if (asyncRelayCommand.ExecutionTask?.Exception is AggregateException exception)
|
||||
{
|
||||
Exception baseException = exception.GetBaseException();
|
||||
logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(asyncRelayCommand));
|
||||
logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(AsyncRelayCommand));
|
||||
Ioc.Default.GetRequiredService<HomaClient2>().UploadLogAsync(baseException).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ global using Microsoft.Extensions.Logging;
|
||||
// Snap.Hutao
|
||||
global using Snap.Hutao.Core.DependencyInjection;
|
||||
global using Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
global using Snap.Hutao.Core.Threading;
|
||||
global using Snap.Hutao.Core.Validation;
|
||||
|
||||
// Runtime
|
||||
|
||||
63
src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
Normal file
63
src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
Normal file
@@ -0,0 +1,63 @@
|
||||
<Window
|
||||
x:Class="Snap.Hutao.LaunchGameWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
Name="RootGrid"
|
||||
d:DataContext="{d:DesignInstance shv:LaunchGameViewModel}">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
x:Name="DragableGrid"
|
||||
Grid.Row="0"
|
||||
Height="32">
|
||||
<TextBlock
|
||||
Text="选择账号并启动"
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding GameAccounts}"
|
||||
SelectedItem="{Binding SelectedGameAccount,Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<StackPanel Margin="0,12">
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
Opacity="0.8"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding AttachUid,TargetNullValue=该账号尚未绑定 UID}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Button
|
||||
Margin="16"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="启动游戏"
|
||||
Command="{Binding LaunchCommand}"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
63
src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
Normal file
63
src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.ViewModel;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏窗口
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
public sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedWindowSource
|
||||
{
|
||||
private const int MinWidth = 240;
|
||||
private const int MinHeight = 240;
|
||||
|
||||
private const int MaxWidth = 320;
|
||||
private const int MaxHeight = 320;
|
||||
|
||||
private readonly IServiceScope scope;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的启动游戏窗口
|
||||
/// </summary>
|
||||
/// <param name="scopeFactory">范围工厂</param>
|
||||
public LaunchGameWindow(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
InitializeComponent();
|
||||
ExtendedWindow<LaunchGameWindow>.Initialize(this);
|
||||
|
||||
scope = scopeFactory.CreateScope();
|
||||
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService<LaunchGameViewModel>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FrameworkElement TitleBar { get => DragableGrid; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool PersistSize { get => false; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SizeInt32 InitSize { get => new(320, 320); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
scope.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor)
|
||||
{
|
||||
pInfo->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, pInfo->ptMinTrackSize.X);
|
||||
pInfo->ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, pInfo->ptMinTrackSize.Y);
|
||||
pInfo->ptMaxTrackSize.X = (int)Math.Min(MaxWidth * scalingFactor, pInfo->ptMaxTrackSize.X);
|
||||
pInfo->ptMaxTrackSize.Y = (int)Math.Min(MaxHeight * scalingFactor, pInfo->ptMaxTrackSize.Y);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
@@ -10,16 +12,41 @@ namespace Snap.Hutao;
|
||||
/// 主窗体
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
[Injection(InjectAs.Singleton, typeof(Window))] // This is for IEnumerable<Window>
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public sealed partial class MainWindow : Window
|
||||
public sealed partial class MainWindow : Window, IExtendedWindowSource
|
||||
{
|
||||
private const int MinWidth = 848;
|
||||
private const int MinHeight = 524;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
/// </summary>
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
ExtendedWindow.Initialize(this, TitleBarView.DragArea);
|
||||
ExtendedWindow<MainWindow>.Initialize(this);
|
||||
IsPresent = true;
|
||||
Closed += (s, e) => IsPresent = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否打开
|
||||
/// </summary>
|
||||
public static bool IsPresent { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FrameworkElement TitleBar { get => TitleBarView.DragArea; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool PersistSize { get => true; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SizeInt32 InitSize { get => new(1200, 741); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor)
|
||||
{
|
||||
pInfo->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, pInfo->ptMinTrackSize.X);
|
||||
pInfo->ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, pInfo->ptMinTrackSize.Y);
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 成就存档切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户切换消息
|
||||
/// </summary>
|
||||
/// <param name="oldArchive">老用户</param>
|
||||
/// <param name="newArchive">新用户</param>
|
||||
public AchievementArchiveChangedMessage(AchievementArchive? oldArchive, AchievementArchive? newArchive)
|
||||
: base(oldArchive, newArchive)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 祈愿记录存档切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal class GachaArchiveChangedMessage : ValueChangedMessage<GachaArchive>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户切换消息
|
||||
/// </summary>
|
||||
/// <param name="oldArchive">老用户</param>
|
||||
/// <param name="newArchive">新用户</param>
|
||||
public GachaArchiveChangedMessage(GachaArchive? oldArchive, GachaArchive? newArchive)
|
||||
: base(oldArchive, newArchive)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 用户切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal class UserChangedMessage : ValueChangedMessage<User>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户切换消息
|
||||
/// </summary>
|
||||
/// <param name="oldUser">老用户</param>
|
||||
/// <param name="newUser">新用户</param>
|
||||
public UserChangedMessage(User? oldUser, User? newUser)
|
||||
: base(oldUser, newUser)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,17 @@ namespace Snap.Hutao.Message;
|
||||
/// 值变化消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">值的类型</typeparam>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal abstract class ValueChangedMessage<TValue>
|
||||
where TValue : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 动态访问
|
||||
/// </summary>
|
||||
public ValueChangedMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的值变化消息
|
||||
/// </summary>
|
||||
@@ -24,10 +32,10 @@ internal abstract class ValueChangedMessage<TValue>
|
||||
/// <summary>
|
||||
/// 旧的值
|
||||
/// </summary>
|
||||
public TValue? OldValue { get; private set; }
|
||||
public TValue? OldValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新的值
|
||||
/// </summary>
|
||||
public TValue? NewValue { get; private set; }
|
||||
public TValue? NewValue { get; set; }
|
||||
}
|
||||
215
src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.Designer.cs
generated
Normal file
215
src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.Designer.cs
generated
Normal file
@@ -0,0 +1,215 @@
|
||||
// <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("20221031104940_GameAccount")]
|
||||
partial class GameAccount
|
||||
{
|
||||
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.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.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,34 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
public partial class GameAccount : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "game_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
AttachUid = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
MihoyoSDK = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_game_accounts", x => x.InnerId);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "game_accounts");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
@@ -131,6 +131,31 @@ namespace Snap.Hutao.Migrations
|
||||
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")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
|
||||
/// <summary>
|
||||
/// 用于视图绑定的成就
|
||||
/// </summary>
|
||||
public class Achievement : Observable
|
||||
public class Achievement : ObservableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 满进度占位符
|
||||
@@ -28,7 +30,7 @@ public class Achievement : Observable
|
||||
this.inner = inner;
|
||||
this.entity = entity;
|
||||
|
||||
// Property should only be set when is user checking.
|
||||
// Property should only be set when it's user checking.
|
||||
isChecked = (int)entity.Status >= 2;
|
||||
}
|
||||
|
||||
@@ -50,7 +52,7 @@ public class Achievement : Observable
|
||||
get => isChecked;
|
||||
set
|
||||
{
|
||||
Set(ref isChecked, value);
|
||||
SetProperty(ref isChecked, value);
|
||||
|
||||
// Only update state when checked
|
||||
if (value)
|
||||
@@ -67,6 +69,6 @@ public class Achievement : Observable
|
||||
/// </summary>
|
||||
public string Time
|
||||
{
|
||||
get => entity.Time.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
get => entity.Time.ToString("yyyy.MM.dd HH:mm:ss");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 词条评分
|
||||
/// </summary>
|
||||
public struct AffixScore
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的圣遗物评分
|
||||
/// </summary>
|
||||
/// <param name="score">评分</param>
|
||||
/// <param name="weight">最大值</param>
|
||||
public AffixScore(double score, double weight)
|
||||
{
|
||||
Score = score;
|
||||
Weight = weight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评分
|
||||
/// </summary>
|
||||
public double Score { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 权重
|
||||
/// </summary>
|
||||
public double Weight { get; }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色信息
|
||||
/// </summary>
|
||||
public class Avatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 侧面图标
|
||||
/// </summary>
|
||||
public Uri SideIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public string Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 好感度等级
|
||||
/// </summary>
|
||||
public int FetterLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public Weapon Weapon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物列表
|
||||
/// </summary>
|
||||
public List<Reliquary> Reliquaries { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 命之座列表
|
||||
/// </summary>
|
||||
public List<Constellation> Constellations { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 技能列表
|
||||
/// </summary>
|
||||
public List<Skill> Skills { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 属性
|
||||
/// </summary>
|
||||
public List<Pair2<string, string, string?>> Properties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 评分
|
||||
/// </summary>
|
||||
public string Score { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 双爆评分
|
||||
/// </summary>
|
||||
public string CritScore { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 命座信息
|
||||
/// </summary>
|
||||
public class Constellation : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否激活
|
||||
/// </summary>
|
||||
public bool IsActiviated { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 装备基类
|
||||
/// </summary>
|
||||
public class EquipBase : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public string Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 品质
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主属性
|
||||
/// </summary>
|
||||
public Pair<string, string> MainProperty { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 名称与描述抽象
|
||||
/// </summary>
|
||||
public abstract class NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家信息
|
||||
/// </summary>
|
||||
public class Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string Nickname { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签名
|
||||
/// </summary>
|
||||
public string Signature { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 完成成就数
|
||||
/// </summary>
|
||||
public int FinishAchievementNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 深渊层间
|
||||
/// </summary>
|
||||
public string SipralAbyssFloorLevel { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物
|
||||
/// </summary>
|
||||
public class Reliquary : EquipBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始词条
|
||||
/// </summary>
|
||||
public List<ReliquarySubProperty> PrimarySubProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 强化词条
|
||||
/// </summary>
|
||||
public List<ReliquarySubProperty> SecondarySubProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 评分
|
||||
/// </summary>
|
||||
public double Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化评分
|
||||
/// </summary>
|
||||
public string ScoreFormatted { get => $"{Score:F2}"; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物副词条
|
||||
/// </summary>
|
||||
public class ReliquarySubProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造副属性
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="score">评分</param>
|
||||
public ReliquarySubProperty(string name, string value, double score)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
Score = score;
|
||||
|
||||
// only 0.2 | 0.4 | 0.6 | 0.8 | 1.0
|
||||
Opacity = score switch
|
||||
{
|
||||
< 25 => 0.25,
|
||||
< 50 => 0.5,
|
||||
< 75 => 0.75,
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 评分
|
||||
/// </summary>
|
||||
public double Score { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 透明度
|
||||
/// </summary>
|
||||
public double Opacity { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 天赋
|
||||
/// </summary>
|
||||
public class Skill : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 技能属性
|
||||
/// </summary>
|
||||
public LevelParam<string, ParameterInfo> Info { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家与角色列表的包装器
|
||||
/// </summary>
|
||||
public class Summary
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家信息
|
||||
/// </summary>
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 角色列表
|
||||
/// </summary>
|
||||
public List<Avatar> Avatars { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public class Weapon : EquipBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 副属性
|
||||
/// </summary>
|
||||
public Pair<string, string> SubProperty { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 精炼属性
|
||||
/// </summary>
|
||||
public string AffixLevel { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 精炼名称
|
||||
/// </summary>
|
||||
public string AffixName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 精炼被动
|
||||
/// </summary>
|
||||
public string AffixDescription { get; set; } = default!;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class GachaStatistics
|
||||
public TypedWishSummary PermanentWish { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 历史
|
||||
/// 历史卡池
|
||||
/// </summary>
|
||||
public List<HistoryWish> HistoryWishes { get; set; } = default!;
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@ namespace Snap.Hutao.Model.Binding.Gacha;
|
||||
/// </summary>
|
||||
public class HistoryWish : WishBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 版本
|
||||
/// </summary>
|
||||
public string Version { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 卡池图片
|
||||
/// </summary>
|
||||
public Uri BannerImage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 五星Up
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public class ComplexAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个胡桃数据库角色
|
||||
/// </summary>
|
||||
/// <param name="avatar">元数据角色</param>
|
||||
/// <param name="rate">率</param>
|
||||
public ComplexAvatar(Avatar avatar, double rate)
|
||||
{
|
||||
Name = avatar.Name;
|
||||
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
|
||||
SideIcon = AvatarSideIconConverter.IconNameToUri(avatar.SideIcon);
|
||||
Quality = avatar.Quality;
|
||||
Rate = $"{rate:P3}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 侧面图标
|
||||
/// </summary>
|
||||
public Uri SideIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 比率
|
||||
/// </summary>
|
||||
public string Rate { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色搭配
|
||||
/// </summary>
|
||||
public class ComplexAvatarCollocation : ComplexAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色搭配
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="rate">比率</param>
|
||||
public ComplexAvatarCollocation(Avatar avatar)
|
||||
: base(avatar, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
public AvatarId AvatarId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public List<ComplexAvatar> Avatars { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public List<ComplexWeapon> Weapons { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装
|
||||
/// </summary>
|
||||
public List<ComplexReliquarySet> ReliquarySets { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色命座信息
|
||||
/// </summary>
|
||||
internal class ComplexAvatarConstellationInfo : ComplexAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色命座信息
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="rate">持有率</param>
|
||||
/// <param name="rates">命座比率</param>
|
||||
public ComplexAvatarConstellationInfo(Avatar avatar, double rate, IEnumerable<double> rates)
|
||||
: base(avatar, rate)
|
||||
{
|
||||
Rates = rates.Select(r => $"{r:P3}").ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命座比率
|
||||
/// </summary>
|
||||
public List<string> Rates { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色榜
|
||||
/// </summary>
|
||||
internal class ComplexAvatarRank
|
||||
{
|
||||
/// <summary>
|
||||
/// 层数
|
||||
/// </summary>
|
||||
public string Floor { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 排行信息
|
||||
/// </summary>
|
||||
public List<ComplexAvatar> Avatars { get; set; } = default!;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user