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"/> +