From 7861ebf9981e3d5a8bf37df8a3a2a327a7556d6e Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Sat, 18 Nov 2023 22:41:25 +0800
Subject: [PATCH] #impl 1096
---
.../UnsafeRuntimeBehaviorTest.cs | 19 ++
.../Snap.Hutao.Win32/NativeMethods.json | 8 +-
.../Snap.Hutao.Win32/NativeMethods.txt | 12 +-
src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs | 3 +-
src/Snap.Hutao/Snap.Hutao/App.xaml | 1 +
.../Control/Helper/ScrollViewerHelper.cs | 14 +
.../Control/Theme/ScrollViewer.xaml | 287 ++++++++++++++++++
.../Snap.Hutao/Core/Windowing/BackdropType.cs | 5 -
.../Picker/FileSystemPickerInteraction.cs | 211 +++++++++++++
.../FileSystemPickerInteractionExtension.cs | 19 ++
.../Picker/IFileSystemPickerInteraction.cs | 38 +--
.../Factory/Picker/IPickerFactory.cs | 38 ---
.../Factory/Picker/PickerFactory.cs | 88 ------
src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs | 4 +-
.../Snap.Hutao/Package.appxmanifest | 2 +-
.../Snap.Hutao/Resource/Localization/SH.resx | 18 ++
...ameRecordCharacterAvatarInfoTransformer.cs | 23 +-
.../Service/Game/Locator/ManualGameLocator.cs | 19 +-
src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 9 +-
.../Snap.Hutao/View/Page/AchievementPage.xaml | 8 +-
.../Snap.Hutao/View/Page/SettingPage.xaml | 127 ++++----
.../Achievement/AchievementImporter.cs | 10 +-
.../Achievement/AchievementViewModel.cs | 18 +-
.../ViewModel/GachaLog/GachaLogViewModel.cs | 73 +++--
.../ViewModel/Setting/SettingViewModel.cs | 13 +-
25 files changed, 748 insertions(+), 319 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Helper/ScrollViewerHelper.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Theme/ScrollViewer.xaml
create mode 100644 src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteractionExtension.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Factory/Picker/IPickerFactory.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs
diff --git a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs
index 0d516772..d876b35b 100644
--- a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs
+++ b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs
@@ -18,4 +18,23 @@ public sealed class UnsafeRuntimeBehaviorTest
Assert.AreEqual(uint.MaxValue, *(uint*)pBytes);
}
}
+}
+
+[TestClass]
+public sealed class NewModifierRuntimeBehaviorTest
+{
+ private interface IBase
+ {
+ int GetValue();
+ }
+
+ private interface IBaseImpl : IBase
+ {
+ new int GetValue();
+ }
+
+ private sealed class Impl : IBaseImpl
+ {
+ public int GetValue() => 1;
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json
index bcd1d020..b25812f1 100644
--- a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json
+++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json
@@ -1,5 +1,11 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
"allowMarshaling": true,
- "useSafeHandles": false
+ "useSafeHandles": false,
+ "comInterop": {
+ "preserveSigMethods": [
+ "IFileOpenDialog.Show",
+ "IFileSaveDialog.Show"
+ ]
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
index ee0d767e..6520488d 100644
--- a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
+++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
@@ -31,6 +31,9 @@ WriteProcessMemory
CoCreateInstance
CoWaitForMultipleObjects
+// SHELL32
+SHCreateItemFromParsingName
+
// USER32
AttachThreadInput
FindWindowExW
@@ -47,18 +50,21 @@ SetForegroundWindow
UnregisterHotKey
// COM
+FileOpenDialog
+FileSaveDialog
+IFileOpenDialog
+IFileSaveDialog
IPersistFile
IShellLinkW
ShellLink
SHELL_LINK_DATA_FLAGS
-FileOpenDialog
-IFileOpenDialog
// WinRT
IMemoryBufferByteAccess
// Const value
INFINITE
+MAX_PATH
WM_GETMINMAXINFO
WM_HOTKEY
WM_NCRBUTTONDOWN
@@ -66,6 +72,8 @@ WM_NCRBUTTONUP
WM_NULL
// Type & Enum definition
+HRESULT_FROM_WIN32
+SLGP_FLAGS
// System.Threading
LPTHREAD_START_ROUTINE
diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs b/src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs
index e5ae3614..a54fa034 100644
--- a/src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs
+++ b/src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs
@@ -8,8 +8,7 @@ internal static partial class PInvoke
{
///
internal static unsafe HRESULT CoCreateInstance(object? pUnkOuter, CLSCTX dwClsContext, out TInterface ppv)
-where TInterface : class
-
+ where TInterface : class
{
HRESULT hr = CoCreateInstance(typeof(TClass).GUID, pUnkOuter, dwClsContext, typeof(TInterface).GUID, out object o);
ppv = (TInterface)o;
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml
index 590d52a5..b15eeb24 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml
@@ -20,6 +20,7 @@
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Helper/ScrollViewerHelper.cs b/src/Snap.Hutao/Snap.Hutao/Control/Helper/ScrollViewerHelper.cs
new file mode 100644
index 00000000..11f0d0c7
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Helper/ScrollViewerHelper.cs
@@ -0,0 +1,14 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Snap.Hutao.Control.Helper;
+
+[SuppressMessage("", "SH001")]
+[DependencyProperty("LeftPanelMaxWidth", typeof(double), IsAttached = true, AttachedType = typeof(ScrollViewer))]
+[DependencyProperty("RightPanel", typeof(UIElement), IsAttached = true, AttachedType = typeof(ScrollViewer))]
+public sealed partial class ScrollViewerHelper
+{
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/ScrollViewer.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/ScrollViewer.xaml
new file mode 100644
index 00000000..d0190712
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/ScrollViewer.xaml
@@ -0,0 +1,287 @@
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs
index d4a0a8c0..f6416860 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs
@@ -9,11 +9,6 @@ namespace Snap.Hutao.Core.Windowing;
[HighQuality]
internal enum BackdropType
{
- ///
- /// 透明
- ///
- Transparent = -1,
-
///
/// 无
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs
new file mode 100644
index 00000000..1c97aebf
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs
@@ -0,0 +1,211 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.IO;
+using Snap.Hutao.Core.LifeCycle;
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.Com;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.Shell.Common;
+using static Windows.Win32.PInvoke;
+
+namespace Snap.Hutao.Factory.Picker;
+
+[ConstructorGenerated]
+[Injection(InjectAs.Transient, typeof(IFileSystemPickerInteraction))]
+internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInteraction
+{
+ private readonly ICurrentWindowReference currentWindowReference;
+
+ public unsafe ValueResult PickFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
+ {
+ CoCreateInstance(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileOpenDialog dialog).ThrowOnFailure();
+
+ FILEOPENDIALOGOPTIONS options =
+ FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
+ FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM |
+ FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR;
+
+ dialog.SetOptions(options);
+ SetDesktopAsStartupFolder(dialog);
+
+ if (!string.IsNullOrEmpty(title))
+ {
+ dialog.SetTitle(title);
+ }
+
+ if (!string.IsNullOrEmpty(defaultFileName))
+ {
+ dialog.SetFileName(defaultFileName);
+ }
+
+ if (filters is { Length: > 0 })
+ {
+ SetFileTypes(dialog, filters);
+ }
+
+ HRESULT res = dialog.Show(currentWindowReference.GetWindowHandle());
+ if (res == HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_CANCELLED))
+ {
+ return new(false, default);
+ }
+ else
+ {
+ Marshal.ThrowExceptionForHR(res);
+ }
+
+ dialog.GetResult(out IShellItem item);
+
+ PWSTR displayName = default;
+ string file;
+ try
+ {
+ item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
+ file = new((char*)displayName);
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem((nint)displayName.Value);
+ }
+
+ return new(true, file);
+ }
+
+ public unsafe ValueResult SaveFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
+ {
+ CoCreateInstance(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileSaveDialog dialog).ThrowOnFailure();
+
+ FILEOPENDIALOGOPTIONS options =
+ FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
+ FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM |
+ FILEOPENDIALOGOPTIONS.FOS_STRICTFILETYPES |
+ FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR;
+
+ dialog.SetOptions(options);
+ SetDesktopAsStartupFolder(dialog);
+
+ if (!string.IsNullOrEmpty(title))
+ {
+ dialog.SetTitle(title);
+ }
+
+ if (!string.IsNullOrEmpty(defaultFileName))
+ {
+ dialog.SetFileName(defaultFileName);
+ }
+
+ if (filters is { Length: > 0 })
+ {
+ SetFileTypes(dialog, filters);
+ }
+
+ HRESULT res = dialog.Show(currentWindowReference.GetWindowHandle());
+ if (res == HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_CANCELLED))
+ {
+ return new(false, default);
+ }
+ else
+ {
+ Marshal.ThrowExceptionForHR(res);
+ }
+
+ dialog.GetResult(out IShellItem item);
+
+ PWSTR displayName = default;
+ string file;
+ try
+ {
+ item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
+ file = new((char*)displayName);
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem((nint)displayName.Value);
+ }
+
+ return new(true, file);
+ }
+
+ public unsafe ValueResult PickFolder(string? title)
+ {
+ CoCreateInstance(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileOpenDialog dialog).ThrowOnFailure();
+
+ FILEOPENDIALOGOPTIONS options =
+ FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
+ FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM |
+ FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS |
+ FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR;
+
+ dialog.SetOptions(options);
+ SetDesktopAsStartupFolder(dialog);
+
+ if (!string.IsNullOrEmpty(title))
+ {
+ dialog.SetTitle(title);
+ }
+
+ HRESULT res = dialog.Show(currentWindowReference.GetWindowHandle());
+ if (res == HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_CANCELLED))
+ {
+ return new(false, default!);
+ }
+ else
+ {
+ Marshal.ThrowExceptionForHR(res);
+ }
+
+ dialog.GetResult(out IShellItem item);
+
+ PWSTR displayName = default;
+ string file;
+ try
+ {
+ item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
+ file = new((char*)displayName);
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem((nint)displayName.Value);
+ }
+
+ return new(true, file);
+ }
+
+ private static unsafe void SetFileTypes(TDialog dialog, (string Name, string Type)[] filters)
+ where TDialog : IFileDialog
+ {
+ List unmanagedStringPtrs = new(filters.Length * 2);
+ List filterSpecs = new(filters.Length);
+ foreach ((string name, string type) in filters)
+ {
+ nint pName = Marshal.StringToHGlobalUni(name);
+ nint pType = Marshal.StringToHGlobalUni(type);
+ unmanagedStringPtrs.Add(pName);
+ unmanagedStringPtrs.Add(pType);
+ COMDLG_FILTERSPEC spec = default;
+ spec.pszName = *(PCWSTR*)&pName;
+ spec.pszSpec = *(PCWSTR*)&pType;
+ filterSpecs.Add(spec);
+ }
+
+ fixed (COMDLG_FILTERSPEC* ptr = CollectionsMarshal.AsSpan(filterSpecs))
+ {
+ dialog.SetFileTypes((uint)filterSpecs.Count, ptr);
+ }
+
+ foreach (ref readonly nint ptr in CollectionsMarshal.AsSpan(unmanagedStringPtrs))
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+
+ private static unsafe void SetDesktopAsStartupFolder(TDialog dialog)
+ where TDialog : IFileDialog
+ {
+ string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
+ SHCreateItemFromParsingName(desktopPath, default, typeof(IShellItem).GUID, out object shellItem).ThrowOnFailure();
+ dialog.SetFolder((IShellItem)shellItem);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteractionExtension.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteractionExtension.cs
new file mode 100644
index 00000000..62e7dd83
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteractionExtension.cs
@@ -0,0 +1,19 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.IO;
+
+namespace Snap.Hutao.Factory.Picker;
+
+internal static class FileSystemPickerInteractionExtension
+{
+ public static ValueResult PickFile(this IFileSystemPickerInteraction interaction, string? title, (string Name, string Type)[]? filters)
+ {
+ return interaction.PickFile(title, null, filters);
+ }
+
+ public static ValueResult PickFolder(this IFileSystemPickerInteraction interaction)
+ {
+ return interaction.PickFolder(null);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs
index bc085e6e..713d1dc3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs
@@ -1,45 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Core.LifeCycle;
-using System.Runtime.InteropServices;
-using Windows.Win32;
-using Windows.Win32.Foundation;
-using Windows.Win32.System.Com;
-using Windows.Win32.UI.Shell;
-using static Windows.Win32.PInvoke;
+using Snap.Hutao.Core.IO;
namespace Snap.Hutao.Factory.Picker;
internal interface IFileSystemPickerInteraction
{
-}
+ ValueResult PickFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters);
-[ConstructorGenerated]
-[Injection(InjectAs.Transient, typeof(IFileSystemPickerInteraction))]
-internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInteraction
-{
- private readonly ICurrentWindowReference currentWindowReference;
+ ValueResult PickFolder(string? title);
- public unsafe string PickFile()
- {
- HRESULT result = CoCreateInstance(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileOpenDialog dialog);
- Marshal.ThrowExceptionForHR(result);
-
- dialog.Show(currentWindowReference.GetWindowHandle());
- dialog.GetResult(out IShellItem item);
- PWSTR name = default;
- string file;
- try
- {
- item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out name);
- file = new((char*)name);
- }
- finally
- {
- Marshal.FreeCoTaskMem((nint)name.Value);
- }
-
- return file;
- }
+ ValueResult SaveFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IPickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IPickerFactory.cs
deleted file mode 100644
index 353c6146..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IPickerFactory.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Windows.Storage.Pickers;
-
-namespace Snap.Hutao.Factory.Picker;
-
-///
-/// 文件选择器工厂
-///
-[HighQuality]
-internal interface IPickerFactory
-{
- ///
- /// 获取 经过初始化的
- ///
- /// 初始位置
- /// 提交按钮文本
- /// 文件类型
- /// 经过初始化的
- FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes);
-
- ///
- /// 获取 经过初始化的
- ///
- /// 初始位置
- /// 文件名
- /// 提交按钮文本
- /// 文件类型
- /// 经过初始化的
- FileSavePicker GetFileSavePicker(PickerLocationId location, string fileName, string commitButton, IDictionary> fileTypes);
-
- ///
- /// 获取 经过初始化的
- ///
- /// 经过初始化的
- FolderPicker GetFolderPicker();
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs
deleted file mode 100644
index 6c4ced9e..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Snap.Hutao.Core;
-using Snap.Hutao.Core.LifeCycle;
-using Windows.Storage.Pickers;
-using Windows.Win32.Foundation;
-using WinRT.Interop;
-
-namespace Snap.Hutao.Factory.Picker;
-
-///
-[HighQuality]
-[ConstructorGenerated]
-[Injection(InjectAs.Transient, typeof(IPickerFactory))]
-internal sealed partial class PickerFactory : IPickerFactory
-{
- private const string AnyType = "*";
-
- private readonly ICurrentWindowReference currentWindowReference;
-
- ///
- public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
- {
- FileOpenPicker picker = GetInitializedPicker();
-
- picker.SuggestedStartLocation = location;
- picker.CommitButtonText = commitButton;
-
- foreach (string type in fileTypes)
- {
- picker.FileTypeFilter.Add(type);
- }
-
- // below Windows 11
- if (!UniversalApiContract.IsPresent(WindowsVersion.Windows11))
- {
- // https://github.com/microsoft/WindowsAppSDK/issues/2931
- picker.FileTypeFilter.Add(AnyType);
- }
-
- return picker;
- }
-
- ///
- public FileSavePicker GetFileSavePicker(PickerLocationId location, string fileName, string commitButton, IDictionary> fileTypes)
- {
- FileSavePicker picker = GetInitializedPicker();
-
- picker.SuggestedStartLocation = location;
- picker.SuggestedFileName = fileName;
- picker.CommitButtonText = commitButton;
-
- foreach (KeyValuePair> kvp in fileTypes)
- {
- picker.FileTypeChoices.Add(kvp);
- }
-
- return picker;
- }
-
- ///
- public FolderPicker GetFolderPicker()
- {
- FolderPicker picker = GetInitializedPicker();
-
- // below Windows 11
- if (!UniversalApiContract.IsPresent(WindowsVersion.Windows11))
- {
- // https://github.com/microsoft/WindowsAppSDK/issues/2931
- picker.FileTypeFilter.Add(AnyType);
- }
-
- return picker;
- }
-
- private T GetInitializedPicker()
- where T : new()
- {
- // Create a folder picker.
- T picker = new();
-
- HWND hwnd = currentWindowReference.GetWindowHandle();
- InitializeWithWindow.Initialize(picker, hwnd);
-
- return picker;
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index 52532905..85e8f047 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -16,8 +16,8 @@ namespace Snap.Hutao;
[SuppressMessage("", "CA1001")]
internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
{
- private const int MinWidth = 848;
- private const int MinHeight = 524;
+ private const int MinWidth = 1200;
+ private const int MinHeight = 750;
private readonly WindowOptions windowOptions;
private readonly ILogger logger;
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index 3a3dac4c..4fc3eba6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -12,7 +12,7 @@
+ Version="1.7.19.0" />
Snap Hutao
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
index 882f607f..d2afe321 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
@@ -599,6 +599,12 @@
新增:{0} 个成就 | 更新:{1} 个成就 | 删除:{2} 个成就
+
+ UIAF Json 文件
+
+
+ 打开 UIAF Json 文件
+
单个成就存档内发现多个相同的成就 Id
@@ -869,6 +875,9 @@
选择游戏本体
+
+ 游戏本体
+
找不到 Unity 日志文件
@@ -1334,6 +1343,9 @@
确定要删除存档 {0} 吗?
+
+ 导出 UIAF Json 文件到指定路径
+
获取培养材料中,请稍候...
@@ -1460,9 +1472,15 @@
从胡桃云服务同步祈愿记录
+
+ 导出 UIGF Json 文件到指定路径
+
正在上传到胡桃云服务
+
+ 导入 UIGF Json 文件
+
我已阅读并同意上方的条款
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs
index cd6cfeff..141e0ae4 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs
@@ -44,21 +44,18 @@ internal sealed class GameRecordCharacterAvatarInfoTransformer : IAvatarInfoTran
Equip equip = avatarInfo.EquipList.Last();
- if (equip.ItemId != source.Weapon.Id)
+ // 切换了武器
+ equip.ItemId = source.Weapon.Id;
+ equip.Weapon = new()
{
- // 切换了武器
- equip.ItemId = source.Weapon.Id;
- equip.Weapon = new()
+ Level = source.Weapon.Level,
+ AffixMap = new()
{
- Level = source.Weapon.Level,
- AffixMap = new()
- {
- [100000 + source.Weapon.Id] = source.Weapon.AffixLevel - 1,
- },
- };
+ [100000U + source.Weapon.Id] = source.Weapon.AffixLevel - 1,
+ },
+ };
- // Special case here, don't set EQUIP_WEAPON
- equip.Flat = new() { ItemType = ItemType.ITEM_WEAPON, };
- }
+ // Special case here, don't set EQUIP_WEAPON
+ equip.Flat = new() { ItemType = ItemType.ITEM_WEAPON, };
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs
index 8e8ec8db..554970a2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs
@@ -3,7 +3,6 @@
using Snap.Hutao.Core.IO;
using Snap.Hutao.Factory.Picker;
-using Windows.Storage.Pickers;
namespace Snap.Hutao.Service.Game.Locator;
@@ -15,30 +14,24 @@ namespace Snap.Hutao.Service.Game.Locator;
[Injection(InjectAs.Transient)]
internal sealed partial class ManualGameLocator : IGameLocator
{
- private readonly ITaskContext taskContext;
- private readonly IPickerFactory pickerFactory;
+ private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
///
- public async ValueTask> LocateGamePathAsync()
+ public ValueTask> LocateGamePathAsync()
{
- await taskContext.SwitchToMainThreadAsync();
-
- FileOpenPicker picker = pickerFactory.GetFileOpenPicker(
- PickerLocationId.Desktop,
+ (bool isPickerOk, ValueFile file) = fileSystemPickerInteraction.PickFile(
SH.ServiceGameLocatorFileOpenPickerCommitText,
- ".exe");
-
- (bool isPickerOk, ValueFile file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
+ [(SH.ServiceGameLocatorPickerFilterText, $"{GameConstants.YuanShenFileName};{GameConstants.GenshinImpactFileName}")]);
if (isPickerOk)
{
string fileName = System.IO.Path.GetFileName(file);
if (fileName is GameConstants.YuanShenFileName or GameConstants.GenshinImpactFileName)
{
- return new(true, file);
+ return ValueTask.FromResult>(new(true, file));
}
}
- return new(false, default!);
+ return ValueTask.FromResult>(new(false, default!));
}
}
\ 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 164c6fd3..a7996319 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -11,7 +11,6 @@
true
win-x64
win-x64
- win10-$(Platform).pubxml
true
False
False
@@ -89,6 +88,7 @@
+
@@ -282,6 +282,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -316,6 +317,12 @@
+
+
+ MSBuild:Compile
+
+
+
MSBuild:Compile
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
index cee08806..3de375ba 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
@@ -121,7 +121,10 @@
-
+
+ TextTrimming="CharacterEllipsis"
+ TextWrapping="NoWrap"/>
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
+ ColumnSpacing="8"
+ Columns="2"
+ RowSpacing="8">
-
+
+
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs
index a2937b94..8cd1a283 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs
@@ -11,7 +11,6 @@ using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Service.Achievement;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Dialog;
-using Windows.Storage.Pickers;
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
namespace Snap.Hutao.ViewModel.Achievement;
@@ -24,11 +23,11 @@ namespace Snap.Hutao.ViewModel.Achievement;
[Injection(InjectAs.Scoped)]
internal sealed partial class AchievementImporter
{
+ private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly IContentDialogFactory contentDialogFactory;
private readonly IAchievementService achievementService;
private readonly IClipboardProvider clipboardInterop;
private readonly IInfoBarService infoBarService;
- private readonly IPickerFactory pickerFactory;
private readonly JsonSerializerOptions options;
private readonly ITaskContext taskContext;
@@ -65,10 +64,9 @@ internal sealed partial class AchievementImporter
{
if (achievementService.CurrentArchive is { } archive)
{
- ValueResult pickerResult = await pickerFactory
- .GetFileOpenPicker(PickerLocationId.Desktop, SH.FilePickerImportCommit, ".json")
- .TryPickSingleFileAsync()
- .ConfigureAwait(false);
+ ValueResult pickerResult = fileSystemPickerInteraction.PickFile(
+ SH.ServiceAchievementUIAFImportPickerTitile,
+ [(SH.ServiceAchievementUIAFImportPickerFilterText, ".json")]);
if (pickerResult.TryGetValue(out ValueFile file))
{
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs
index f4fd36c3..f94970de 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs
@@ -15,7 +15,6 @@ using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
-using Windows.Storage.Pickers;
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
using MetadataAchievementGoal = Snap.Hutao.Model.Metadata.Achievement.AchievementGoal;
@@ -33,8 +32,8 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
private readonly SortDescription uncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending);
private readonly SortDescription completionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
+ private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly IContentDialogFactory contentDialogFactory;
- private readonly IPickerFactory pickerFactory;
private readonly AchievementImporter achievementImporter;
private readonly IAchievementService achievementService;
private readonly IMetadataService metadataService;
@@ -257,17 +256,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{
if (SelectedArchive is not null && Achievements is not null)
{
- string fileName = $"{achievementService.CurrentArchive?.Name}.json";
- Dictionary> fileTypes = new()
- {
- [SH.ViewModelAchievementExportFileType] = [".json"],
- };
+ (bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile(
+ SH.ViewModelAchievementUIAFExportPickerTitle,
+ $"{achievementService.CurrentArchive?.Name}.json",
+ [(SH.ViewModelAchievementExportFileType, "*.json")]);
- FileSavePicker picker = pickerFactory
- .GetFileSavePicker(PickerLocationId.Desktop, fileName, SH.FilePickerExportCommit, fileTypes);
-
- (bool isPickerOk, ValueFile file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
- if (isPickerOk)
+ if (isOk)
{
UIAF uiaf = await achievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
if (await file.SerializeToJsonAsync(uiaf, options).ConfigureAwait(false))
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
index 478ba2e7..6d32962b 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
@@ -17,7 +17,6 @@ using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
-using Windows.Storage.Pickers;
namespace Snap.Hutao.ViewModel.GachaLog;
@@ -31,13 +30,13 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
{
private readonly HutaoCloudStatisticsViewModel hutaoCloudStatisticsViewModel;
private readonly IGachaLogQueryProviderFactory gachaLogQueryProviderFactory;
+ private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly IContentDialogFactory contentDialogFactory;
private readonly HutaoCloudViewModel hutaoCloudViewModel;
private readonly IProgressFactory progressFactory;
private readonly IGachaLogService gachaLogService;
private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions options;
- private readonly IPickerFactory pickerFactory;
private readonly ITaskContext taskContext;
private ObservableCollection? archives;
@@ -215,52 +214,52 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
[Command("ImportFromUIGFJsonCommand")]
private async Task ImportFromUIGFJsonAsync()
{
- FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, SH.FilePickerImportCommit, ".json");
- (bool isPickerOk, ValueFile file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
- if (isPickerOk)
+ (bool isOk, ValueFile file) = fileSystemPickerInteraction.PickFile(
+ SH.ViewModelGachaUIGFImportPickerTitile,
+ [(SH.ViewModelGachaLogExportFileType, "*.json")]);
+
+ if (!isOk)
{
- ValueResult result = await file.DeserializeFromJsonAsync(options).ConfigureAwait(false);
- if (result.TryGetValue(out UIGF? uigf))
- {
- await TryImportUIGFInternalAsync(uigf).ConfigureAwait(false);
- }
- else
- {
- infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
- }
+ return;
+ }
+
+ ValueResult result = await file.DeserializeFromJsonAsync(options).ConfigureAwait(false);
+ if (result.TryGetValue(out UIGF? uigf))
+ {
+ await TryImportUIGFInternalAsync(uigf).ConfigureAwait(false);
+ }
+ else
+ {
+ infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
}
}
[Command("ExportToUIGFJsonCommand")]
private async Task ExportToUIGFJsonAsync()
{
- if (SelectedArchive is not null)
+ if (SelectedArchive is null)
{
- Dictionary> fileTypes = new()
- {
- [SH.ViewModelGachaLogExportFileType] = [".json"],
- };
+ return;
+ }
- FileSavePicker picker = pickerFactory.GetFileSavePicker(
- PickerLocationId.Desktop,
- $"{SelectedArchive.Uid}.json",
- SH.FilePickerExportCommit,
- fileTypes);
+ (bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile(
+ SH.ViewModelGachaLogUIGFExportPickerTitle,
+ $"{SelectedArchive.Uid}.json",
+ [(SH.ViewModelGachaLogExportFileType, "*.json")]);
- (bool isPickerOk, ValueFile file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
+ if (!isOk)
+ {
+ return;
+ }
- if (isPickerOk)
- {
- UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false);
- if (await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false))
- {
- infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
- }
- else
- {
- infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
- }
- }
+ UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false);
+ if (await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false))
+ {
+ infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
+ }
+ else
+ {
+ infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs
index 57ffa664..9aec077f 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs
@@ -26,7 +26,6 @@ using Snap.Hutao.ViewModel.Guide;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
-using Windows.Storage.Pickers;
using Windows.System;
namespace Snap.Hutao.ViewModel.Setting;
@@ -41,6 +40,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
{
private readonly HomeCardOptions homeCardOptions = new();
+ private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly HutaoPassportViewModel hutaoPassportViewModel;
private readonly IContentDialogFactory contentDialogFactory;
private readonly IGameLocatorFactory gameLocatorFactory;
@@ -50,7 +50,6 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private readonly HutaoUserOptions hutaoUserOptions;
private readonly IInfoBarService infoBarService;
private readonly RuntimeOptions runtimeOptions;
- private readonly IPickerFactory pickerFactory;
private readonly HotKeyOptions hotKeyOptions;
private readonly IUserService userService;
private readonly ITaskContext taskContext;
@@ -143,8 +142,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
[Command("SetPowerShellPathCommand")]
private async Task SetPowerShellPathAsync()
{
- FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.DocumentsLibrary, SH.FilePickerPowerShellCommit, ".exe");
- (bool isOk, ValueFile file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
+ (bool isOk, ValueFile file) = fileSystemPickerInteraction.PickFile(SH.FilePickerPowerShellCommit, [("PowerShell", "powershell.exe")]);
if (isOk && Path.GetFileNameWithoutExtension(file).Equals("POWERSHELL", StringComparison.OrdinalIgnoreCase))
{
@@ -200,12 +198,9 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
}
[Command("SetDataFolderCommand")]
- private async Task SetDataFolderAsync()
+ private void SetDataFolder()
{
- (bool isOk, string folder) = await pickerFactory
- .GetFolderPicker()
- .TryPickSingleFolderAsync()
- .ConfigureAwait(false);
+ (bool isOk, string folder) = fileSystemPickerInteraction.PickFolder();
if (isOk)
{