#impl 1096

This commit is contained in:
DismissedLight
2023-11-18 22:41:25 +08:00
parent 0cc75ab245
commit 7861ebf998
25 changed files with 748 additions and 319 deletions

View File

@@ -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;
}
}

View File

@@ -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"
]
}
}

View File

@@ -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

View File

@@ -8,8 +8,7 @@ internal static partial class PInvoke
{
/// <inheritdoc cref="CoCreateInstance(Guid*, object, CLSCTX, Guid*, out object)"/>
internal static unsafe HRESULT CoCreateInstance<TClass, TInterface>(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;

View File

@@ -20,6 +20,7 @@
<ResourceDictionary Source="ms-appx:///Control/Theme/NumericValue.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>

View File

@@ -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
{
}

View File

@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shch="using:Snap.Hutao.Control.Helper">
<Style x:Key="TwoPanelScrollViewerStyle" TargetType="ScrollViewer">
<Setter Property="HorizontalScrollMode" Value="Auto"/>
<Setter Property="VerticalScrollMode" Value="Auto"/>
<Setter Property="IsHorizontalRailEnabled" Value="True"/>
<Setter Property="IsVerticalRailEnabled" Value="True"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="ZoomMode" Value="Disabled"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="VerticalScrollBarVisibility" Value="Visible"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Border
x:Name="Root"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid
Grid.RowSpan="2"
Grid.ColumnSpan="2"
Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="{Binding Path=(shch:ScrollViewerHelper.LeftPanelMaxWidth), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollContentPresenter x:Name="ScrollContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/>
<ContentPresenter Grid.Column="1" Content="{Binding Path=(shch:ScrollViewerHelper.RightPanel), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Grid>
<Grid Grid.RowSpan="2" Grid.ColumnSpan="2"/>
<Grid
Grid.Column="1"
Padding="{ThemeResource ScrollViewerScrollBarMargin}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}">
<ScrollBar
x:Name="VerticalScrollBar"
HorizontalAlignment="Right"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableHeight}"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{TemplateBinding VerticalOffset}"/>
</Grid>
<Grid
Grid.Row="1"
Padding="{ThemeResource ScrollViewerScrollBarMargin}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}">
<ScrollBar
x:Name="HorizontalScrollBar"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableWidth}"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{TemplateBinding HorizontalOffset}"/>
</Grid>
<Border
x:Name="ScrollBarSeparator"
Grid.Row="1"
Grid.Column="1"
Background="{ThemeResource ScrollViewerScrollBarSeparatorBackground}"
Opacity="0"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ScrollingIndicatorStates">
<VisualStateGroup.Transitions>
<VisualTransition From="MouseIndicator" To="NoIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="MouseIndicatorFull" To="NoIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="MouseIndicatorFull" To="MouseIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="TouchIndicator" To="NoIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="NoIndicator"/>
<VisualState x:Name="TouchIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseIndicatorFull">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ScrollBarSeparatorStates">
<VisualStateGroup.Transitions>
<VisualTransition From="ScrollBarSeparatorExpanded" To="ScrollBarSeparatorCollapsed">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorContractBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="{ThemeResource ScrollViewerSeparatorContractDuration}"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="ScrollBarSeparatorCollapsed"/>
<VisualState x:Name="ScrollBarSeparatorExpanded">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorExpandBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="{ThemeResource ScrollViewerSeparatorExpandDuration}"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ScrollBarSeparatorExpandedWithoutAnimation">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorExpandBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ScrollBarSeparatorCollapsedWithoutAnimation">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorContractBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -9,11 +9,6 @@ namespace Snap.Hutao.Core.Windowing;
[HighQuality]
internal enum BackdropType
{
/// <summary>
/// 透明
/// </summary>
Transparent = -1,
/// <summary>
/// 无
/// </summary>

View File

@@ -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<bool, ValueFile> PickFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
{
CoCreateInstance<FileOpenDialog, IFileOpenDialog>(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<bool, ValueFile> SaveFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
{
CoCreateInstance<FileSaveDialog, IFileSaveDialog>(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<bool, string> PickFolder(string? title)
{
CoCreateInstance<FileOpenDialog, IFileOpenDialog>(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>(TDialog dialog, (string Name, string Type)[] filters)
where TDialog : IFileDialog
{
List<nint> unmanagedStringPtrs = new(filters.Length * 2);
List<COMDLG_FILTERSPEC> 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>(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);
}
}

View File

@@ -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<bool, ValueFile> PickFile(this IFileSystemPickerInteraction interaction, string? title, (string Name, string Type)[]? filters)
{
return interaction.PickFile(title, null, filters);
}
public static ValueResult<bool, string> PickFolder(this IFileSystemPickerInteraction interaction)
{
return interaction.PickFolder(null);
}
}

View File

@@ -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<bool, ValueFile> 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<bool, string> PickFolder(string? title);
public unsafe string PickFile()
{
HRESULT result = CoCreateInstance<FileOpenDialog, IFileOpenDialog>(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<bool, ValueFile> SaveFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters);
}

View File

@@ -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;
/// <summary>
/// 文件选择器工厂
/// </summary>
[HighQuality]
internal interface IPickerFactory
{
/// <summary>
/// 获取 经过初始化的 <see cref="FileOpenPicker"/>
/// </summary>
/// <param name="location">初始位置</param>
/// <param name="commitButton">提交按钮文本</param>
/// <param name="fileTypes">文件类型</param>
/// <returns>经过初始化的 <see cref="FileOpenPicker"/></returns>
FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes);
/// <summary>
/// 获取 经过初始化的 <see cref="FileSavePicker"/>
/// </summary>
/// <param name="location">初始位置</param>
/// <param name="fileName">文件名</param>
/// <param name="commitButton">提交按钮文本</param>
/// <param name="fileTypes">文件类型</param>
/// <returns>经过初始化的 <see cref="FileSavePicker"/></returns>
FileSavePicker GetFileSavePicker(PickerLocationId location, string fileName, string commitButton, IDictionary<string, IList<string>> fileTypes);
/// <summary>
/// 获取 经过初始化的 <see cref="FolderPicker"/>
/// </summary>
/// <returns>经过初始化的 <see cref="FolderPicker"/></returns>
FolderPicker GetFolderPicker();
}

View File

@@ -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;
/// <inheritdoc cref="IPickerFactory"/>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Transient, typeof(IPickerFactory))]
internal sealed partial class PickerFactory : IPickerFactory
{
private const string AnyType = "*";
private readonly ICurrentWindowReference currentWindowReference;
/// <inheritdoc/>
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
{
FileOpenPicker picker = GetInitializedPicker<FileOpenPicker>();
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;
}
/// <inheritdoc/>
public FileSavePicker GetFileSavePicker(PickerLocationId location, string fileName, string commitButton, IDictionary<string, IList<string>> fileTypes)
{
FileSavePicker picker = GetInitializedPicker<FileSavePicker>();
picker.SuggestedStartLocation = location;
picker.SuggestedFileName = fileName;
picker.CommitButtonText = commitButton;
foreach (KeyValuePair<string, IList<string>> kvp in fileTypes)
{
picker.FileTypeChoices.Add(kvp);
}
return picker;
}
/// <inheritdoc/>
public FolderPicker GetFolderPicker()
{
FolderPicker picker = GetInitializedPicker<FolderPicker>();
// below Windows 11
if (!UniversalApiContract.IsPresent(WindowsVersion.Windows11))
{
// https://github.com/microsoft/WindowsAppSDK/issues/2931
picker.FileTypeFilter.Add(AnyType);
}
return picker;
}
private T GetInitializedPicker<T>()
where T : new()
{
// Create a folder picker.
T picker = new();
HWND hwnd = currentWindowReference.GetWindowHandle();
InitializeWithWindow.Initialize(picker, hwnd);
return picker;
}
}

View File

@@ -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<MainWindow> logger;

View File

@@ -12,7 +12,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.7.18.0" />
Version="1.7.19.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -599,6 +599,12 @@
<data name="ServiceAchievementImportResultFormat" xml:space="preserve">
<value>新增:{0} 个成就 | 更新:{1} 个成就 | 删除:{2} 个成就</value>
</data>
<data name="ServiceAchievementUIAFImportPickerFilterText" xml:space="preserve">
<value>UIAF Json 文件</value>
</data>
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
<value>打开 UIAF Json 文件</value>
</data>
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
<value>单个成就存档内发现多个相同的成就 Id</value>
</data>
@@ -869,6 +875,9 @@
<data name="ServiceGameLocatorFileOpenPickerCommitText" xml:space="preserve">
<value>选择游戏本体</value>
</data>
<data name="ServiceGameLocatorPickerFilterText" xml:space="preserve">
<value>游戏本体</value>
</data>
<data name="ServiceGameLocatorUnityLogFileNotFound" xml:space="preserve">
<value>找不到 Unity 日志文件</value>
</data>
@@ -1334,6 +1343,9 @@
<data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve">
<value>确定要删除存档 {0} 吗?</value>
</data>
<data name="ViewModelAchievementUIAFExportPickerTitle" xml:space="preserve">
<value>导出 UIAF Json 文件到指定路径</value>
</data>
<data name="ViewModelAvatarPropertyBatchCultivateProgressTitle" xml:space="preserve">
<value>获取培养材料中,请稍候...</value>
</data>
@@ -1460,9 +1472,15 @@
<data name="ViewModelGachaLogRetrieveFromHutaoCloudProgress" xml:space="preserve">
<value>从胡桃云服务同步祈愿记录</value>
</data>
<data name="ViewModelGachaLogUIGFExportPickerTitle" xml:space="preserve">
<value>导出 UIGF Json 文件到指定路径</value>
</data>
<data name="ViewModelGachaLogUploadToHutaoCloudProgress" xml:space="preserve">
<value>正在上传到胡桃云服务</value>
</data>
<data name="ViewModelGachaUIGFImportPickerTitile" xml:space="preserve">
<value>导入 UIGF Json 文件</value>
</data>
<data name="ViewModelGuideActionAgreement" xml:space="preserve">
<value>我已阅读并同意上方的条款</value>
</data>

View File

@@ -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, };
}
}

View File

@@ -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;
/// <inheritdoc/>
public async ValueTask<ValueResult<bool, string>> LocateGamePathAsync()
public ValueTask<ValueResult<bool, string>> 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<ValueResult<bool, string>>(new(true, file));
}
}
return new(false, default!);
return ValueTask.FromResult<ValueResult<bool, string>>(new(false, default!));
}
}

View File

@@ -11,7 +11,6 @@
<UseRidGraph>true</UseRidGraph>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<UseWPF>False</UseWPF>
<UseWindowsForms>False</UseWindowsForms>
@@ -89,6 +88,7 @@
<None Remove="Control\Theme\NumericValue.xaml" />
<None Remove="Control\Theme\PageOverride.xaml" />
<None Remove="Control\Theme\PivotOverride.xaml" />
<None Remove="Control\Theme\ScrollViewer.xaml" />
<None Remove="Control\Theme\SettingsStyle.xaml" />
<None Remove="Control\Theme\TransitionCollection.xaml" />
<None Remove="Control\Theme\Uri.xaml" />
@@ -282,6 +282,7 @@
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.1.0" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.507">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -316,6 +317,12 @@
<AdditionalFiles Include="Resource\Localization\SH.zh-Hant.resx" />
</ItemGroup>
<ItemGroup>
<Page Update="Control\Theme\ScrollViewer.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Control\Theme\FlyoutStyle.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -121,7 +121,10 @@
</Grid.ColumnDefinitions>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Inner.Title}"/>
<TextBlock
Text="{Binding Inner.Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
<Border
Margin="4,0,0,0"
Padding="4,1"
@@ -144,7 +147,8 @@
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Inner.Description}"
TextTrimming="CharacterEllipsis"/>
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
</StackPanel>
<TextBlock
Grid.Column="1"

View File

@@ -7,77 +7,89 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shch="using:Snap.Hutao.Control.Helper"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvs="using:Snap.Hutao.ViewModel.Setting"
d:DataContext="{d:DesignInstance shvs:SettingViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<ScrollViewer>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="1000"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="16,16,16,16" Spacing="{StaticResource SettingsCardSpacing}">
<ScrollViewer shch:ScrollViewerHelper.LeftPanelMaxWidth="800" Style="{StaticResource TwoPanelScrollViewerStyle}">
<shch:ScrollViewerHelper.RightPanel>
<StackPanel Width="360" Margin="0,16,16,16">
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Grid Height="280" Style="{ThemeResource GridCardStyle}">
<Border CornerRadius="{ThemeResource ControlCornerRadius}">
<Image
VerticalAlignment="Center"
Source="ms-appx:///Resource/BlurBackground.png"
Stretch="Fill"/>
<Grid Style="{ThemeResource GridCardStyle}">
<Border
VerticalAlignment="Stretch"
cw:UIElementExtensions.ClipToBounds="True"
CornerRadius="{ThemeResource ControlCornerRadius}">
<Image Source="ms-appx:///Resource/BlurBackground.png" Stretch="Fill"/>
</Border>
<Grid Background="{ThemeResource SystemControlBackgroundAltMediumBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="48"
MaxWidth="120"
MaxHeight="120"
Margin="48,48"
Source="ms-appx:///Assets/Square44x44Logo.targetsize-256.png"/>
<StackPanel
<cwc:UniformGrid
Grid.Row="1"
Margin="8"
Margin="8,0"
VerticalAlignment="Bottom"
Orientation="Horizontal">
ColumnSpacing="8"
Columns="2"
RowSpacing="8">
<HyperlinkButton
Margin="0,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Command="{Binding UpdateCheckCommand}"
Content="{shcm:ResourceString Name=ViewPageSettingUpdateCheckAction}"/>
<HyperlinkButton
Margin="12,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Command="{Binding StoreReviewCommand}"
Content="{shcm:ResourceString Name=ViewPageSettingStoreReviewNavigate}"/>
<HyperlinkButton
Margin="12,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Content="{shcm:ResourceString Name=ViewPageSettingOfficialSiteNavigate}"
NavigateUri="{StaticResource DocumentLink_Home}"/>
<HyperlinkButton
Margin="12,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Content="{shcm:ResourceString Name=ViewPageSettingFeedbackNavigate}"
NavigateUri="{StaticResource DocumentLink_BugReport}"/>
<HyperlinkButton
Margin="12,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Content="{shcm:ResourceString Name=ViewPageSettingTranslateNavigate}"
NavigateUri="{StaticResource DocumentLink_Translate}"/>
<HyperlinkButton
Margin="12,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Content="{shcm:ResourceString Name=ViewPageSettingSponsorNavigate}"
NavigateUri="{StaticResource Sponsor_Afadian}"/>
</StackPanel>
</cwc:UniformGrid>
<TextBlock
Grid.Row="2"
Margin="8"
HorizontalAlignment="Center"
Opacity="0.7"
Style="{StaticResource CaptionTextBlockStyle}"
Text="Copyright © 2022 - 2024 DGP Studio. All Rights Reserved."
TextWrapping="Wrap"/>
</Grid>
</Grid>
</Border>
<TextBlock
Grid.Row="0"
Margin="0,4"
Text="Copyright © 2022 - 2024 DGP Studio. All Rights Reserved."
TextWrapping="Wrap"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingAboutHeader}"/>
</StackPanel>
</shch:ScrollViewerHelper.RightPanel>
<Grid Padding="16" HorizontalAlignment="Left">
<StackPanel Grid.Column="0" Spacing="{StaticResource SettingsCardSpacing}">
<cwc:SettingsExpander
Description="{Binding HutaoOptions.Version}"
Header="{shcm:ResourceString Name=AppName}"
@@ -200,28 +212,36 @@
Description="{shcm:ResourceString Name=ViewPageSettingKeyShortcutAutoClickingDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingKeyShortcutAutoClickingHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE92E;}">
<StackPanel Orientation="Horizontal" Spacing="16">
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Win"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasWindows, Mode=TwoWay}"/>
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Ctrl"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasControl, Mode=TwoWay}"/>
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Shift"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasShift, Mode=TwoWay}"/>
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Alt"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasAlt, Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<cwc:UniformGrid
Margin="16,-12"
ColumnSpacing="16"
Columns="2"
Orientation="Horizontal"
RowSpacing="0">
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Win"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasWindows, Mode=TwoWay}"/>
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Ctrl"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasControl, Mode=TwoWay}"/>
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Shift"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasShift, Mode=TwoWay}"/>
<CheckBox
MinWidth="64"
VerticalAlignment="Center"
Content="Alt"
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasAlt, Mode=TwoWay}"/>
</cwc:UniformGrid>
<ComboBox
MinWidth="120"
VerticalAlignment="Center"
DisplayMemberPath="Name"
ItemsSource="{Binding HotKeyOptions.VirtualKeys}"
@@ -233,6 +253,7 @@
IsOn="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewpageSettingHomeHeader}"/>

View File

@@ -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<bool, ValueFile> pickerResult = await pickerFactory
.GetFileOpenPicker(PickerLocationId.Desktop, SH.FilePickerImportCommit, ".json")
.TryPickSingleFileAsync()
.ConfigureAwait(false);
ValueResult<bool, ValueFile> pickerResult = fileSystemPickerInteraction.PickFile(
SH.ServiceAchievementUIAFImportPickerTitile,
[(SH.ServiceAchievementUIAFImportPickerFilterText, ".json")]);
if (pickerResult.TryGetValue(out ValueFile file))
{

View File

@@ -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<string, IList<string>> 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))

View File

@@ -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<GachaArchive>? 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<bool, UIGF?> result = await file.DeserializeFromJsonAsync<UIGF>(options).ConfigureAwait(false);
if (result.TryGetValue(out UIGF? uigf))
{
await TryImportUIGFInternalAsync(uigf).ConfigureAwait(false);
}
else
{
infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
}
return;
}
ValueResult<bool, UIGF?> result = await file.DeserializeFromJsonAsync<UIGF>(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<string, IList<string>> 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);
}
}

View File

@@ -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)
{