diff --git a/.gitignore b/.gitignore
index 9a1e7e72..13219c40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,9 +4,12 @@
.vs/
-src/SettingsUI/bin
-src/SettingsUI/obj
+src/Snap.Hutao/SettingsUI/bin
+src/Snap.Hutao/SettingsUI/obj
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.SourceGeneration/bin/
+src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/
diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig
index b1697969..c24ac219 100644
--- a/src/Snap.Hutao/.editorconfig
+++ b/src/Snap.Hutao/.editorconfig
@@ -94,6 +94,9 @@ dotnet_diagnostic.SA1636.severity = none
# SA1414: Tuple types in signatures should have element names
dotnet_diagnostic.SA1414.severity = none
+# SA0001: XML comment analysis disabled
+dotnet_diagnostic.SA0001.severity = none
+
[*.vb]
#### 命名样式 ####
diff --git a/src/SettingsUI/Controls/CheckBoxWithDescriptionControl.cs b/src/Snap.Hutao/SettingsUI/Controls/CheckBoxWithDescriptionControl.cs
similarity index 100%
rename from src/SettingsUI/Controls/CheckBoxWithDescriptionControl.cs
rename to src/Snap.Hutao/SettingsUI/Controls/CheckBoxWithDescriptionControl.cs
diff --git a/src/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs b/src/Snap.Hutao/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs
similarity index 100%
rename from src/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs
rename to src/Snap.Hutao/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs
diff --git a/src/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml b/src/Snap.Hutao/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml
similarity index 100%
rename from src/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml
rename to src/Snap.Hutao/SettingsUI/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml
diff --git a/src/SettingsUI/Controls/Setting/Setting.cs b/src/Snap.Hutao/SettingsUI/Controls/Setting/Setting.cs
similarity index 100%
rename from src/SettingsUI/Controls/Setting/Setting.cs
rename to src/Snap.Hutao/SettingsUI/Controls/Setting/Setting.cs
diff --git a/src/SettingsUI/Controls/Setting/Setting.xaml b/src/Snap.Hutao/SettingsUI/Controls/Setting/Setting.xaml
similarity index 100%
rename from src/SettingsUI/Controls/Setting/Setting.xaml
rename to src/Snap.Hutao/SettingsUI/Controls/Setting/Setting.xaml
diff --git a/src/SettingsUI/Controls/SettingExpander/SettingExpander.cs b/src/Snap.Hutao/SettingsUI/Controls/SettingExpander/SettingExpander.cs
similarity index 100%
rename from src/SettingsUI/Controls/SettingExpander/SettingExpander.cs
rename to src/Snap.Hutao/SettingsUI/Controls/SettingExpander/SettingExpander.cs
diff --git a/src/SettingsUI/Controls/SettingsGroup/SettingsGroup.cs b/src/Snap.Hutao/SettingsUI/Controls/SettingsGroup/SettingsGroup.cs
similarity index 100%
rename from src/SettingsUI/Controls/SettingsGroup/SettingsGroup.cs
rename to src/Snap.Hutao/SettingsUI/Controls/SettingsGroup/SettingsGroup.cs
diff --git a/src/SettingsUI/Controls/SettingsGroup/SettingsGroup.xaml b/src/Snap.Hutao/SettingsUI/Controls/SettingsGroup/SettingsGroup.xaml
similarity index 100%
rename from src/SettingsUI/Controls/SettingsGroup/SettingsGroup.xaml
rename to src/Snap.Hutao/SettingsUI/Controls/SettingsGroup/SettingsGroup.xaml
diff --git a/src/SettingsUI/Controls/SettingsGroup/SettingsGroupAutomationPeer.cs b/src/Snap.Hutao/SettingsUI/Controls/SettingsGroup/SettingsGroupAutomationPeer.cs
similarity index 100%
rename from src/SettingsUI/Controls/SettingsGroup/SettingsGroupAutomationPeer.cs
rename to src/Snap.Hutao/SettingsUI/Controls/SettingsGroup/SettingsGroupAutomationPeer.cs
diff --git a/src/SettingsUI/SettingsUI.csproj b/src/Snap.Hutao/SettingsUI/SettingsUI.csproj
similarity index 99%
rename from src/SettingsUI/SettingsUI.csproj
rename to src/Snap.Hutao/SettingsUI/SettingsUI.csproj
index 891362fd..d06543fb 100644
--- a/src/SettingsUI/SettingsUI.csproj
+++ b/src/Snap.Hutao/SettingsUI/SettingsUI.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/SettingsUI/Styles/Button.xaml b/src/Snap.Hutao/SettingsUI/Styles/Button.xaml
similarity index 100%
rename from src/SettingsUI/Styles/Button.xaml
rename to src/Snap.Hutao/SettingsUI/Styles/Button.xaml
diff --git a/src/SettingsUI/Styles/Common.xaml b/src/Snap.Hutao/SettingsUI/Styles/Common.xaml
similarity index 100%
rename from src/SettingsUI/Styles/Common.xaml
rename to src/Snap.Hutao/SettingsUI/Styles/Common.xaml
diff --git a/src/SettingsUI/Styles/TextBlock.xaml b/src/Snap.Hutao/SettingsUI/Styles/TextBlock.xaml
similarity index 100%
rename from src/SettingsUI/Styles/TextBlock.xaml
rename to src/Snap.Hutao/SettingsUI/Styles/TextBlock.xaml
diff --git a/src/SettingsUI/Themes/Colors.xaml b/src/Snap.Hutao/SettingsUI/Themes/Colors.xaml
similarity index 100%
rename from src/SettingsUI/Themes/Colors.xaml
rename to src/Snap.Hutao/SettingsUI/Themes/Colors.xaml
diff --git a/src/SettingsUI/Themes/Generic.xaml b/src/Snap.Hutao/SettingsUI/Themes/Generic.xaml
similarity index 100%
rename from src/SettingsUI/Themes/Generic.xaml
rename to src/Snap.Hutao/SettingsUI/Themes/Generic.xaml
diff --git a/src/SettingsUI/Themes/SettingsExpanderStyles.xaml b/src/Snap.Hutao/SettingsUI/Themes/SettingsExpanderStyles.xaml
similarity index 100%
rename from src/SettingsUI/Themes/SettingsExpanderStyles.xaml
rename to src/Snap.Hutao/SettingsUI/Themes/SettingsExpanderStyles.xaml
diff --git a/src/SettingsUI/Themes/SettingsUI.xaml b/src/Snap.Hutao/SettingsUI/Themes/SettingsUI.xaml
similarity index 100%
rename from src/SettingsUI/Themes/SettingsUI.xaml
rename to src/Snap.Hutao/SettingsUI/Themes/SettingsUI.xaml
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs
new file mode 100644
index 00000000..a98d1b20
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs
@@ -0,0 +1,98 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+
+namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
+
+///
+/// 注入代码生成器
+/// 旨在使用源生成器提高注入效率
+/// 防止在运行时动态查找注入类型
+///
+[Generator]
+public class InjectionGenerator : ISourceGenerator
+{
+ ///
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // Register a syntax receiver that will be created for each generation pass
+ context.RegisterForSyntaxNotifications(() => new InjectionSyntaxContextReceiver());
+ }
+
+ ///
+ public void Execute(GeneratorExecutionContext context)
+ {
+ // retrieve the populated receiver
+ if (context.SyntaxContextReceiver is not InjectionSyntaxContextReceiver receiver)
+ {
+ return;
+ }
+
+ StringBuilder sourceCodeBuilder = new();
+ sourceCodeBuilder.Append(@"// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+// This class is generated by Snap.Hutao.SourceGeneration
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Snap.Hutao.Core.DependencyInjection;
+
+internal static partial class ServiceCollectionExtensions
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Snap.Hutao.SourceGeneration.DedendencyInjection.InjectionGenerator"","" 1.0.0.0"")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public static partial IServiceCollection AddInjections(this IServiceCollection services)
+ {");
+
+ sourceCodeBuilder.Append("\r\n");
+ FillWithInjectionServices(receiver, sourceCodeBuilder);
+ sourceCodeBuilder.Append(@" return services;
+ }
+}");
+ sourceCodeBuilder.Append("\r\n");
+
+ context.AddSource("ServiceCollectionExtensions.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
+ }
+
+ private static void FillWithInjectionServices(InjectionSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
+ {
+ foreach (INamedTypeSymbol classSymbol in receiver.Classes.OrderByDescending(symbol => symbol.ToDisplayString()))
+ {
+ AttributeData injectionInfo = classSymbol
+ .GetAttributes()
+ .Single(attr => attr.AttributeClass!.ToDisplayString() == InjectionSyntaxContextReceiver.AttributeName);
+ ImmutableArray arguments = injectionInfo.ConstructorArguments;
+
+ TypedConstant injectAs = arguments[0];
+
+ string injectAsName = injectAs.ToCSharpString();
+ switch (injectAsName)
+ {
+ case "Snap.Hutao.Core.DependencyInjection.InjectAs.Singleton":
+ sourceCodeBuilder.Append(@" services.AddSingleton(");
+ break;
+ case "Snap.Hutao.Core.DependencyInjection.InjectAs.Transient":
+ sourceCodeBuilder.Append(@" services.AddTransient(");
+ break;
+ default:
+ throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]。");
+ }
+
+ if (arguments.Length == 2)
+ {
+ TypedConstant interfaceType = arguments[1];
+ sourceCodeBuilder.Append($"{interfaceType.ToCSharpString()}, ");
+ }
+
+ sourceCodeBuilder.Append($"typeof({classSymbol.ToDisplayString()}));\r\n");
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs
new file mode 100644
index 00000000..33ed1a84
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs
@@ -0,0 +1,43 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+
+namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
+
+///
+/// Created on demand before each generation pass
+///
+public class InjectionSyntaxContextReceiver : ISyntaxContextReceiver
+{
+ public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
+
+ ///
+ /// 所有需要注入的类型符号
+ ///
+ public List Classes { get; } = new();
+
+ ///
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ // any class with at least one attribute is a candidate for injection generation
+ if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
+ {
+ // get as named type symbol
+ if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
+ {
+ if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
+ {
+ Classes.Add(classSymbol);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj
new file mode 100644
index 00000000..9dd6ef85
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netstandard2.0
+ false
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json
new file mode 100644
index 00000000..6852fc3d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "companyName": "DGP Studio",
+ "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license.",
+ "xmlHeader": false,
+ "variables": {
+ "licenseName": "MIT"
+ }
+ },
+ "orderingRules": {
+ "elementOrder": [
+ "kind",
+ "accessibility",
+ "constant",
+ "static",
+ "readonly"
+ ],
+ "usingDirectivesPlacement": "outsideNamespace"
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao.sln b/src/Snap.Hutao/Snap.Hutao.sln
index c8f8a852..2f861db8 100644
--- a/src/Snap.Hutao/Snap.Hutao.sln
+++ b/src/Snap.Hutao/Snap.Hutao.sln
@@ -10,7 +10,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "..\SettingsUI\SettingsUI.csproj", "{FC2E96B6-775E-465C-82FB-9931826C8049}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\SettingsUI.csproj", "{DCA5678C-896E-49FB-97A7-5A504A5CFF17}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -48,22 +50,38 @@ Global
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.ActiveCfg = Release|x86
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Build.0 = Release|x86
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Deploy.0 = Release|x86
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|arm64.ActiveCfg = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|arm64.Build.0 = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|x64.ActiveCfg = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|x64.Build.0 = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|x86.ActiveCfg = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Debug|x86.Build.0 = Debug|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|Any CPU.Build.0 = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|arm64.ActiveCfg = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|arm64.Build.0 = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|x64.ActiveCfg = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|x64.Build.0 = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|x86.ActiveCfg = Release|Any CPU
- {FC2E96B6-775E-465C-82FB-9931826C8049}.Release|x86.Build.0 = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|arm64.Build.0 = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x64.Build.0 = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Debug|x86.Build.0 = Debug|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.ActiveCfg = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.Build.0 = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.ActiveCfg = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.Build.0 = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.ActiveCfg = Release|Any CPU
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.Build.0 = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.Build.0 = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.Build.0 = Debug|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.ActiveCfg = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.Build.0 = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.ActiveCfg = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
index 825b0a50..06d92ef4 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
@@ -12,7 +12,7 @@ namespace Snap.Hutao;
///
public partial class App : Application
{
- private Window? mainWindow;
+ private static Window? window;
///
/// Initializes the singleton application object.
@@ -25,6 +25,11 @@ public partial class App : Application
InitializeDependencyInjection();
}
+ ///
+ /// 当前窗口
+ ///
+ public static Window? Window { get => window; set => window = value; }
+
///
/// Invoked when the application is launched normally by the end user.
/// Other entry points will be used such as when the application is launched to open a specific file.
@@ -32,8 +37,8 @@ public partial class App : Application
/// Details about the launch request and process.
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
- mainWindow = Ioc.Default.GetRequiredService();
- mainWindow.Activate();
+ Window = Ioc.Default.GetRequiredService();
+ Window.Activate();
}
private static void InitializeDependencyInjection()
@@ -45,7 +50,7 @@ public partial class App : Application
.AddDatebase()
.AddHttpClients()
.AddDefaultJsonSerializerOptions()
- .AddInjections(typeof(App))
+ .AddInjections()
.BuildServiceProvider();
Ioc.Default.ConfigureServices(services);
diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
index c68703f8..c6cef5c8 100644
--- a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Context.Database;
///
/// 应用程序数据库上下文
///
-internal class AppDbContext : DbContext
+public class AppDbContext : DbContext
{
///
/// 构造一个新的应用程序数据库上下文
@@ -25,6 +25,11 @@ internal class AppDbContext : DbContext
///
public DbSet Settings { get; set; } = default!;
+ ///
+ /// 用户表
+ ///
+ public DbSet Users { get; set; } = default!;
+
///
/// 构造一个临时的应用程序数据库上下文
///
@@ -34,4 +39,4 @@ internal class AppDbContext : DbContext
{
return new(new DbContextOptionsBuilder().UseSqlite(sqlConnectionString).Options);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContextDesignTimeFactory.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContextDesignTimeFactory.cs
new file mode 100644
index 00000000..a849e0d3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContextDesignTimeFactory.cs
@@ -0,0 +1,21 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Design;
+using Snap.Hutao.Context.FileSystem;
+
+namespace Snap.Hutao.Context.Database;
+
+public class AppDbContextDesignTimeFactory : IDesignTimeDbContextFactory
+{
+ public AppDbContext CreateDbContext(string[] args)
+ {
+ MyDocumentContext myDocument = new(new());
+ myDocument.EnsureDirectory();
+
+ string dbFile = myDocument.Locate("Userdata.db");
+ string sqlConnectionString = $"Data Source={dbFile}";
+
+ return AppDbContext.CreateFrom(sqlConnectionString);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs
index 05778421..fee5136e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs
@@ -1,4 +1,7 @@
-using CommunityToolkit.WinUI.UI;
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.WinUI.UI;
using CommunityToolkit.WinUI.UI.Animations;
using Microsoft.UI.Composition;
using System.Numerics;
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs
index f9fb285a..468af39d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs
@@ -1,4 +1,7 @@
-using CommunityToolkit.WinUI.UI;
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.WinUI.UI;
using CommunityToolkit.WinUI.UI.Animations;
using Microsoft.UI.Composition;
using System.Numerics;
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs b/src/Snap.Hutao/Snap.Hutao/Core/Convertion/Md5Convert.cs
similarity index 94%
rename from src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
rename to src/Snap.Hutao/Snap.Hutao/Core/Convertion/Md5Convert.cs
index 699803ea..ad8f41e6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Convertion/Md5Convert.cs
@@ -4,7 +4,7 @@
using System.Security.Cryptography;
using System.Text;
-namespace Snap.Hutao.Core.Converting;
+namespace Snap.Hutao.Core.Convertion;
///
/// 支持Md5转换
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index eae06975..53f8ed31 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT license.
using Microsoft.Win32;
-using Snap.Hutao.Core.Converting;
+using Snap.Hutao.Core.Convertion;
using Snap.Hutao.Extension;
using Windows.ApplicationModel;
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs
index 69b7658c..0a6a4dd6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs
@@ -32,10 +32,10 @@ public class InjectionAttribute : Attribute
///
/// 注入类型
///
- public InjectAs InjectAs { get; set; }
+ public InjectAs InjectAs { get; }
///
/// 该类实现的接口类型
///
- public Type? InterfaceType { get; set; }
+ public Type? InterfaceType { get; }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceCollectionExtensions.cs
index 75b11955..f5d6ffdc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceCollectionExtensions.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.Extensions.DependencyInjection;
-using Snap.Hutao.Extension;
namespace Snap.Hutao.Core.DependencyInjection;
@@ -10,50 +9,12 @@ namespace Snap.Hutao.Core.DependencyInjection;
/// 服务管理器
/// 依赖注入的核心管理类
///
-internal static class ServiceCollectionExtensions
+internal static partial class ServiceCollectionExtensions
{
///
- /// 向容器注册服务, 调用
+ /// 向容器注册服务
///
/// 容器
- /// 入口类型,该类型所在的程序集均会被扫描
/// 可继续操作的服务集合
- public static IServiceCollection AddInjections(this IServiceCollection services, Type entryType)
- {
- entryType.Assembly.ForEachType(type => Register(services, type));
- return services;
- }
-
- ///
- /// 向容器注册类型
- ///
- /// 容器
- /// 待检测的类型
- /// 可继续操作的服务集合
- public static IServiceCollection Register(this IServiceCollection services, Type type)
- {
- if (type.TryGetAttribute(out InjectionAttribute? attr))
- {
- if (attr.InterfaceType is not null)
- {
- return attr.InjectAs switch
- {
- InjectAs.Singleton => services.AddSingleton(attr.InterfaceType, type),
- InjectAs.Transient => services.AddTransient(attr.InterfaceType, type),
- _ => Must.NeverHappen(),
- };
- }
- else
- {
- return attr.InjectAs switch
- {
- InjectAs.Singleton => services.AddSingleton(type),
- InjectAs.Transient => services.AddTransient(type),
- _ => throw Must.NeverHappen(),
- };
- }
- }
-
- return services;
- }
+ public static partial IServiceCollection AddInjections(this IServiceCollection services);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Json.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Json.cs
deleted file mode 100644
index af02fe38..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Json.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.IO;
-using System.Text.Json;
-
-namespace Snap.Hutao.Core.Json;
-
-///
-/// Json操作
-///
-[Injection(InjectAs.Transient)]
-public class Json
-{
- private readonly JsonSerializerOptions jsonSerializerOptions;
- private readonly ILogger logger;
-
- ///
- /// 初始化一个新的 Json操作 实例
- ///
- /// 配置
- /// 日志器
- public Json(JsonSerializerOptions jsonSerializerOptions, ILogger logger)
- {
- this.jsonSerializerOptions = jsonSerializerOptions;
- this.logger = logger;
- }
-
- ///
- /// 将JSON反序列化为指定的.NET类型
- ///
- /// 要反序列化的对象的类型
- /// 要反序列化的JSON
- /// Json字符串中的反序列化对象, 如果反序列化失败会返回
- public T? ToObject(string value)
- {
- try
- {
- T? result = JsonSerializer.Deserialize(value);
- return result;
- }
- catch (Exception ex)
- {
- logger.LogError("反序列化Json时遇到问题\n{ex}", ex);
- }
-
- return default(T);
- }
-
- ///
- /// 将JSON反序列化为指定的.NET类型
- /// 若为null则返回一个新建的实例
- ///
- /// 指定的类型
- /// 字符串
- /// Json字符串中的反序列化对象, 如果反序列化失败会抛出异常
- public T ToObjectOrNew(string value)
- where T : new()
- {
- return ToObject(value) ?? new T();
- }
-
- ///
- /// 将指定的对象序列化为JSON字符串
- ///
- /// 要序列化的对象
- /// 对象的JSON字符串表示形式
- public string Stringify(object? value)
- {
- return JsonSerializer.Serialize(value, jsonSerializerOptions);
- }
-
- ///
- /// 使用 , 和 从文件中读取后转化为实体类
- ///
- /// 要反序列化的对象的类型
- /// 存放JSON数据的文件路径
- /// JSON字符串中的反序列化对象, 如果反序列化失败则抛出异常,若文件不存在则返回
- public T? FromFile(string fileName)
- {
- if (File.Exists(fileName))
- {
- // FileShare.Read is important to read some file
- using (StreamReader sr = new(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
- {
- return ToObject(sr.ReadToEnd());
- }
- }
- else
- {
- return default;
- }
- }
-
- ///
- /// 使用 , 和 从文件中读取后转化为实体类
- /// 若为null则返回一个新建的实例
- ///
- /// 要反序列化的对象的类型
- /// 存放JSON数据的文件路径
- /// JSON字符串中的反序列化对象
- public T FromFileOrNew(string fileName)
- where T : new()
- {
- return FromFile(fileName) ?? new T();
- }
-
- ///
- /// 从文件中读取后转化为实体类
- ///
- /// 要反序列化的对象的类型
- /// 存放JSON数据的文件
- /// JSON字符串中的反序列化对象
- public T? FromFile(FileInfo file)
- {
- using (StreamReader sr = file.OpenText())
- {
- return ToObject(sr.ReadToEnd());
- }
- }
-
- ///
- /// 将对象保存到文件
- ///
- /// 文件名称
- /// 对象
- public void ToFile(string fileName, object? value)
- {
- File.WriteAllText(fileName, Stringify(value));
- }
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Result.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Result.cs
new file mode 100644
index 00000000..dd6c321f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Result.cs
@@ -0,0 +1,46 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Threading;
+
+///
+/// 用于包装异步操作的结果
+///
+///
+///
+public record Result
+ where TResult : notnull
+ where TValue : notnull
+{
+ ///
+ /// 构造一个新的结果
+ ///
+ /// 是否成功
+ /// 值
+ public Result(TResult isOk, TValue value)
+ {
+ IsOk = isOk;
+ Value = value;
+ }
+
+ ///
+ /// 是否成功
+ ///
+ public TResult IsOk { get; }
+
+ ///
+ /// 值
+ ///
+ public TValue Value { get; }
+
+ ///
+ /// 用于元组析构
+ ///
+ /// 是否成功
+ /// 值
+ public void Deconstruct(out TResult isOk, out TValue value)
+ {
+ isOk = IsOk;
+ value = Value;
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Results.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Results.cs
new file mode 100644
index 00000000..b6c56d02
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Results.cs
@@ -0,0 +1,24 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Threading;
+
+///
+/// 构造复杂的结果
+///
+public static class Results
+{
+ ///
+ /// 根据条件构造结果
+ ///
+ /// 结果的类型
+ /// 条件
+ /// 条件符合时的值
+ /// 条件不符合时的值
+ /// 结果
+ public static Result Condition(bool condition, T trueValue, T falseValue)
+ where T : notnull
+ {
+ return new(condition, condition ? trueValue : falseValue);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs
index 70822137..0cf7c88c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs
@@ -77,4 +77,4 @@ public class Watcher : Observable
watcher.IsCompleted = true;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
index f53ad1a5..e72d7987 100644
--- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
@@ -11,17 +11,6 @@ namespace Snap.Hutao.Extension;
///
public static class EnumerableExtensions
{
- ///
- /// 将二维可枚举对象一维化
- ///
- /// 源类型
- /// 源
- /// 扁平的对象
- public static IEnumerable Flatten(this IEnumerable> source)
- {
- return source.SelectMany(x => x);
- }
-
///
/// 计数
///
@@ -66,18 +55,75 @@ public static class EnumerableExtensions
return source ?? new();
}
+ ///
+ /// 寻找枚举中唯一的值,找不到时
+ /// 回退到首个或默认值
+ ///
+ /// 源类型
+ /// 源
+ /// 谓语
+ /// 目标项
+ public static TSource? FirstOrFirstOrDefault(this IEnumerable source, Func predicate)
+ {
+ return source.FirstOrDefault(predicate) ?? source.FirstOrDefault();
+ }
+
+ ///
+ /// 将二维可枚举对象一维化
+ ///
+ /// 源类型
+ /// 源
+ /// 扁平的对象
+ public static IEnumerable Flatten(this IEnumerable> source)
+ {
+ return source.SelectMany(x => x);
+ }
+
///
/// 对集合中的每个物品执行指定的操作
///
/// 集合类型
/// 集合
/// 指定的操作
- public static void ForEach(this IEnumerable source, Action action)
+ /// 修改后的集合
+ public static IEnumerable ForEach(this IEnumerable source, Action action)
{
foreach (TSource item in source)
{
action(item);
}
+
+ return source;
+ }
+
+ ///
+ /// 对集合中的每个物品执行指定的操作
+ ///
+ /// 集合类型
+ /// 集合
+ /// 指定的操作
+ /// 修改后的集合
+ public static async Task> ForEachAsync(this IEnumerable source, Func func)
+ {
+ foreach (TSource item in source)
+ {
+ await func(item);
+ }
+
+ return source;
+ }
+
+ ///
+ /// 寻找枚举中唯一的值,找不到时
+ /// 回退到首个或默认值
+ ///
+ /// 源类型
+ /// 源
+ /// 谓语
+ /// 目标项
+ public static TSource? SingleOrFirstOrDefault(this IEnumerable source, Func predicate)
+ {
+ return source.SingleOrDefault(predicate) ?? source.FirstOrDefault();
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/TaskExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/TaskExtensions.cs
index aa4bb7df..288501b6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Extension/TaskExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/TaskExtensions.cs
@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using System.Collections.Generic;
-
namespace Snap.Hutao.Extension;
///
diff --git a/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs b/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs
index 76acac97..d8cc82de 100644
--- a/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs
+++ b/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs
@@ -12,3 +12,4 @@ global using System.ComponentModel;
global using System.Diagnostics.CodeAnalysis;
global using System.Threading;
global using System.Threading.Tasks;
+global using System.Windows.Input;
diff --git a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
index 7f07aa67..b1d4c1bf 100644
--- a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
+++ b/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
@@ -5,8 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Context.FileSystem;
-using Snap.Hutao.Core;
-using Snap.Hutao.Core.Setting;
+using System.Diagnostics;
+using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -47,44 +47,20 @@ internal static class IocConfiguration
string dbFile = myDocument.Locate("Userdata.db");
string sqlConnectionString = $"Data Source={dbFile}";
- if (ShouldMigrate(myDocument, dbFile))
+ // temporarily create a context
+ using (AppDbContext context = AppDbContext.CreateFrom(sqlConnectionString))
{
- // temporarily create a context
- using (AppDbContext context = AppDbContext.CreateFrom(sqlConnectionString))
+ Debug.WriteLine("Migrate started");
+ if (context.Database.GetPendingMigrations().Any())
{
context.Database.Migrate();
}
+
+ Debug.WriteLine("Migrate completed");
}
- LocalSetting.Set(SettingKeys.LastAppVersion, CoreEnvironment.Version.ToString());
-
+ // LocalSetting.Set(SettingKeys.LastAppVersion, CoreEnvironment.Version.ToString());
return services
.AddDbContextPool(builder => builder.UseSqlite(sqlConnectionString));
}
-
- private static bool ShouldMigrate(MyDocumentContext myDocument, string dbFile)
- {
- bool shouldMigrate = false;
-
- // 数据库文件存在
- if (myDocument.FileExists(dbFile))
- {
- string? versionString = LocalSetting.Get(SettingKeys.LastAppVersion);
-
- // 版本更新后
- if (Version.TryParse(versionString, out Version? lastVersion))
- {
- if (lastVersion < CoreEnvironment.Version)
- {
- shouldMigrate = true;
- }
- }
- }
- else
- {
- shouldMigrate = true;
- }
-
- return shouldMigrate;
- }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
index 0b0c95a8..879a8114 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
@@ -8,9 +8,6 @@
mc:Ignorable="d">
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index 314892c3..3dca749a 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
+using Snap.Hutao.Context.Database;
namespace Snap.Hutao;
@@ -19,5 +20,13 @@ public sealed partial class MainWindow : Window
InitializeComponent();
ExtendsContentIntoTitleBar = true;
SetTitleBar(TitleBarView.DragableArea);
+
+ Closed += MainWindowClosed;
}
-}
+
+ private void MainWindowClosed(object sender, WindowEventArgs args)
+ {
+ // save datebase
+ Ioc.Default.GetRequiredService().SaveChanges();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20220618110357_SettingAndUser.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20220618110357_SettingAndUser.Designer.cs
new file mode 100644
index 00000000..48ed4009
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20220618110357_SettingAndUser.Designer.cs
@@ -0,0 +1,54 @@
+//
+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("20220618110357_SettingAndUser")]
+ partial class SettingAndUser
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("settings");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Cookie")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20220618110357_SettingAndUser.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20220618110357_SettingAndUser.cs
new file mode 100644
index 00000000..113a7e88
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20220618110357_SettingAndUser.cs
@@ -0,0 +1,46 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Snap.Hutao.Migrations
+{
+ public partial class SettingAndUser : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "settings",
+ columns: table => new
+ {
+ Key = table.Column(type: "TEXT", nullable: false),
+ Value = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_settings", x => x.Key);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "users",
+ columns: table => new
+ {
+ InnerId = table.Column(type: "TEXT", nullable: false),
+ IsSelected = table.Column(type: "INTEGER", nullable: false),
+ Cookie = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_users", x => x.InnerId);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "settings");
+
+ migrationBuilder.DropTable(
+ name: "users");
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
new file mode 100644
index 00000000..bd71958e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
@@ -0,0 +1,52 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Snap.Hutao.Context.Database;
+
+#nullable disable
+
+namespace Snap.Hutao.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ partial class AppDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("settings");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Cookie")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
index 69e9d8db..b18f2cf6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
@@ -1,8 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Extension;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
namespace Snap.Hutao.Model.Entity;
@@ -10,8 +15,26 @@ namespace Snap.Hutao.Model.Entity;
/// 用户
///
[Table("users")]
-public class User
+public class User : Observable
{
+ ///
+ /// 无用户
+ ///
+ public static readonly User None = new();
+ private UserGameRole? selectedUserGameRole;
+
+ ///
+ /// 内部Id
+ ///
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid InnerId { get; set; }
+
+ ///
+ /// 是否被选中
+ ///
+ public bool IsSelected { get; set; }
+
///
/// 用户的Cookie
///
@@ -21,5 +44,92 @@ public class User
/// 用户信息
///
[NotMapped]
- public UserInfo? UserInfo { get; set; }
+ public UserInfo? UserInfo { get; private set; }
+
+ ///
+ /// 用户信息
+ ///
+ [NotMapped]
+ public List? UserGameRoles { get; private set; }
+
+ ///
+ /// 用户信息
+ ///
+ [NotMapped]
+ public UserGameRole? SelectedUserGameRole
+ {
+ get => selectedUserGameRole;
+ private set => Set(ref selectedUserGameRole, value);
+ }
+
+ ///
+ /// 判断用户是否为空用户
+ ///
+ /// 待检测的用户
+ /// 是否为空用户
+ public static bool IsNone([NotNullWhen(false)] User? user)
+ {
+ return ReferenceEquals(NoneIfNullOrNoCookie(user), None);
+ }
+
+ ///
+ /// 设置用户的选中状态
+ /// 同时更新用户选择的角色信息
+ ///
+ /// 用户
+ /// 是否选中
+ public static void SetSelectionState(User user, bool isSelected)
+ {
+ Verify.Operation(!IsNone(user), "尝试设置一个空的用户");
+
+ user!.IsSelected = isSelected;
+ if (isSelected)
+ {
+ user.SelectedUserGameRole ??= user.UserGameRoles!.FirstOrFirstOrDefault(role => role.IsChosen);
+ }
+ }
+
+ ///
+ /// 初始化此用户
+ ///
+ /// 用户客户端
+ /// 角色客户端
+ /// 取消令牌
+ /// 用户是否初始化完成,若Cookie失效会返回
+ internal async Task InitializeAsync(UserClient userClient, UserGameRoleClient userGameRoleClient, CancellationToken token = default)
+ {
+ if (IsNone(this))
+ {
+ return false;
+ }
+
+ UserInfo = await userClient
+ .GetUserFullInfoAsync(this, token)
+ .ConfigureAwait(false);
+
+ UserGameRoles = await userGameRoleClient
+ .GetUserGameRolesAsync(this, token)
+ .ConfigureAwait(false);
+
+ SelectedUserGameRole = UserGameRoles.FirstOrDefault(role => role.IsChosen) ?? UserGameRoles.FirstOrDefault();
+
+ return UserInfo != null && UserGameRoles != null;
+ }
+
+ ///
+ /// 尝试尽可能转换为
+ ///
+ /// 用户
+ /// 转换后的用户
+ private static User NoneIfNullOrNoCookie(User? user)
+ {
+ if (user is null || user.Cookie == null)
+ {
+ return None;
+ }
+ else
+ {
+ return user;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index b81d279c..f50089dd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="1.0.3.0" />
胡桃
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs
index 18acd25d..4c13c021 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
-using System.Windows.Input;
namespace Snap.Hutao.Service.Abstraction;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs
index 087593be..7e2b9586 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs
@@ -2,8 +2,8 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
-using Snap.Hutao.Web.Hoyolab.Bbs.User;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Abstraction;
@@ -15,12 +15,34 @@ public interface IUserService
///
/// 获取当前用户信息
///
- User Current { get; }
+ User? CurrentUser { get; set; }
///
- /// 获取用户信息枚举
+ /// 异步获取用户信息枚举
/// 每个用户信息都应准备完成
+ /// 此操作不能取消
///
+ /// 取消令牌
/// 准备完成的用户信息枚举
- Task> GetInitializedUsersAsync();
+ Task> GetInitializedUsersAsync();
+
+ ///
+ /// 异步添加用户
+ ///
+ /// 待添加的用户
+ /// 用户初始化是否成功
+ Task TryAddUserAsync(User user);
+
+ ///
+ /// 异步移除用户
+ ///
+ /// 待移除的用户
+ void RemoveUser(User user);
+
+ ///
+ /// 将cookie的字符串形式转换为字典
+ ///
+ /// cookie的字符串形式
+ /// 包含cookie信息的字典
+ IDictionary ParseCookie(string cookie);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
index fc6be8ba..4f76b449 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
@@ -7,7 +7,6 @@ using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-using System.Windows.Input;
namespace Snap.Hutao.Service;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/UserService.cs
index db2caa84..412179e7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/UserService.cs
@@ -1,20 +1,116 @@
-using Snap.Hutao.Model.Entity;
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Context.Database;
+using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
-using System;
+using Snap.Hutao.Web.Hoyolab.Bbs.User;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Snap.Hutao.Service;
///
/// 用户服务
///
-[Injection(InjectAs.Transient)]
+[Injection(InjectAs.Transient, typeof(IUserService))]
internal class UserService : IUserService
{
+ private readonly AppDbContext appDbContext;
+ private readonly UserClient userClient;
+ private readonly UserGameRoleClient userGameRoleClient;
+ private User? currentUser;
+ private ObservableCollection? cachedUser = null;
- public User Current { get => throw new NotImplementedException(); }
-}
+ ///
+ /// 构造一个新的用户服务
+ ///
+ /// 应用程序数据库上下文
+ /// 用户客户端
+ /// 角色客户端
+ public UserService(AppDbContext appDbContext, UserClient userClient, UserGameRoleClient userGameRoleClient)
+ {
+ this.appDbContext = appDbContext;
+ this.userClient = userClient;
+ this.userGameRoleClient = userGameRoleClient;
+ }
+
+ ///
+ public User? CurrentUser
+ {
+ get => currentUser;
+ set
+ {
+ if (!User.IsNone(currentUser))
+ {
+ User.SetSelectionState(currentUser, false);
+ appDbContext.Users.Update(currentUser);
+ }
+
+ if (!User.IsNone(value))
+ {
+ currentUser = value;
+ User.SetSelectionState(currentUser, true);
+ appDbContext.Users.Update(currentUser);
+ }
+ }
+ }
+
+ ///
+ public async Task TryAddUserAsync(User user)
+ {
+ if (await user.InitializeAsync(userClient, userGameRoleClient))
+ {
+ appDbContext.Users.Add(user);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ public void RemoveUser(User user)
+ {
+ appDbContext.Users.Remove(user);
+ }
+
+ ///
+ public async Task> GetInitializedUsersAsync()
+ {
+ if (cachedUser == null)
+ {
+ appDbContext.Users.Load();
+ cachedUser = appDbContext.Users.Local.ToObservableCollection();
+
+ foreach (User user in cachedUser)
+ {
+ await user.InitializeAsync(userClient, userGameRoleClient);
+ }
+
+ CurrentUser = await appDbContext.Users.SingleOrDefaultAsync(user => user.IsSelected);
+ }
+
+ return cachedUser;
+ }
+
+ ///
+ public IDictionary ParseCookie(string cookie)
+ {
+ Dictionary cookieDictionary = new();
+
+ string[] values = cookie.TrimEnd(';').Split(';');
+ foreach (string[] parts in values.Select(c => c.Split(new[] { '=' }, 2)))
+ {
+ string cookieName = parts[0].Trim();
+ string cookieValue = parts.Length == 1 ? string.Empty : parts[1].Trim();
+
+ cookieDictionary[cookieName] = cookieValue;
+ }
+
+ return cookieDictionary;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 5747a912..1ac200a0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -26,8 +26,10 @@
Snap.Hutao.Program
DISABLE_XAML_GENERATED_MAIN
+
+
@@ -35,6 +37,7 @@
+
@@ -57,6 +60,7 @@
+
@@ -82,9 +86,6 @@
-
-
-
MSBuild:Compile
@@ -125,4 +126,13 @@
MSBuild:Compile
+
+
+
+
+
+
+ MSBuild:Compile
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml
new file mode 100644
index 00000000..cc00d98b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs
new file mode 100644
index 00000000..90fb6f83
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs
@@ -0,0 +1,47 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml.Controls;
+using Snap.Hutao.Core.Threading;
+
+namespace Snap.Hutao.View.Dialog;
+
+///
+/// 添加用户对话框
+///
+public sealed partial class UserDialog : ContentDialog
+{
+ ///
+ /// 构造一个新的添加用户对话框
+ ///
+ public UserDialog()
+ {
+ InitializeComponent();
+ XamlRoot = App.Window!.Content.XamlRoot;
+ }
+
+ ///
+ /// 获取输入的Cookie
+ ///
+ /// 输入的结果
+ public async Task> GetInputCookieAsync()
+ {
+ ContentDialogResult result = await ShowAsync();
+ string cookie = InputText.Text;
+
+ return new(result != ContentDialogResult.Secondary, cookie);
+ }
+
+ private void InputTextChanged(object sender, TextChangedEventArgs e)
+ {
+ string text = InputText.Text;
+
+ bool inputEmpty = string.IsNullOrEmpty(text);
+
+ (PrimaryButtonText, IsPrimaryButtonEnabled) = inputEmpty switch
+ {
+ true => ("请输入Cookie", false),
+ false => ("确认", true),
+ };
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
index 198f85be..21552f70 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
@@ -20,20 +20,20 @@
IsPaneOpen="True"
IsBackEnabled="{Binding ElementName=ContentFrame,Path=CanGoBack}">
-
-
-
-
-
+
-
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
index 91706a52..0eaa470f 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
@@ -31,150 +31,149 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ CompositeMode="SourceOver"
+ VerticalAlignment="Top"
+ cwu:VisualExtensions.NormalizedCenterPoint="0.5">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Height="24"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Bottom"
+ Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}">
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs
index 9fcf1c44..c5a8d6fb 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs
@@ -16,7 +16,7 @@ public sealed partial class TitleView : UserControl
///
public TitleView()
{
- this.InitializeComponent();
+ InitializeComponent();
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index 09aa047c..86eebf06 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -5,7 +5,14 @@
xmlns:local="using:Snap.Hutao.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:mxi="using:Microsoft.Xaml.Interactivity"
+ xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d">
+
+
+
+
+
@@ -14,18 +21,23 @@
+ Height="40"/>
+