From 344e5cd31b894cbf8be1d83028c4e303f3640f76 Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Thu, 7 Jul 2022 15:48:52 +0800
Subject: [PATCH] add system backdrop mica
---
src/Snap.Hutao/SettingsUI/SettingsUI.csproj | 2 +-
.../Snap.Hutao.SourceGeneration.csproj | 1 +
src/Snap.Hutao/Snap.Hutao.sln | 12 +-
.../Backdroping/BackbdropFallBackBehavior.cs | 20 ++
.../Control/Backdroping/SystemBackdrop.cs | 184 ++++++++++++++++++
src/Snap.Hutao/Snap.Hutao/Core/Property.cs | 2 +-
.../Snap.Hutao/Core/Setting/SettingKeys.cs | 20 ++
src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs | 30 +++
src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs | 129 ++++++++++++
.../Core/Win32/ShowWindowCommand.cs | 89 +++++++++
.../Snap.Hutao/Core/Win32/User32.cs | 65 +++++++
.../Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs | 60 ++++++
.../Snap.Hutao/Extension/EnumExtensions.cs | 27 +++
src/Snap.Hutao/Snap.Hutao/MainWindow.xaml | 2 +-
src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs | 62 +++++-
.../Model/Annotation/DescriptionAttribute.cs | 25 +++
.../Model/Intrinsic/FightProperty.cs | 18 ++
.../Model/Metadata/Avatar/ProudableSkill.cs | 1 -
.../Model/Metadata/Converter/IconConverter.cs | 26 +++
.../Metadata/Converter/SideIconConverter.cs | 26 +++
.../Snap.Hutao/Package.appxmanifest | 2 +-
.../Snap.Hutao/Service/User/IUserService.cs | 3 +-
.../Snap.Hutao/Service/User/UserAddResult.cs | 7 +-
.../Snap.Hutao/Service/User/UserService.cs | 63 ++++--
src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 6 +
.../Snap.Hutao/View/Dialog/UserDialog.xaml.cs | 7 +-
src/Snap.Hutao/Snap.Hutao/View/MainView.xaml | 6 +
.../Snap.Hutao/View/MainView.xaml.cs | 2 +-
.../Snap.Hutao/View/Page/WikiAvatarPage.xaml | 33 ++++
.../View/Page/WikiAvatarPage.xaml.cs | 35 ++++
src/Snap.Hutao/Snap.Hutao/View/UserView.xaml | 4 +-
.../Snap.Hutao/ViewModel/UserViewModel.cs | 30 +--
.../ViewModel/WikiAvatarViewModel.cs | 57 ++++++
.../Hk4e/Common/Announcement/Announcement.cs | 4 +-
.../Snap.Hutao/Web/Hoyolab/PlayerUid.cs | 2 +-
35 files changed, 1004 insertions(+), 58 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Backdroping/BackbdropFallBackBehavior.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Extension/EnumExtensions.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Annotation/DescriptionAttribute.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/IconConverter.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SideIconConverter.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
diff --git a/src/Snap.Hutao/SettingsUI/SettingsUI.csproj b/src/Snap.Hutao/SettingsUI/SettingsUI.csproj
index bc952b96..157821f1 100644
--- a/src/Snap.Hutao/SettingsUI/SettingsUI.csproj
+++ b/src/Snap.Hutao/SettingsUI/SettingsUI.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj
index 9dd6ef85..003c57a2 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj
@@ -5,6 +5,7 @@
false
latest
enable
+ x64
diff --git a/src/Snap.Hutao/Snap.Hutao.sln b/src/Snap.Hutao/Snap.Hutao.sln
index bb171023..c5c47ce6 100644
--- a/src/Snap.Hutao/Snap.Hutao.sln
+++ b/src/Snap.Hutao/Snap.Hutao.sln
@@ -62,24 +62,24 @@ Global
{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|x64.ActiveCfg = Release|x64
+ {DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.Build.0 = Release|x64
{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|x64.ActiveCfg = Debug|x64
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|x64
{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|x64.ActiveCfg = Release|x64
+ {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/BackbdropFallBackBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/BackbdropFallBackBehavior.cs
new file mode 100644
index 00000000..678a2b43
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/BackbdropFallBackBehavior.cs
@@ -0,0 +1,20 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Control.HostBackdrop;
+
+///
+/// 回退行为
+///
+public enum BackbdropFallBackBehavior
+{
+ ///
+ /// 回退到无
+ ///
+ None,
+
+ ///
+ /// 回退到亚克力
+ ///
+ Acrylic,
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs
new file mode 100644
index 00000000..b9286d1b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs
@@ -0,0 +1,184 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Composition;
+using Microsoft.UI.Composition.SystemBackdrops;
+using Microsoft.UI.Xaml;
+using System.Runtime.InteropServices;
+using Windows.System;
+using WinRT;
+
+namespace Snap.Hutao.Control.HostBackdrop;
+
+///
+/// 系统背景帮助类
+///
+public class SystemBackdrop
+{
+ private readonly Window window;
+ private readonly BackbdropFallBackBehavior fallBackBehavior;
+
+ private WindowsSystemDispatcherQueueHelper? dispatcherQueueHelper;
+ private ISystemBackdropControllerWithTargets? backdropController;
+ private SystemBackdropConfiguration? configurationSource;
+
+ ///
+ /// 构造一个新的系统背景帮助类
+ ///
+ /// 窗体
+ /// 回退行为
+ public SystemBackdrop(Window window, BackbdropFallBackBehavior fallBackBehavior = BackbdropFallBackBehavior.Acrylic)
+ {
+ this.window = window;
+ this.fallBackBehavior = fallBackBehavior;
+ }
+
+ private enum BackDropType
+ {
+ None,
+ Acrylic,
+ Mica,
+ }
+
+ ///
+ /// 尝试设置背景
+ ///
+ /// 是否设置成功
+ public bool TrySetBackdrop()
+ {
+ BackDropType targetBackDropType = ResolveBackdropType();
+
+ if (targetBackDropType == BackDropType.None)
+ {
+ return false;
+ }
+ else
+ {
+ dispatcherQueueHelper = new WindowsSystemDispatcherQueueHelper();
+ dispatcherQueueHelper.EnsureWindowsSystemDispatcherQueueController();
+
+ // Hooking up the policy object
+ configurationSource = new SystemBackdropConfiguration();
+ window.Activated += WindowActivated;
+ window.Closed += WindowClosed;
+ ((FrameworkElement)window.Content).ActualThemeChanged += WindowThemeChanged;
+
+ // Initial configuration state.
+ configurationSource.IsInputActive = true;
+ SetConfigurationSourceTheme();
+
+ backdropController = targetBackDropType switch
+ {
+ BackDropType.Mica => new MicaController(),
+ BackDropType.Acrylic => new DesktopAcrylicController(),
+ _ => throw Must.NeverHappen(),
+ };
+
+ ICompositionSupportsSystemBackdrop target = window.As();
+ backdropController.AddSystemBackdropTarget(target);
+ backdropController.SetSystemBackdropConfiguration(configurationSource);
+
+ return true;
+ }
+ }
+
+ private BackDropType ResolveBackdropType()
+ {
+ BackDropType targetBackDropType = BackDropType.None;
+ if (MicaController.IsSupported())
+ {
+ targetBackDropType = BackDropType.Mica;
+ }
+ else
+ {
+ if (fallBackBehavior == BackbdropFallBackBehavior.Acrylic)
+ {
+ if (DesktopAcrylicController.IsSupported())
+ {
+ targetBackDropType = BackDropType.Acrylic;
+ }
+ }
+ }
+
+ return targetBackDropType;
+ }
+
+ private void WindowActivated(object sender, WindowActivatedEventArgs args)
+ {
+ Must.NotNull(configurationSource!);
+ configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
+ }
+
+ private void WindowClosed(object sender, WindowEventArgs args)
+ {
+ // Make sure any Mica/Acrylic controller is disposed so it doesn't try to
+ // use this closed window.
+ if (backdropController != null)
+ {
+ backdropController.Dispose();
+ backdropController = null;
+ }
+
+ window.Activated -= WindowActivated;
+ configurationSource = null;
+ }
+
+ private void WindowThemeChanged(FrameworkElement sender, object args)
+ {
+ if (configurationSource != null)
+ {
+ SetConfigurationSourceTheme();
+ }
+ }
+
+ private void SetConfigurationSourceTheme()
+ {
+ Must.NotNull(configurationSource!).Theme = ((FrameworkElement)window.Content).ActualTheme switch
+ {
+ ElementTheme.Dark => SystemBackdropTheme.Dark,
+ ElementTheme.Light => SystemBackdropTheme.Light,
+ ElementTheme.Default => SystemBackdropTheme.Default,
+ _ => throw Must.NeverHappen(),
+ };
+ }
+
+ private class WindowsSystemDispatcherQueueHelper
+ {
+ private object dispatcherQueueController = null!;
+
+ ///
+ /// 确保系统调度队列控制器存在
+ ///
+ public void EnsureWindowsSystemDispatcherQueueController()
+ {
+ if (DispatcherQueue.GetForCurrentThread() != null)
+ {
+ // one already exists, so we'll just use it.
+ return;
+ }
+
+ if (dispatcherQueueController == null)
+ {
+ DispatcherQueueOptions options;
+ options.DwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
+ options.ThreadType = 2; // DQTYPE_THREAD_CURRENT
+ options.ApartmentType = 2; // DQTAT_COM_STA
+
+ _ = CreateDispatcherQueueController(options, ref dispatcherQueueController!);
+ }
+ }
+
+ [DllImport("CoreMessaging.dll")]
+ private static extern int CreateDispatcherQueueController(
+ [In] DispatcherQueueOptions options,
+ [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object dispatcherQueueController);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct DispatcherQueueOptions
+ {
+ internal int DwSize;
+ internal int ThreadType;
+ internal int ApartmentType;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Property.cs b/src/Snap.Hutao/Snap.Hutao/Core/Property.cs
index 218acaff..52e99899 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Property.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Property.cs
@@ -81,4 +81,4 @@ internal static class Property
{
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(callback));
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
index c6138b23..dd757a36 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
@@ -12,4 +12,24 @@ internal static class SettingKeys
/// 上次打开时App的版本
///
public const string LastAppVersion = "LastAppVersion";
+
+ ///
+ /// 窗体左侧
+ ///
+ public const string WindowLeft = "WindowLeft";
+
+ ///
+ /// 窗体顶部
+ ///
+ public const string WindowTop = "WindowTop";
+
+ ///
+ /// 窗体右侧
+ ///
+ public const string WindowRight = "WindowRight";
+
+ ///
+ /// 窗体底部
+ ///
+ public const string WindowBottom = "WindowBottom";
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs
new file mode 100644
index 00000000..adff3792
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs
@@ -0,0 +1,30 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Runtime.InteropServices;
+
+namespace Snap.Hutao.Core.Win32;
+
+[SuppressMessage("", "SA1600")]
+[StructLayout(LayoutKind.Sequential)]
+public struct POINT
+{
+ public int X;
+ public int Y;
+
+ public POINT(int x, int y)
+ {
+ this.X = x;
+ this.Y = y;
+ }
+
+ public static implicit operator System.Drawing.Point(POINT p)
+ {
+ return new System.Drawing.Point(p.X, p.Y);
+ }
+
+ public static implicit operator POINT(System.Drawing.Point p)
+ {
+ return new POINT(p.X, p.Y);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs
new file mode 100644
index 00000000..781b1de1
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs
@@ -0,0 +1,129 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace Snap.Hutao.Core.Win32;
+
+[SuppressMessage("", "SA1132")]
+[SuppressMessage("", "SA1600")]
+[StructLayout(LayoutKind.Sequential)]
+public struct RECT
+{
+ public int Left, Top, Right, Bottom;
+
+ public RECT(int left, int top, int right, int bottom)
+ {
+ Left = left;
+ Top = top;
+ Right = right;
+ Bottom = bottom;
+ }
+
+ public RECT(System.Drawing.Rectangle r)
+ : this(r.Left, r.Top, r.Right, r.Bottom)
+ {
+ }
+
+ public int X
+ {
+ get => Left;
+ set
+ {
+ Right -= Left - value;
+ Left = value;
+ }
+ }
+
+ public int Y
+ {
+ get => Top;
+ set
+ {
+ Bottom -= Top - value;
+ Top = value;
+ }
+ }
+
+ public int Height
+ {
+ get => Bottom - Top;
+ set => Bottom = value + Top;
+ }
+
+ public int Width
+ {
+ get => Right - Left;
+ set => Right = value + Left;
+ }
+
+ public System.Drawing.Point Location
+ {
+ get => new(Left, Top);
+ set
+ {
+ X = value.X;
+ Y = value.Y;
+ }
+ }
+
+ public System.Drawing.Size Size
+ {
+ get => new(Width, Height);
+ set
+ {
+ Width = value.Width;
+ Height = value.Height;
+ }
+ }
+
+ public static implicit operator System.Drawing.Rectangle(RECT r)
+ {
+ return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
+ }
+
+ public static implicit operator RECT(System.Drawing.Rectangle r)
+ {
+ return new RECT(r);
+ }
+
+ public static bool operator ==(RECT r1, RECT r2)
+ {
+ return r1.Equals(r2);
+ }
+
+ public static bool operator !=(RECT r1, RECT r2)
+ {
+ return !r1.Equals(r2);
+ }
+
+ public bool Equals(RECT r)
+ {
+ return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is RECT rect)
+ {
+ return Equals(rect);
+ }
+ else if (obj is System.Drawing.Rectangle rectangle)
+ {
+ return Equals(new RECT(rectangle));
+ }
+
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return ((System.Drawing.Rectangle)this).GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs
new file mode 100644
index 00000000..a0821434
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs
@@ -0,0 +1,89 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Win32;
+
+[SuppressMessage("", "SA1600")]
+public enum ShowWindowCommand
+{
+ ///
+ /// Hides the window and activates another window.
+ ///
+ Hide = 0,
+
+ ///
+ /// Activates and displays a window. If the window is minimized or
+ /// maximized, the system restores it to its original size and position.
+ /// An application should specify this flag when displaying the window
+ /// for the first time.
+ ///
+ Normal = 1,
+
+ ///
+ /// Activates the window and displays it as a minimized window.
+ ///
+ ShowMinimized = 2,
+
+ ///
+ /// Maximizes the specified window.
+ ///
+ Maximize = 3, // is this the right value?
+
+ ///
+ /// Activates the window and displays it as a maximized window.
+ ///
+ ShowMaximized = 3,
+
+ ///
+ /// Displays a window in its most recent size and position. This value
+ /// is similar to , except
+ /// the window is not activated.
+ ///
+ ShowNoActivate = 4,
+
+ ///
+ /// Activates the window and displays it in its current size and position.
+ ///
+ Show = 5,
+
+ ///
+ /// Minimizes the specified window and activates the next top-level
+ /// window in the Z order.
+ ///
+ Minimize = 6,
+
+ ///
+ /// Displays the window as a minimized window. This value is similar to
+ /// , except the
+ /// window is not activated.
+ ///
+ ShowMinNoActive = 7,
+
+ ///
+ /// Displays the window in its current size and position. This value is
+ /// similar to , except the
+ /// window is not activated.
+ ///
+ ShowNA = 8,
+
+ ///
+ /// Activates and displays the window. If the window is minimized or
+ /// maximized, the system restores it to its original size and position.
+ /// An application should specify this flag when restoring a minimized window.
+ ///
+ Restore = 9,
+
+ ///
+ /// Sets the show state based on the SW_* value specified in the
+ /// STARTUPINFO structure passed to the CreateProcess function by the
+ /// program that started the application.
+ ///
+ ShowDefault = 10,
+
+ ///
+ /// Windows 2000/XP: Minimizes a window, even if the thread
+ /// that owns the window is not responding. This flag should only be
+ /// used when minimizing windows from a different thread.
+ ///
+ ForceMinimize = 11,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs
new file mode 100644
index 00000000..69ff9d9f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs
@@ -0,0 +1,65 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Runtime.InteropServices;
+
+namespace Snap.Hutao.Core.Win32;
+
+///
+/// 包含 user32.dll 平台调用的代码
+///
+internal static class User32
+{
+ [SuppressMessage("", "SA1600")]
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
+
+ ///
+ /// Sets the show state and the restored, minimized, and maximized positions of the specified window.
+ ///
+ ///
+ /// A handle to the window.
+ ///
+ ///
+ /// A pointer to a WINDOWPLACEMENT structure that specifies the new show state and window positions.
+ ///
+ /// Before calling SetWindowPlacement, set the length member of the WINDOWPLACEMENT structure to sizeof(WINDOWPLACEMENT). SetWindowPlacement fails if the length member is not set correctly.
+ ///
+ ///
+ ///
+ /// If the function succeeds, the return value is nonzero.
+ ///
+ /// If the function fails, the return value is zero. To get extended error information, call GetLastError.
+ ///
+ ///
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
+
+ ///
+ /// Changes the text of the specified window's title bar (if it has one). If the specified window is a control, the
+ /// text of the control is changed. However, SetWindowText cannot change the text of a control in another application.
+ ///
+ /// Go to for more
+ /// information
+ ///
+ ///
+ /// C++ ( hWnd [in]. Type: HWND )
A handle to the window or control whose text is to be changed.
+ /// C++ ( lpString [in, optional]. Type: LPCTSTR )
The new title or control text.
+ ///
+ /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.
+ /// To get extended error information, call GetLastError.
+ ///
+ ///
+ /// If the target window is owned by the current process, causes a WM_SETTEXT message to
+ /// be sent to the specified window or control. If the control is a list box control created with the WS_CAPTION style,
+ /// however, sets the text for the control, not for the list box entries.
To set the
+ /// text of a control in another process, send the WM_SETTEXT message directly instead of calling
+ /// . The function does not expand tab characters (ASCII code
+ /// 0x09). Tab characters are displayed as vertical bar(|) characters.
For an example go to
+ /// Sending a Message.
+ ///
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ public static extern bool SetWindowText(IntPtr hwnd, string lpString);
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs
new file mode 100644
index 00000000..033a0950
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs
@@ -0,0 +1,60 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Runtime.InteropServices;
+
+namespace Snap.Hutao.Core.Win32;
+
+///
+/// Contains information about the placement of a window on the screen.
+///
+[Serializable]
+[StructLayout(LayoutKind.Sequential)]
+internal struct WINDOWPLACEMENT
+{
+ ///
+ /// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT).
+ ///
+ /// GetWindowPlacement and SetWindowPlacement fail if this member is not set correctly.
+ ///
+ ///
+ public int Length;
+
+ ///
+ /// Specifies flags that control the position of the minimized window and the method by which the window is restored.
+ ///
+ public int Flags;
+
+ ///
+ /// The current show state of the window.
+ ///
+ public ShowWindowCommand ShowCmd;
+
+ ///
+ /// The coordinates of the window's upper-left corner when the window is minimized.
+ ///
+ public POINT MinPosition;
+
+ ///
+ /// The coordinates of the window's upper-left corner when the window is maximized.
+ ///
+ public POINT MaxPosition;
+
+ ///
+ /// The window's coordinates when the window is in the restored position.
+ ///
+ public RECT NormalPosition;
+
+ ///
+ /// Gets the default (empty) value.
+ ///
+ public static WINDOWPLACEMENT Default
+ {
+ get
+ {
+ WINDOWPLACEMENT result = default(WINDOWPLACEMENT);
+ result.Length = Marshal.SizeOf(result);
+ return result;
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumExtensions.cs
new file mode 100644
index 00000000..2005005d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumExtensions.cs
@@ -0,0 +1,27 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Reflection;
+
+namespace Snap.Hutao.Extension;
+
+///
+/// 枚举拓展
+///
+public static class EnumExtensions
+{
+ ///
+ /// 获取枚举的描述
+ ///
+ /// 枚举的类型
+ /// 枚举值
+ /// 描述
+ public static string GetDescription(this TEnum @enum)
+ where TEnum : struct, Enum
+ {
+ string enumName = Must.NotNull(Enum.GetName(@enum)!);
+ FieldInfo? field = @enum.GetType().GetField(enumName);
+ DescriptionAttribute? attr = field?.GetCustomAttribute();
+ return attr?.Description ?? enumName;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
index 00f8412f..e98cc4a6 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
@@ -8,7 +8,7 @@
mc:Ignorable="d"
Closed="MainWindowClosed">
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index ab7740ef..02437512 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -3,6 +3,12 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Context.Database;
+using Snap.Hutao.Control.HostBackdrop;
+using Snap.Hutao.Core.Setting;
+using Snap.Hutao.Core.Win32;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using WinRT.Interop;
namespace Snap.Hutao;
@@ -13,22 +19,76 @@ namespace Snap.Hutao;
public sealed partial class MainWindow : Window
{
private readonly AppDbContext appDbContext;
+ private readonly ILogger logger;
+ private readonly IntPtr handle;
///
/// 构造一个新的主窗体
///
/// 数据库上下文
- public MainWindow(AppDbContext appDbContext)
+ /// 日志器
+ public MainWindow(AppDbContext appDbContext, ILogger logger)
{
this.appDbContext = appDbContext;
+ this.logger = logger;
InitializeComponent();
+
ExtendsContentIntoTitleBar = true;
SetTitleBar(TitleBarView.DragableArea);
+
+ handle = WindowNative.GetWindowHandle(this);
+ InitializeWindow();
+ }
+
+ private static RECT RetriveWindowRect()
+ {
+ int left = LocalSetting.GetValueType(SettingKeys.WindowLeft);
+ int top = LocalSetting.GetValueType(SettingKeys.WindowTop);
+ int right = LocalSetting.GetValueType(SettingKeys.WindowRight);
+ int bottom = LocalSetting.GetValueType(SettingKeys.WindowBottom);
+
+ return new RECT(left, top, right, bottom);
+ }
+
+ private void InitializeWindow()
+ {
+ RECT rect = RetriveWindowRect();
+ if (rect.Size.IsEmpty)
+ {
+ return;
+ }
+
+ WINDOWPLACEMENT windowPlacement = new()
+ {
+ Length = Marshal.SizeOf(),
+ MaxPosition = new Point(-1, -1),
+ NormalPosition = rect,
+ ShowCmd = ShowWindowCommand.Normal,
+ };
+
+ User32.SetWindowPlacement(handle, ref windowPlacement);
+ User32.SetWindowText(handle, "胡桃");
+
+ bool micaApplied = new SystemBackdrop(this).TrySetBackdrop();
+ logger.LogInformation("{name} 设置{result}", nameof(SystemBackdrop), micaApplied ? "成功" : "失败");
+ }
+
+ private void SaveWindowRect()
+ {
+ WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Default;
+ User32.GetWindowPlacement(handle, ref windowPlacement);
+
+ LocalSetting.SetValueType(SettingKeys.WindowLeft, windowPlacement.NormalPosition.Left);
+ LocalSetting.SetValueType(SettingKeys.WindowTop, windowPlacement.NormalPosition.Top);
+ LocalSetting.SetValueType(SettingKeys.WindowRight, windowPlacement.NormalPosition.Right);
+ LocalSetting.SetValueType(SettingKeys.WindowBottom, windowPlacement.NormalPosition.Bottom);
}
private void MainWindowClosed(object sender, WindowEventArgs args)
{
+ SaveWindowRect();
+
// save datebase
int changes = appDbContext.SaveChanges();
Verify.Operation(changes == 0, "存在可避免的未经处理的数据库更改");
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Annotation/DescriptionAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Model/Annotation/DescriptionAttribute.cs
new file mode 100644
index 00000000..9b73569c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Annotation/DescriptionAttribute.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Model.Annotation;
+
+///
+/// 枚举的文本描述特性
+///
+[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
+internal class DescriptionAttribute : Attribute
+{
+ ///
+ /// 构造一个新的枚举的文本描述特性
+ ///
+ /// 描述
+ public DescriptionAttribute(string description)
+ {
+ Description = description;
+ }
+
+ ///
+ /// 获取文本描述
+ ///
+ public string Description { get; init; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/FightProperty.cs b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/FightProperty.cs
index 1a944118..21af660e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/FightProperty.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/FightProperty.cs
@@ -17,6 +17,7 @@ public enum FightProperty
///
/// 基础生命值
///
+ [Description("基础生命值")]
FIGHT_PROP_BASE_HP = 1,
///
@@ -27,11 +28,13 @@ public enum FightProperty
///
/// 生命值加成百分比
///
+ [Description("生命值")]
FIGHT_PROP_HP_PERCENT = 3,
///
/// 基础攻击力
///
+ [Description("基础攻击力")]
FIGHT_PROP_BASE_ATTACK = 4,
///
@@ -42,11 +45,13 @@ public enum FightProperty
///
/// 攻击力百分比
///
+ [Description("攻击力")]
FIGHT_PROP_ATTACK_PERCENT = 6,
///
/// 基础防御力
///
+ [Description("基础防御力")]
FIGHT_PROP_BASE_DEFENSE = 7,
///
@@ -57,6 +62,7 @@ public enum FightProperty
///
/// 防御力百分比
///
+ [Description("防御力")]
FIGHT_PROP_DEFENSE_PERCENT = 9,
///
@@ -82,6 +88,7 @@ public enum FightProperty
///
/// 暴击率
///
+ [Description("暴击率")]
FIGHT_PROP_CRITICAL = 20,
///
@@ -92,11 +99,13 @@ public enum FightProperty
///
/// 暴击伤害
///
+ [Description("暴击伤害")]
FIGHT_PROP_CRITICAL_HURT = 22,
///
/// 元素充能效率
///
+ [Description("元素充能效率")]
FIGHT_PROP_CHARGE_EFFICIENCY = 23,
///
@@ -112,6 +121,7 @@ public enum FightProperty
///
/// 治疗提升
///
+ [Description("治疗加成")]
FIGHT_PROP_HEAL_ADD = 26,
///
@@ -122,6 +132,7 @@ public enum FightProperty
///
/// 元素精通
///
+ [Description("元素精通")]
FIGHT_PROP_ELEMENT_MASTERY = 28,
///
@@ -132,6 +143,7 @@ public enum FightProperty
///
/// 物理伤害加成
///
+ [Description("物理伤害加成")]
FIGHT_PROP_PHYSICAL_ADD_HURT = 30,
///
@@ -147,16 +159,19 @@ public enum FightProperty
///
/// 火元素伤害加成
///
+ [Description("火元素伤害加成")]
FIGHT_PROP_FIRE_ADD_HURT = 40,
///
/// 雷元素伤害加成
///
+ [Description("雷元素伤害加成")]
FIGHT_PROP_ELEC_ADD_HURT = 41,
///
/// 水元素伤害加成
///
+ [Description("水元素伤害加成")]
FIGHT_PROP_WATER_ADD_HURT = 42,
///
@@ -167,16 +182,19 @@ public enum FightProperty
///
/// 风元素伤害加成
///
+ [Description("风元素伤害加成")]
FIGHT_PROP_WIND_ADD_HURT = 44,
///
/// 岩元素伤害加成
///
+ [Description("岩元素伤害加成")]
FIGHT_PROP_ROCK_ADD_HURT = 45,
///
/// 冰元素伤害加成
///
+ [Description("冰元素伤害加成")]
FIGHT_PROP_ICE_ADD_HURT = 46,
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs
index b24fdcea..37c4366f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs
@@ -8,7 +8,6 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
///
public class ProudableSkill : SkillBase
{
-
///
/// 组Id
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/IconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/IconConverter.cs
new file mode 100644
index 00000000..c1b4018b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/IconConverter.cs
@@ -0,0 +1,26 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml.Data;
+
+namespace Snap.Hutao.Model.Metadata.Converter;
+
+///
+/// 角色头像转换器
+///
+internal class IconConverter : IValueConverter
+{
+ private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_icon/{0}.png";
+
+ ///
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ return new Uri(string.Format(BaseUrl, value));
+ }
+
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw Must.NeverHappen();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SideIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SideIconConverter.cs
new file mode 100644
index 00000000..ef595504
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SideIconConverter.cs
@@ -0,0 +1,26 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml.Data;
+
+namespace Snap.Hutao.Model.Metadata.Converter;
+
+///
+/// 角色侧面头像转换器
+///
+internal class SideIconConverter : IValueConverter
+{
+ private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_side_icon/{0}.png";
+
+ ///
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ return new Uri(string.Format(BaseUrl, value));
+ }
+
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw Must.NeverHappen();
+ }
+}
\ 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 04580171..faf68f1e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="1.0.9.0" />
胡桃
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs
index c3069d98..8a31ef6a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs
@@ -31,8 +31,9 @@ public interface IUserService
/// 通常用户是未初始化的
///
/// 待添加的用户
+ /// 用户的米游社UID
/// 用户初始化是否成功
- Task TryAddUserAsync(User user);
+ Task TryAddUserAsync(User user, string uid);
///
/// 异步移除用户
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs
index 7722ebcd..2dd2b6f9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs
@@ -11,7 +11,12 @@ public enum UserAddResult
///
/// 添加成功
///
- Ok,
+ Added,
+
+ ///
+ /// 用户的Cookie成功更新
+ ///
+ Updated,
///
/// 已经存在该用户
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
index a33ae082..ea8a6a97 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
@@ -69,31 +69,45 @@ internal class UserService : IUserService
}
///
- public async Task TryAddUserAsync(User user)
+ public async Task TryAddUserAsync(User newUser, string uid)
{
- string? newUsersCookie = user.Cookie;
+ Must.NotNull(cachedUsers!);
- // Prevent users add same account.
- bool userAlreadyExists = await appDbContext.Users
- .AnyAsync(u => u.Cookie == newUsersCookie)
- .ConfigureAwait(false);
+ // 查找是否有相同的uid
+ User? userWithSameUid = cachedUsers
+ .SingleOrDefault(u => u.UserInfo!.Uid == uid);
- if (userAlreadyExists)
+ if (userWithSameUid != null)
{
- return UserAddResult.AlreadyExists;
+ // Prevent users from adding same cookie.
+ if (userWithSameUid.Cookie == newUser.Cookie)
+ {
+ return UserAddResult.AlreadyExists;
+ }
+ else
+ {
+ // Try update user here.
+ userWithSameUid.Cookie = newUser.Cookie;
+ appDbContext.Users.Update(userWithSameUid);
+
+ await appDbContext
+ .SaveChangesAsync()
+ .ConfigureAwait(false);
+
+ return UserAddResult.Updated;
+ }
}
- bool userInitialized = await user
- .InitializeAsync(userClient, userGameRoleClient)
- .ConfigureAwait(false);
-
- if (userInitialized)
+ // must continue on the caller thread.
+ if (await newUser.InitializeAsync(userClient, userGameRoleClient))
{
- appDbContext.Users.Add(user);
+ appDbContext.Users.Add(newUser);
+
await appDbContext
.SaveChangesAsync()
.ConfigureAwait(false);
- return UserAddResult.Ok;
+
+ return UserAddResult.Added;
}
return UserAddResult.InitializeFailed;
@@ -111,16 +125,23 @@ internal class UserService : IUserService
{
if (cachedUsers == null)
{
- appDbContext.Users.Load();
+ await appDbContext.Users
+ .LoadAsync()
+ .ConfigureAwait(false);
+
cachedUsers = appDbContext.Users.Local.ToObservableCollection();
foreach (User user in cachedUsers)
{
user.RemoveCommand = removeCommand;
- await user.InitializeAsync(userClient, userGameRoleClient);
+ await user
+ .InitializeAsync(userClient, userGameRoleClient)
+ .ConfigureAwait(false);
}
- CurrentUser = await appDbContext.Users.SingleOrDefaultAsync(user => user.IsSelected);
+ CurrentUser = await appDbContext.Users
+ .SingleOrDefaultAsync(user => user.IsSelected)
+ .ConfigureAwait(false);
}
return cachedUsers;
@@ -129,15 +150,15 @@ internal class UserService : IUserService
///
public IDictionary ParseCookie(string cookie)
{
- Dictionary cookieDictionary = new();
+ SortedDictionary cookieDictionary = new();
string[] values = cookie.TrimEnd(';').Split(';');
- foreach (string[] parts in values.Select(c => c.Split(new[] { '=' }, 2)))
+ foreach (string[] parts in values.Select(c => c.Split('=', 2)))
{
string cookieName = parts[0].Trim();
string cookieValue = parts.Length == 1 ? string.Empty : parts[1].Trim();
- cookieDictionary[cookieName] = cookieValue;
+ cookieDictionary.Add(cookieName, cookieValue);
}
return cookieDictionary;
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 98e100f4..c26ec322 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -36,6 +36,7 @@
+
@@ -132,6 +133,11 @@
+
+
+ MSBuild:Compile
+
+
MSBuild:Compile
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs
index a9a63a97..683d15c6 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml.cs
@@ -14,10 +14,11 @@ public sealed partial class UserDialog : ContentDialog
///
/// 构造一个新的添加用户对话框
///
- public UserDialog()
+ /// 呈现的父窗口
+ public UserDialog(Microsoft.UI.Xaml.Window window)
{
InitializeComponent();
- XamlRoot = App.Window!.Content.XamlRoot;
+ XamlRoot = window.Content.XamlRoot;
}
///
@@ -29,7 +30,7 @@ public sealed partial class UserDialog : ContentDialog
ContentDialogResult result = await ShowAsync();
string cookie = InputText.Text;
- return new(result != ContentDialogResult.Secondary, cookie);
+ return new(result == ContentDialogResult.Primary, cookie);
}
private void InputTextChanged(object sender, TextChangedEventArgs e)
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
index b7d33f69..063c6e3c 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
@@ -34,6 +34,12 @@
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
index a78cba36..f2fbf6d1 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
@@ -28,6 +28,6 @@ public sealed partial class MainView : UserControl
navigationService = Ioc.Default.GetRequiredService();
navigationService.Initialize(NavView, ContentFrame);
- navigationService.Navigate(INavigationAwaiter.Default, false);
+ navigationService.Navigate(INavigationAwaiter.Default, true);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
new file mode 100644
index 00000000..d1c0478d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs
new file mode 100644
index 00000000..5b9dc449
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+using Snap.Hutao.ViewModel;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+
+namespace Snap.Hutao.View.Page;
+
+///
+/// 角色资料页
+///
+public sealed partial class WikiAvatarPage : Microsoft.UI.Xaml.Controls.Page
+{
+ ///
+ /// 构造一个新的角色资料页
+ ///
+ public WikiAvatarPage()
+ {
+ DataContext = Ioc.Default.GetRequiredService();
+ InitializeComponent();
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index df2bc99f..1dbddc2c 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -35,7 +35,6 @@
Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}"
TextTrimming="CharacterEllipsis"/>
+ Command="{Binding AddUserCommand}"/>
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
index 1c2f3afe..e0d85120 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
-using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Entity;
@@ -20,6 +19,8 @@ namespace Snap.Hutao.ViewModel;
[Injection(InjectAs.Transient)]
internal class UserViewModel : ObservableObject
{
+ private const string AccountIdKey = "account_id";
+
private readonly IUserService userService;
private readonly IInfoBarService infoBarService;
private readonly ICommand removeUserCommandCache;
@@ -39,7 +40,7 @@ internal class UserViewModel : ObservableObject
this.infoBarService = infoBarService;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
- AddUserCommand = asyncRelayCommandFactory.Create(AddUserAsync);
+ AddUserCommand = asyncRelayCommandFactory.Create(AddUserAsync);
removeUserCommandCache = asyncRelayCommandFactory.Create(RemoveUserAsync);
}
@@ -83,10 +84,10 @@ internal class UserViewModel : ObservableObject
// O(1) to validate cookie
foreach ((string key, string value) in map)
{
- if (key == "account_id" || key == "cookie_token" || key == "ltoken" || key == "ltuid")
+ if (key == AccountIdKey || key == "cookie_token" || key == "ltoken" || key == "ltuid")
{
validFlag--;
- filteredCookie[key] = value;
+ filteredCookie.Add(key, value);
}
}
@@ -107,12 +108,10 @@ internal class UserViewModel : ObservableObject
SelectedUser = userService.CurrentUser;
}
- private async Task AddUserAsync(Flyout? flyout)
+ private async Task AddUserAsync()
{
- // hide the flyout, otherwise dialog can't open.
- flyout?.Hide();
-
- Result result = await new UserDialog().GetInputCookieAsync();
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ Result result = await new UserDialog(mainWindow).GetInputCookieAsync();
// user confirms the input
if (result.IsOk)
@@ -128,22 +127,27 @@ internal class UserViewModel : ObservableObject
RemoveCommand = removeUserCommandCache,
};
- switch (await userService.TryAddUserAsync(user))
+ switch (await userService.TryAddUserAsync(user, filteredCookie[AccountIdKey]))
{
- case UserAddResult.Ok:
+ case UserAddResult.Added:
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 添加成功");
break;
+ case UserAddResult.Updated:
+ infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 更新成功");
+ break;
case UserAddResult.AlreadyExists:
infoBarService.Information($"用户 [{user.UserInfo!.Nickname}] 已经存在");
break;
case UserAddResult.InitializeFailed:
- infoBarService.Warning("此Cookie无法获取用户信息,请重新输入");
+ infoBarService.Warning("此 Cookie 无法获取用户信息,请重新输入");
break;
+ default:
+ throw Must.NeverHappen();
}
}
else
{
- infoBarService.Warning("提供的字符串并不是有效的Cookie,请重新输入");
+ infoBarService.Warning("提供的文本不是正确的 Cookie ,请重新输入");
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
new file mode 100644
index 00000000..4e2e30af
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
@@ -0,0 +1,57 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using Snap.Hutao.Factory.Abstraction;
+using Snap.Hutao.Model.Metadata.Avatar;
+using Snap.Hutao.Service.Metadata;
+using System.Collections.Generic;
+
+namespace Snap.Hutao.ViewModel;
+
+///
+/// 角色资料视图模型
+///
+[Injection(InjectAs.Transient)]
+internal class WikiAvatarViewModel : ObservableObject
+{
+ private readonly IMetadataService metadataService;
+
+ private List? avatars;
+ private Avatar? selected;
+
+ ///
+ /// 构造一个新的角色资料视图模型
+ ///
+ /// 元数据服务
+ /// 异步命令工厂
+ public WikiAvatarViewModel(IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
+ {
+ this.metadataService = metadataService;
+ OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
+ }
+
+ ///
+ /// 角色列表
+ ///
+ public List? Avatars { get => avatars; set => SetProperty(ref avatars, value); }
+
+ ///
+ /// 选中的角色
+ ///
+ public Avatar? Selected { get => selected; set => SetProperty(ref selected, value); }
+
+ ///
+ /// 打开页面命令
+ ///
+ public ICommand OpenUICommand { get; }
+
+ private async Task OpenUIAsync()
+ {
+ if (await metadataService.InitializeAsync())
+ {
+ IEnumerable? avatars = await metadataService.GetAvatarsAsync();
+ Avatars = new List(avatars);
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs
index 4a203185..0bc5abe4 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs
@@ -32,7 +32,7 @@ public class Announcement : AnnouncementContent
{
get
{
- DateTime now = DateTime.UtcNow + TimeSpan.FromHours(8);
+ DateTimeOffset now = DateTimeOffset.UtcNow;
// 尚未开始
if (StartTime > now)
@@ -76,7 +76,7 @@ public class Announcement : AnnouncementContent
if (timePercent == 0)
{
// UTC+8
- DateTime currentTime = DateTime.UtcNow.AddHours(8);
+ DateTimeOffset currentTime = DateTimeOffset.UtcNow;
TimeSpan current = currentTime - StartTime;
TimeSpan total = EndTime - StartTime;
timePercent = current / total;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs
index ab6ec83e..dec0f898 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs
@@ -17,7 +17,7 @@ public struct PlayerUid
/// 服务器,当提供该参数时会无条件信任
public PlayerUid(string value, string? region = default)
{
- Requires.Argument(value.Length == 9, nameof(value), "uid应为9位数字");
+ Requires.Argument(value.Length == 9, nameof(value), "uid 应为9位数字");
Value = value;
if (region != null)