mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-26 12:09:32 +08:00
Compare commits
84
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc19a02f4f | ||
|
|
d59d63d57e | ||
|
|
9ad6e8fa8d | ||
|
|
45ef15b220 | ||
|
|
25dfd1f888 | ||
|
|
0495cd3079 | ||
|
|
ea9c45d78b | ||
|
|
4427fd1a5f | ||
|
|
edf3c3a72c | ||
|
|
55490d2351 | ||
|
|
17ae6655b4 | ||
|
|
342d25deac | ||
|
|
f5616dac2e | ||
|
|
28914c53f6 | ||
|
|
f0cbe8ef88 | ||
|
|
e0b45bf8af | ||
|
|
854fe420fd | ||
|
|
c95ce1087a | ||
|
|
14ae662675 | ||
|
|
1594bd250e | ||
|
|
c83ab4fb08 | ||
|
|
e690454e0c | ||
|
|
27b4f4bb10 | ||
|
|
70315c3a99 | ||
|
|
b8e49a5332 | ||
|
|
1374dc52b8 | ||
|
|
150f93e7b7 | ||
|
|
d1aea71f67 | ||
|
|
37a522ec7f | ||
|
|
8b816b9787 | ||
|
|
4f487a5ca8 | ||
|
|
6c7a547a83 | ||
|
|
bde5751d69 | ||
|
|
23544b6c39 | ||
|
|
2c60a9fb69 | ||
|
|
4ce67c8f1a | ||
|
|
e8a1a6153e | ||
|
|
452fa2ac84 | ||
|
|
60e6d71d48 | ||
|
|
ae0e2349e7 | ||
|
|
3a0f2a4950 | ||
|
|
5263863c23 | ||
|
|
c52f3c3e00 | ||
|
|
be61df890e | ||
|
|
6a7c9113a0 | ||
|
|
ac953af7a1 | ||
|
|
147a6b911f | ||
|
|
972054ca7c | ||
|
|
0cea809b40 | ||
|
|
7ec34e59a7 | ||
|
|
00d41dfbc8 | ||
|
|
0272a34e5c | ||
|
|
d450753c92 | ||
|
|
1e6263c241 | ||
|
|
a4602d29a7 | ||
|
|
7623788991 | ||
|
|
8075183be1 | ||
|
|
058f1a9bf2 | ||
|
|
196b7f0f5e | ||
|
|
99d24b2513 | ||
|
|
7e3fb6cf06 | ||
|
|
a0a0dd9bc7 | ||
|
|
81f75e61ca | ||
|
|
11890c68a8 | ||
|
|
be8621d4d5 | ||
|
|
511c84b2ba | ||
|
|
9e75da9e46 | ||
|
|
547b360db8 | ||
|
|
9be21a8e0a | ||
|
|
efbb41bf3f | ||
|
|
c2220359dd | ||
|
|
a788afdeeb | ||
|
|
b543a889a0 | ||
|
|
478950b75f | ||
|
|
27b4114d7d | ||
|
|
6ead1f3666 | ||
|
|
22a015d8b9 | ||
|
|
f5b7cd6db8 | ||
|
|
5a397d1311 | ||
|
|
0c1bedaeb1 | ||
|
|
e959dcc017 | ||
|
|
41610453a0 | ||
|
|
adf3e0ca9d | ||
|
|
21e80a5b7b |
@@ -68,19 +68,19 @@ jobs:
|
||||
run: dotnet --info
|
||||
|
||||
- name: Build BD.WTTS.Client.AppHost
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q /property:WarningLevel=1
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
|
||||
- name: Build BD.WTTS.Client.Plugins.Accelerator.ReverseProxy
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q /property:WarningLevel=1
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
|
||||
- name: Build BD.WTTS.Client.Avalonia.App
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q /property:WarningLevel=1
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
|
||||
- name: Build BD.WTTS.Client.Avalonia.Designer.HostApp
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q /property:WarningLevel=1
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
|
||||
- name: Test
|
||||
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q /property:WarningLevel=1
|
||||
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
|
||||
# release:
|
||||
# name: Release
|
||||
|
||||
@@ -172,17 +172,36 @@ else
|
||||
fi
|
||||
chmod +x "$base_path/$exec_name.sh"
|
||||
# xdg-icon-resource install "$base_path/Icons/Watt-Toolkit.png" --size 128 Watt-Toolkit
|
||||
#添加桌面文件
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
InitDesktop() {
|
||||
while true; do
|
||||
# 使用 zenity 提示用户选择安装路径或使用默认路径
|
||||
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" --column="选择" --column="路径" TRUE "$HOME/.local/share/applications/" FALSE "$HOME/Desktop")
|
||||
|
||||
# 检查用户输入
|
||||
if [ "$choice" == "$HOME/.local/share/applications/" ]; then
|
||||
target_dir="$HOME/.local/share/applications/"
|
||||
break
|
||||
elif [ "$choice" == "$HOME/Desktop" ]; then
|
||||
target_dir="$HOME/Desktop/"
|
||||
break
|
||||
else
|
||||
echo "无效选项,请输入 1 或 2。"
|
||||
fi
|
||||
done
|
||||
#添加桌面文件
|
||||
rm -rf "$target_dir/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
|
||||
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
|
||||
StartupNotify=false" >"$target_dir/Watt Toolkit.desktop"
|
||||
chmod +x "$target_dir/Watt Toolkit.desktop"
|
||||
|
||||
}
|
||||
InitDesktop
|
||||
# update-desktop-database ~/.local/share/applications
|
||||
#运行程序
|
||||
Show_Run "下载安装完成,是否启动程序?"
|
||||
|
||||
@@ -1,7 +1,38 @@
|
||||
#!/bin/bash
|
||||
base_path="$HOME/WattToolkit"
|
||||
mkdir -p "$base_path"
|
||||
cd "$base_path" || exit 1
|
||||
# 设置默认的 base_path 参数
|
||||
default_base_path="$HOME/WattToolkit"
|
||||
base_path="$default_base_path"
|
||||
|
||||
# 循环直到用户输入有效路径或直接回车
|
||||
while true; do
|
||||
# 使用 zenity 提示用户选择安装路径或使用默认路径
|
||||
custom_base_path=$(zenity --entry --title="安装路径" --text="请输入安装路径(默认为 $default_base_path,不输入则使用默认路径)")
|
||||
|
||||
# 如果用户提供了自定义路径,则使用该路径
|
||||
if [ -n "$custom_base_path" ]; then
|
||||
base_path="$custom_base_path"
|
||||
break
|
||||
elif [ -z "$custom_base_path" ]; then
|
||||
# 用户直接回车,使用默认路径
|
||||
break
|
||||
else
|
||||
# 用户输入无效路径,重新提示
|
||||
echo "无效路径,请重新输入。"
|
||||
fi
|
||||
done
|
||||
|
||||
# 创建安装路径
|
||||
mkdir -p "$base_path" || {
|
||||
echo "无法创建安装路径 $base_path,请检查权限或路径是否正确。"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 切换到安装路径
|
||||
cd "$base_path" || {
|
||||
echo "无法切换到安装路径 $base_path,请检查权限或路径是否正确。"
|
||||
exit 1
|
||||
}
|
||||
|
||||
appVer_path="$base_path/WattToolkit.AppVer"
|
||||
exec_name="Steam++"
|
||||
tar_name="WattToolkit.tgz"
|
||||
@@ -326,17 +357,36 @@ Kill_Process
|
||||
#解压
|
||||
Decompression
|
||||
# xdg-icon-resource install "$base_path/Icons/Watt-Toolkit.png" --size 128 Watt-Toolkit
|
||||
#添加桌面文件
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
InitDesktop() {
|
||||
while true; do
|
||||
# 使用 zenity 提示用户选择安装路径或使用默认路径
|
||||
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" --column="选择" --column="路径" TRUE "$HOME/.local/share/applications/" FALSE "$HOME/Desktop")
|
||||
|
||||
# 检查用户输入
|
||||
if [ "$choice" == "$HOME/.local/share/applications/" ]; then
|
||||
target_dir="$HOME/.local/share/applications/"
|
||||
break
|
||||
elif [ "$choice" == "$HOME/Desktop" ]; then
|
||||
target_dir="$HOME/Desktop/"
|
||||
break
|
||||
else
|
||||
echo "无效选项,请输入 1 或 2。"
|
||||
fi
|
||||
done
|
||||
#添加桌面文件
|
||||
rm -rf "$target_dir/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
|
||||
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
|
||||
StartupNotify=false" >"$target_dir/Watt Toolkit.desktop"
|
||||
chmod +x "$target_dir/Watt Toolkit.desktop"
|
||||
|
||||
}
|
||||
InitDesktop
|
||||
# update-desktop-database ~/.local/share/applications
|
||||
#运行程序
|
||||
Show_Run "下载安装完成,是否启动程序?"
|
||||
|
||||
+1
-1
Submodule ref/Avalonia.Image2 updated: c4162f332f...de291c0d95
+1
-1
Submodule ref/DirectoryPackages updated: 6974b6215e...ee8197bcbd
+1
-1
Submodule ref/SteamClient updated: 079a2c22ef...d137c6195b
Submodule ref/WTTS.MicroServices.ClientSDK updated: 29a5a63dcb...5f3111baad
+1
-1
Submodule ref/WinAuth updated: d3f45cffe2...f6ad4f84ca
@@ -16,12 +16,12 @@ public static partial class AssemblyInfo
|
||||
/// <summary>
|
||||
/// 预览版本号,范围 1~9,只增不减
|
||||
/// </summary>
|
||||
const string ver_for_preview = "7";
|
||||
const string ver_for_preview = "8";
|
||||
|
||||
/// <summary>
|
||||
/// RC 版本号,范围 0~9,只增不减
|
||||
/// </summary>
|
||||
const string ver_for_rc = "7";
|
||||
const string ver_for_rc = "8";
|
||||
|
||||
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
|
||||
|
||||
|
||||
@@ -38,9 +38,9 @@ public interface IFontManagerImpl2 : IFontManagerImpl
|
||||
return Impl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface);
|
||||
}
|
||||
|
||||
bool IFontManagerImpl.TryCreateGlyphTypeface(Stream stream, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface)
|
||||
bool IFontManagerImpl.TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
return Impl.TryCreateGlyphTypeface(stream, out glyphTypeface);
|
||||
return Impl.TryCreateGlyphTypeface(stream, fontSimulations, out glyphTypeface);
|
||||
}
|
||||
|
||||
//IGlyphTypeface IFontManagerImpl.CreateGlyphTypeface(Typeface typeface)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
@@ -8,70 +8,76 @@
|
||||
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
|
||||
IgnorableNamespaces="uap rescap desktop desktop2">
|
||||
|
||||
<Identity
|
||||
Name="4651ED44255E.47979655102CE"
|
||||
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
|
||||
Version="3.0.207.0" />
|
||||
<Identity
|
||||
Name="4651ED44255E.47979655102CE"
|
||||
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
|
||||
Version="3.0.207.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Watt Toolkit</DisplayName>
|
||||
<PublisherDisplayName>软妹币玩家</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<DisplayName>Watt Toolkit</DisplayName>
|
||||
<PublisherDisplayName>软妹币玩家</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Watt Toolkit"
|
||||
Description="「Watt Toolkit」是一个开源跨平台的多功能游戏工具箱,此工具的大部分功能都是需要您下载安装 Steam 才能使用。"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png" ShortName="Steam++">
|
||||
<uap:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo"/>
|
||||
<uap:ShowOn Tile="wide310x150Logo"/>
|
||||
<uap:ShowOn Tile="square310x310Logo"/>
|
||||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile >
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
<uap:InitialRotationPreference>
|
||||
<uap:Rotation Preference="landscape"/>
|
||||
</uap:InitialRotationPreference>
|
||||
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badgeAndTileText"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="Steam++\Steam++.exe" />
|
||||
<desktop:Extension Category="windows.startupTask" Executable="Steam++\Steam++.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run" />
|
||||
</desktop:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Watt Toolkit"
|
||||
Description="「Watt Toolkit」是一个开源跨平台的多功能游戏工具箱,此工具的大部分功能都是需要您下载安装 Steam 才能使用。"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png" ShortName="Steam++">
|
||||
<uap:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo"/>
|
||||
<uap:ShowOn Tile="wide310x150Logo"/>
|
||||
<uap:ShowOn Tile="square310x310Logo"/>
|
||||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile >
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
<uap:InitialRotationPreference>
|
||||
<uap:Rotation Preference="landscape"/>
|
||||
</uap:InitialRotationPreference>
|
||||
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badgeAndTileText"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="Steam++\Steam++.exe" />
|
||||
<desktop:Extension Category="windows.startupTask" Executable="Steam++\Steam++.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run" />
|
||||
</desktop:Extension>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="spp">
|
||||
<uap:Logo>Images\Square44x44Logo.png</uap:Logo>
|
||||
<uap:DisplayName>Steam++ URI Scheme</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Extensions>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
</Extensions>
|
||||
<Extensions>
|
||||
<desktop2:Extension Category="windows.firewallRules">
|
||||
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
|
||||
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
|
||||
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
|
||||
</desktop2:FirewallRules>
|
||||
</desktop2:Extension>
|
||||
</Extensions>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="allowElevation" />
|
||||
</Capabilities>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="allowElevation" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
||||
@@ -130,6 +130,9 @@
|
||||
<Compile Update="UI\Views\Controls\Widgets\CarouselItems.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UI\Views\Pages\PluginStorePage.axaml.cs">
|
||||
<DependentUpon>PluginStorePage.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
namespace BD.WTTS.Converters;
|
||||
|
||||
public sealed class WaveProgressValueConverter : IValueConverter
|
||||
{
|
||||
public static readonly WaveProgressValueConverter Instance = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not double i) return 0;
|
||||
return 155 - (i * 2.1);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaveProgressValueColorConverter : IValueConverter
|
||||
{
|
||||
public static readonly WaveProgressValueColorConverter Instance = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not double d) return Brushes.Black;
|
||||
if (d > 50) return Brushes.GhostWhite;
|
||||
return Application.Current?.ActualThemeVariant == ThemeVariant.Dark ? Brushes.GhostWhite : Brushes.Black;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaveProgressValueTextConverter : IValueConverter
|
||||
{
|
||||
public static readonly WaveProgressValueTextConverter Instance = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is not double d ? "0%" : $"{d:#0}%";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaveProgressGradientOffsetConverter : IValueConverter
|
||||
{
|
||||
public static readonly WaveProgressGradientOffsetConverter Instance = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not double v)
|
||||
return Brushes.Blue;
|
||||
|
||||
var app = Application.Current;
|
||||
if (app == null)
|
||||
return null;
|
||||
|
||||
Color primaryColor = Colors.DodgerBlue;
|
||||
Color accentColor = Colors.Transparent;
|
||||
|
||||
try
|
||||
{
|
||||
if (app.FindResource("SystemAccentColor") is Color primaryColor2)
|
||||
{
|
||||
primaryColor = primaryColor2;
|
||||
}
|
||||
if (app.FindResource("SystemAccentColorLight2") is Color accentColor2)
|
||||
{
|
||||
accentColor = accentColor2;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
var isLight = app.RequestedThemeVariant == ThemeVariant.Light;
|
||||
|
||||
v /= 100;
|
||||
v += isLight ? 0.2 : 0.4;
|
||||
if (v > 1)
|
||||
v = 1;
|
||||
|
||||
return new LinearGradientBrush()
|
||||
{
|
||||
EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
|
||||
StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
|
||||
GradientStops = new GradientStops()
|
||||
{
|
||||
new GradientStop() { Color = primaryColor, Offset = 0 },
|
||||
new GradientStop() { Color = isLight ? Colors.Transparent : accentColor, Offset = v },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
@@ -125,12 +125,20 @@ partial class App
|
||||
{
|
||||
subMenu = new NativeMenu();
|
||||
foreach (var sub in item.Value.Items)
|
||||
subMenu.Add(new NativeMenuItem
|
||||
{
|
||||
var menu = new NativeMenuItem
|
||||
{
|
||||
Header = sub.Name,
|
||||
Command = sub.Command,
|
||||
CommandParameter = sub.CommandParameter,
|
||||
});
|
||||
};
|
||||
|
||||
if (sub.IsVisible is Avalonia.Data.IBinding v)
|
||||
menu[!NativeMenuItem.IsVisibleProperty] = v;
|
||||
if (sub.IsEnabled is Avalonia.Data.IBinding e)
|
||||
menu[!NativeMenuItem.IsEnabledProperty] = e;
|
||||
subMenu.Add(menu);
|
||||
}
|
||||
menus.Add(new NativeMenuItem
|
||||
{
|
||||
Header = item.Value.Name,
|
||||
|
||||
@@ -225,16 +225,30 @@ public sealed partial class App : Application
|
||||
|
||||
public static FontFamily DefaultFontFamily => _DefaultFontFamily.Value;
|
||||
|
||||
public void OpenBrowserCommand(object url)
|
||||
public async void OpenBrowserCommand(object? url)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (url == null || string.IsNullOrEmpty(url.ToString()))
|
||||
var urlString = url?.ToString();
|
||||
if (string.IsNullOrEmpty(urlString))
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "打开链接失败");
|
||||
return;
|
||||
}
|
||||
Browser2.Open(url.ToString());
|
||||
try
|
||||
{
|
||||
Uri uri = new(urlString);
|
||||
if (uri.Host.EndsWith("steampp.net", StringComparison.OrdinalIgnoreCase) ||
|
||||
uri.Host.EndsWith("mossimo.net", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await UserService.Current.OpenAuthUrl(urlString);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
Browser2.Open(urlString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -66,6 +66,7 @@
|
||||
<StyleInclude Source="/UI/Styling/Controls/IconElement.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/CustomFilePicker.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/NumericUpDown.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/Skeleton.axaml" />
|
||||
|
||||
<Style Selector="FlyoutPresenter.NoPadding">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<Border Padding="20">
|
||||
<!-- Add Controls for Previewer Here -->
|
||||
<StackPanel Spacing="10">
|
||||
<ui:HyperlinkButton Content="更新日志" />
|
||||
<HyperlinkButton Content="更新日志" />
|
||||
<Button Content="更新日志" Theme="{StaticResource HyperlinkButton}" />
|
||||
<Button
|
||||
Content="更新日志"
|
||||
@@ -220,4 +220,7 @@
|
||||
<Style Selector="Button.Plain:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
</Styles>
|
||||
|
||||
@@ -18,27 +18,15 @@
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{TemplateBinding IsLoading}"
|
||||
Spacing="8">
|
||||
<spp:Image2
|
||||
Spacing="10">
|
||||
<!--<spp:Image2
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/mona-loading-default.gif"
|
||||
Stretch="UniformToFill" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{TemplateBinding CustomLoadingText,
|
||||
Converter={StaticResource IsNullConverter},
|
||||
ConverterParameter=invert}"
|
||||
Text="{TemplateBinding CustomLoadingText}"
|
||||
Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
Stretch="UniformToFill" />-->
|
||||
<Border
|
||||
Width="35"
|
||||
Height="35"
|
||||
@@ -51,6 +39,18 @@
|
||||
Minimum="{TemplateBinding Minimum}"
|
||||
Value="{TemplateBinding ProgressValue}" />
|
||||
</Border>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{TemplateBinding CustomLoadingText,
|
||||
Converter={StaticResource IsNullConverter},
|
||||
ConverterParameter=invert}"
|
||||
Text="{TemplateBinding CustomLoadingText}"
|
||||
Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!--<TextBlock
|
||||
@@ -101,5 +101,8 @@
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:loading /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Effect" Value="blur(5)" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
x:CompileBindings="True">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<!-- Add Controls for Previewer Here -->
|
||||
<StackPanel>
|
||||
<ToggleSwitch Name="loading" Content="Loading" />
|
||||
<ToggleSwitch Name="active" Content="Active" />
|
||||
<Grid
|
||||
MinWidth="400"
|
||||
HorizontalAlignment="Left"
|
||||
ColumnDefinitions="auto,*">
|
||||
<spp:Skeleton
|
||||
Width="100"
|
||||
Height="100"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<Image RenderOptions.BitmapInterpolationMode="HighQuality" Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/ApplicationIcon.ico" />
|
||||
</spp:Skeleton>
|
||||
<UniformGrid
|
||||
Grid.Column="1"
|
||||
Margin="5,0"
|
||||
Columns="1"
|
||||
Rows="3">
|
||||
<spp:Skeleton
|
||||
Margin="0,0,200,0"
|
||||
VerticalAlignment="Top"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<SelectableTextBlock Margin="0,2" Text="是先有鸡还是先有蛋" />
|
||||
</spp:Skeleton>
|
||||
<spp:Skeleton
|
||||
Margin="0,0,100,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<SelectableTextBlock Margin="0,2" Text="什么是工匠精神" />
|
||||
</spp:Skeleton>
|
||||
<spp:Skeleton
|
||||
VerticalAlignment="Bottom"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<SelectableTextBlock Margin="0,2" Text="看山不是山,看水不是水,看山还是山,看水还是水" />
|
||||
</spp:Skeleton>
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Styles.Resources>
|
||||
<ControlTheme x:Key="{x:Type spp:Skeleton}" TargetType="spp:Skeleton">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="spp:Skeleton">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
ClipToBounds="{TemplateBinding ClipToBounds}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Panel>
|
||||
<ContentPresenter
|
||||
x:Name="PART_ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
<spp:PureRectangle
|
||||
x:Name="PART_LoadingBorder"
|
||||
Background="{DynamicResource SkeletonDefaultBackground}"
|
||||
Classes.Active="{Binding Path=IsActive, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
IsHitTestVisible="{TemplateBinding IsLoading}"
|
||||
IsVisible="{TemplateBinding IsLoading}" />
|
||||
<spp:PureRectangle
|
||||
x:Name="PART_ActiveBorder"
|
||||
IsHitTestVisible="{TemplateBinding IsLoading}"
|
||||
IsVisible="{TemplateBinding IsLoading}" />
|
||||
</Panel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</Styles.Resources>
|
||||
|
||||
<Style Selector="spp|Skeleton[IsActive=True][IsLoading=True] /template/ spp|PureRectangle#PART_ActiveBorder">
|
||||
<Style.Animations>
|
||||
<Animation
|
||||
Easing="CubicEaseInOut"
|
||||
FillMode="None"
|
||||
IterationCount="Infinite"
|
||||
PlaybackDirection="Alternate"
|
||||
Duration="0:0:1.4">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Background" Value="{DynamicResource SkeletonStartAnimationBackground}" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Background" Value="{DynamicResource SkeletonEndAnimationBackground}" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -38,6 +38,7 @@
|
||||
<x:String x:Key="Back"></x:String>
|
||||
<x:String x:Key="Sort"></x:String>
|
||||
<x:String x:Key="Move"></x:String>
|
||||
<x:String x:Key="Device"></x:String>
|
||||
|
||||
|
||||
<ui:ImageIconSource x:Key="AppIcon" Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/ApplicationIcon.ico" />
|
||||
|
||||
@@ -21,4 +21,16 @@
|
||||
</OnPlatform>
|
||||
</RenderOptions.BitmapInterpolationMode>-->
|
||||
</ImageBrush>
|
||||
|
||||
<LinearGradientBrush x:Key="SkeletonStartAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#FF1C1F23" />
|
||||
<GradientStop Offset="0" Color="#FF2E3238" />
|
||||
<GradientStop Offset="1" Color="#FF1C1F23" />
|
||||
</LinearGradientBrush>
|
||||
<LinearGradientBrush x:Key="SkeletonEndAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#FF1C1F23" />
|
||||
<GradientStop Offset="1" Color="#FF2E3238" />
|
||||
<GradientStop Offset="1" Color="#FF1C1F23" />
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="SkeletonDefaultBackground" Color="#FF1C1F23" />
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -21,4 +21,16 @@
|
||||
</OnPlatform>
|
||||
</RenderOptions.BitmapInterpolationMode>-->
|
||||
</ImageBrush>
|
||||
|
||||
<LinearGradientBrush x:Key="SkeletonStartAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#FFF9F9F9" />
|
||||
<GradientStop Offset="0" Color="#FFE6E8EA" />
|
||||
<GradientStop Offset="1" Color="#FFF9F9F9" />
|
||||
</LinearGradientBrush>
|
||||
<LinearGradientBrush x:Key="SkeletonEndAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#FFF9F9F9" />
|
||||
<GradientStop Offset="1" Color="#FFE6E8EA" />
|
||||
<GradientStop Offset="1" Color="#FFF9F9F9" />
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="SkeletonDefaultBackground" Color="#FFF9F9F9" />
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -7,6 +7,8 @@ public class PageBase : UserControl
|
||||
{
|
||||
public PageBase() : base()
|
||||
{
|
||||
_disposables = new();
|
||||
|
||||
SizeChanged += PageBaseSizeChanged;
|
||||
|
||||
if (HeaderBackground == null)
|
||||
@@ -14,6 +16,7 @@ public class PageBase : UserControl
|
||||
this[!HeaderBackgroundProperty] = new DynamicResourceExtension("PageHeaderBackgroundBrush");
|
||||
}
|
||||
|
||||
_disposables.Add(UISettings.WindowBackgroundCustomImage.Subscribe(x => IsShowBackgroundImage = !x));
|
||||
//AddHandler(Frame.NavigatingFromEvent, FrameNavigatingFrom, RoutingStrategies.Direct);
|
||||
//AddHandler(Frame.NavigatedToEvent, FrameNavigatedTo, RoutingStrategies.Direct);
|
||||
}
|
||||
@@ -157,6 +160,7 @@ public class PageBase : UserControl
|
||||
{
|
||||
base.OnUnloaded(e);
|
||||
_hasLoaded = false;
|
||||
_disposables.Dispose();
|
||||
}
|
||||
|
||||
private void PageBaseSizeChanged(object? sender, SizeChangedEventArgs e)
|
||||
@@ -315,6 +319,7 @@ public class PageBase : UserControl
|
||||
//private IconSourceElement? _previewImageHost;
|
||||
//private StackPanel? _detailsHost;
|
||||
private ScrollViewer? _scroller;
|
||||
private CompositeDisposable _disposables;
|
||||
}
|
||||
|
||||
public class PageBase<TViewModel> : PageBase, IViewFor<TViewModel> where TViewModel : class
|
||||
@@ -324,13 +329,14 @@ public class PageBase<TViewModel> : PageBase, IViewFor<TViewModel> where TViewMo
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PageBase{TViewModel}"/> class.
|
||||
/// </summary>
|
||||
public PageBase()
|
||||
public PageBase() : base()
|
||||
{
|
||||
// This WhenActivated block calls ViewModel's WhenActivated
|
||||
// block if the ViewModel implements IActivatableViewModel.
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
disposables.Add(UISettings.WindowBackgroundCustomImage.Subscribe(x => IsShowBackgroundImage = !x));
|
||||
//disposables.Add(UISettings.WindowBackgroundCustomImage.Subscribe(x => IsShowBackgroundImage = !x));
|
||||
});
|
||||
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class PureCircle : Control
|
||||
{
|
||||
public static readonly StyledProperty<double> DiameterProperty = AvaloniaProperty.Register<PureCircle, double>(
|
||||
nameof(Diameter));
|
||||
|
||||
public double Diameter
|
||||
{
|
||||
get => GetValue(DiameterProperty);
|
||||
set => SetValue(DiameterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush?>
|
||||
BackgroundProperty = Border.BackgroundProperty.AddOwner<PureCircle>();
|
||||
|
||||
public IBrush? Background
|
||||
{
|
||||
get => GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
|
||||
static PureCircle()
|
||||
{
|
||||
AffectsMeasure<PureCircle>(DiameterProperty);
|
||||
AffectsArrange<PureCircle>(DiameterProperty);
|
||||
AffectsRender<PureCircle>(BackgroundProperty, DiameterProperty);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
double diameter = Math.Max(0, Diameter);
|
||||
return new Size(diameter, diameter);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
double diameter = Math.Max(0, Diameter);
|
||||
return new Size(diameter, diameter);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
double radius = Math.Max(0, Diameter) / 2;
|
||||
context.DrawEllipse(Background, null, new(radius, radius), radius, radius);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Provide the most simplified shape implementation. This is a rectangle with a background, without border and corner radius.
|
||||
/// </summary>
|
||||
public class PureRectangle : Control
|
||||
{
|
||||
public static readonly StyledProperty<IBrush?> BackgroundProperty =
|
||||
Border.BackgroundProperty.AddOwner<PureRectangle>();
|
||||
|
||||
public IBrush? Background
|
||||
{
|
||||
get => GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
|
||||
static PureRectangle()
|
||||
{
|
||||
AffectsRender<PureRectangle>(BackgroundProperty);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
context.DrawRectangle(Background, null, new Rect(Bounds.Size));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class PureRing : PureCircle
|
||||
{
|
||||
public static readonly StyledProperty<double> InnerDiameterProperty = AvaloniaProperty.Register<PureRing, double>(
|
||||
nameof(InnerDiameter));
|
||||
|
||||
public double InnerDiameter
|
||||
{
|
||||
get => GetValue(InnerDiameterProperty);
|
||||
set => SetValue(InnerDiameterProperty, value);
|
||||
}
|
||||
|
||||
static PureRing()
|
||||
{
|
||||
AffectsRender<PureCircle>(InnerDiameterProperty);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
double outer = Diameter / 2;
|
||||
double inner = InnerDiameter / 2;
|
||||
double thickness = outer - inner;
|
||||
context.DrawEllipse(null,
|
||||
new Pen(Background, outer - inner),
|
||||
new Point(outer, outer),
|
||||
outer - thickness / 2,
|
||||
outer - thickness / 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class Skeleton : ContentControl
|
||||
{
|
||||
public static readonly StyledProperty<bool> IsActiveProperty =
|
||||
AvaloniaProperty.Register<Skeleton, bool>(nameof(IsActive));
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get { return GetValue(IsActiveProperty); }
|
||||
set { SetValue(IsActiveProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsLoadingProperty =
|
||||
AvaloniaProperty.Register<Skeleton, bool>(nameof(IsLoading));
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get => GetValue(IsLoadingProperty);
|
||||
set => SetValue(IsLoadingProperty, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<UserControl
|
||||
x:Class="BD.WTTS.UI.Views.Controls.WaveProgress"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="spp|WaveProgress">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border
|
||||
Canvas.Left="20"
|
||||
Canvas.Top="20"
|
||||
Canvas.Right="20"
|
||||
Canvas.Bottom="20"
|
||||
Width="200"
|
||||
Height="200"
|
||||
Margin="20"
|
||||
Background="Transparent"
|
||||
BorderBrush="{DynamicResource ControlElevationBorderBrush}"
|
||||
BorderThickness="1.2"
|
||||
BoxShadow="{DynamicResource LowShadow}"
|
||||
ClipToBounds="False"
|
||||
CornerRadius="100">
|
||||
<Border.Resources>
|
||||
<spp:WaveProgressValueConverter x:Key="WValueConvert" />
|
||||
<spp:WaveProgressValueTextConverter x:Key="WValueTextConvert" />
|
||||
<spp:WaveProgressValueColorConverter x:Key="WValueColorConvert" />
|
||||
<spp:WaveProgressGradientOffsetConverter x:Key="WGradientConverter" />
|
||||
</Border.Resources>
|
||||
|
||||
|
||||
<Border
|
||||
Width="200"
|
||||
Height="200"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="100">
|
||||
<Panel>
|
||||
<Canvas Width="200" Height="200">
|
||||
<Path
|
||||
Canvas.Top="{TemplateBinding Value,
|
||||
Converter={StaticResource WValueConvert}}"
|
||||
Classes="WaveAnimatedFast"
|
||||
Data="M 0,50 H 700 V 350 H 0 M 0,50 C 25,55 75,45 100,50 S 175,55 200,50 S 275,45 300,50 S 375,55 400,50 S 475,45 500,50 S 575,55 600,50 S 675,45 700,50"
|
||||
Fill="{TemplateBinding Value,
|
||||
Converter={StaticResource WGradientConverter}}"
|
||||
Opacity="0.45"
|
||||
StrokeThickness="0">
|
||||
|
||||
<Path.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Canvas.Top" Duration="0:0:1.5">
|
||||
<DoubleTransition.Easing>
|
||||
<CircularEaseOut />
|
||||
</DoubleTransition.Easing>
|
||||
</DoubleTransition>
|
||||
</Transitions>
|
||||
</Path.Transitions>
|
||||
</Path>
|
||||
<Path
|
||||
Canvas.Top="{TemplateBinding Value,
|
||||
Converter={StaticResource WValueConvert}}"
|
||||
Classes="WaveAnimated"
|
||||
Data="M 0,50 H 700 V 350 H 0 M 0,50 C 25,55 75,45 100,50 S 175,55 200,50 S 275,45 300,50 S 375,55 400,50 S 475,45 500,50 S 575,55 600,50 S 675,45 700,50"
|
||||
Fill="{TemplateBinding Value,
|
||||
Converter={StaticResource WGradientConverter}}"
|
||||
Opacity="0.8"
|
||||
StrokeThickness="0">
|
||||
<Path.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Canvas.Top" Duration="0:0:2.5">
|
||||
<DoubleTransition.Easing>
|
||||
<CircularEaseOut />
|
||||
</DoubleTransition.Easing>
|
||||
</DoubleTransition>
|
||||
</Transitions>
|
||||
</Path.Transitions>
|
||||
</Path>
|
||||
</Canvas>
|
||||
<TextBlock
|
||||
Name="PART_Text"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{TemplateBinding Value,
|
||||
Converter={StaticResource WValueColorConvert}}"
|
||||
Text="{TemplateBinding Value,
|
||||
Converter={StaticResource WValueTextConvert}}"
|
||||
Theme="{StaticResource TitleTextBlockStyle}">
|
||||
<TextBlock.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.3" />
|
||||
</Transitions>
|
||||
</TextBlock.Transitions>
|
||||
</TextBlock>
|
||||
</Panel>
|
||||
</Border>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="spp|WaveProgress[IsTextVisible=True] /template/ TextBlock#PART_Text">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="spp|WaveProgress[IsTextVisible=False] /template/ TextBlock#PART_Text">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.WaveAnimated">
|
||||
<Style.Animations>
|
||||
<Animation IterationCount="INFINITE" Duration="0:0:6">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Canvas.Left" Value="-100" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Canvas.Left" Value="-500" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="Path.WaveAnimatedFast">
|
||||
<Style.Animations>
|
||||
<Animation IterationCount="INFINITE" Duration="0:0:3">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Canvas.Left" Value="-100" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Canvas.Left" Value="-500" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public sealed partial class WaveProgress : UserControl
|
||||
{
|
||||
public static readonly DirectProperty<WaveProgress, double> ValueProperty =
|
||||
AvaloniaProperty.RegisterDirect<WaveProgress, double>(
|
||||
nameof(Value),
|
||||
o => o.Value,
|
||||
(o, v) => o.Value = v,
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
enableDataValidation: true);
|
||||
|
||||
public static readonly StyledProperty<bool> IsTextVisibleProperty =
|
||||
AvaloniaProperty.Register<WaveProgress, bool>(nameof(IsTextVisible), defaultValue: true);
|
||||
|
||||
public WaveProgress()
|
||||
{
|
||||
InitializeComponent();
|
||||
//var theme = App.Current.thm;
|
||||
//theme.OnBaseThemeChanged += _ =>
|
||||
//{
|
||||
// Value++;
|
||||
// Value--;
|
||||
//};
|
||||
//theme.OnColorThemeChanged += _ =>
|
||||
//{
|
||||
// Value++;
|
||||
// Value--;
|
||||
//};
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private double _value = 65;
|
||||
|
||||
public double Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (value is < 0 or > 100)
|
||||
return;
|
||||
|
||||
SetAndRaise(ValueProperty, ref _value, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTextVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetValue(IsTextVisibleProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValue(IsTextVisibleProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,53 @@
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<TextBlock Text="Test Control" />
|
||||
<StackPanel>
|
||||
<ToggleSwitch Name="loading" Content="Loading" />
|
||||
<ToggleSwitch Name="active" Content="Active" />
|
||||
<Grid
|
||||
MinWidth="400"
|
||||
HorizontalAlignment="Left"
|
||||
ColumnDefinitions="auto,*">
|
||||
<spp:Skeleton
|
||||
Width="100"
|
||||
Height="100"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<Image RenderOptions.BitmapInterpolationMode="HighQuality" Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/ApplicationIcon.ico" />
|
||||
</spp:Skeleton>
|
||||
<UniformGrid
|
||||
Grid.Column="1"
|
||||
Margin="5,0"
|
||||
Columns="1"
|
||||
Rows="3">
|
||||
<spp:Skeleton
|
||||
Margin="0,0,200,0"
|
||||
VerticalAlignment="Top"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<SelectableTextBlock Margin="0,2" Text="是先有鸡还是先有蛋" />
|
||||
</spp:Skeleton>
|
||||
<spp:Skeleton
|
||||
Margin="0,0,100,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<SelectableTextBlock Margin="0,2" Text="什么是工匠精神" />
|
||||
</spp:Skeleton>
|
||||
<spp:Skeleton
|
||||
VerticalAlignment="Bottom"
|
||||
CornerRadius="2"
|
||||
IsActive="{Binding #active.IsChecked}"
|
||||
IsLoading="{Binding #loading.IsChecked}">
|
||||
<SelectableTextBlock Margin="0,2" Text="看山不是山,看水不是水,看山还是山,看水还是水" />
|
||||
</spp:Skeleton>
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Carousel>
|
||||
</spp:PageBase>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<Panel>
|
||||
<Image Source="/UI/Assets/back.png" Stretch="UniformToFill" />
|
||||
<!--<Image Source="/UI/Assets/back.png" Stretch="UniformToFill" />-->
|
||||
<!--<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -279,60 +279,60 @@
|
||||
ItemsSource="{Binding Shops}">
|
||||
<spp:CarouselItems.ItemTemplate>
|
||||
<DataTemplate x:DataType="spp:ShopRecommendGoodItem">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2
|
||||
EnableCancelToken="False"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<!--<Border spp:Animations.EnableAnimations="True">-->
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2
|
||||
EnableCancelToken="False"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</Border>
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
<!--</Border>-->
|
||||
</DataTemplate>
|
||||
</spp:CarouselItems.ItemTemplate>
|
||||
</spp:CarouselItems>
|
||||
|
||||
@@ -216,9 +216,22 @@
|
||||
|
||||
<StackPanel VerticalAlignment="Center" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<TextBlock Text="{Binding Path=User.NickName, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
|
||||
|
||||
<Border VerticalAlignment="Center" IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
|
||||
<TextBlock FontSize="10" Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}❤️{0}}" />
|
||||
<Border
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#453a94" />
|
||||
<GradientStop Offset="1.0" Color="#f43b47" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}❤️{0}}" />
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
@@ -263,18 +276,18 @@
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/info"
|
||||
Header="{Binding Path=Res.PersonalCenter, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
|
||||
<MenuItem
|
||||
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/bind"
|
||||
Header="{Binding Path=Res.BindThirdPartyAccount, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/safe"
|
||||
Header="{Binding Path=Res.PersonalSafe, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<UserControl
|
||||
x:Class="BD.WTTS.UI.Views.Pages.PluginStorePage"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="spp:HomePageViewModel"
|
||||
mc:Ignorable="d">
|
||||
<ScrollViewer Padding="20">
|
||||
<!--<spp:ContentLoader IsLoading="False">-->
|
||||
<StackPanel
|
||||
x:Name="HomeRoot"
|
||||
MaxWidth="1080"
|
||||
Spacing="20">
|
||||
<TextBlock
|
||||
Margin="-3,0,0,0"
|
||||
Text="插件商店是什么?"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<TextBlock Text="3.0版本的计划是将所有功能插件化,拆分后按需下载安装,每个人都可以只下载自己需要的插件功能使用,并且支持所有人都可以开发插件,等等还有许多暂时不方便说的内容,因为计划的过于异想天开,且遇到的问题有点多,所以开发难度有点点大,这个功能上线还需要一点时间,对一直等待的大家说声抱歉麻烦在多等等了😭。" TextWrapping="WrapWithOverflow" />
|
||||
<TextBlock
|
||||
Margin="-3,0,0,0"
|
||||
Text="已安装功能"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<ItemsControl ItemsSource="{Binding MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}">
|
||||
<ItemsControl.Resources>
|
||||
<ControlTheme
|
||||
x:Key="NavButtonStyle"
|
||||
BasedOn="{StaticResource {x:Type Button}}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Width" Value="120" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="6" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
|
||||
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ItemsControl.Resources>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="spp:TabItemViewModel">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<Button
|
||||
Command="{Binding $parent[spp:HomePage].((spp:HomePageViewModel)DataContext).NavgationToMenuPageCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Theme="{StaticResource NavButtonStyle}">
|
||||
<Grid RowDefinitions="3*,*">
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CornerRadius="4 4 0 0"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality">
|
||||
<spp:Image2
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0,10,0,0"
|
||||
Source="{Binding IconKey}"
|
||||
Stretch="Uniform" />
|
||||
</Border>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="12"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BD.WTTS.UI.Views.Pages;
|
||||
|
||||
public partial class PluginStorePage : UserControl
|
||||
{
|
||||
public PluginStorePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,10 @@
|
||||
Text="{Binding Path=Res.Settings_UI, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<Border IsEnabled="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<ui:SettingsExpander
|
||||
Description="{Binding Path=Res.Advertisement_NoShow_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Header="{Binding Path=Res.Settings_UI_StartDefaultPage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsEnabled="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
|
||||
<Border>
|
||||
<ui:SettingsExpander Header="{Binding Path=Res.Settings_UI_StartDefaultPage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource HeartBroken}" />
|
||||
<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource Device}" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ComboBox ItemsSource="{Binding Path=MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}" SelectedItem="{Binding SelectDefaultPage, Mode=TwoWay}">
|
||||
|
||||
@@ -172,13 +172,13 @@
|
||||
|
||||
<WrapPanel Margin="0,20" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Path=Res.User_LoginUnexpectedPrompt, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
<ui:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
Margin="5,0"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding ManualLogin}">
|
||||
<TextBlock Text="{Binding Path=Res.Login_ManualLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
</ui:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</WrapPanel>
|
||||
|
||||
<ProgressBar
|
||||
@@ -222,23 +222,23 @@
|
||||
FontSize="12"
|
||||
Text="{Binding Path=Res.User_RegisterAgreed, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
<ui:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
Margin="5,0"
|
||||
Padding="0"
|
||||
Command="{Binding OpenHyperlink}"
|
||||
CommandParameter="Agreement"
|
||||
Cursor="Hand">
|
||||
<TextBlock Text="{Binding Path=Res.User_Agreement, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
</ui:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
<TextBlock Text="{Binding Path=Res.And, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
<ui:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
Margin="5,0"
|
||||
Padding="0"
|
||||
Command="{Binding OpenHyperlink}"
|
||||
CommandParameter="Privacy"
|
||||
Cursor="Hand">
|
||||
<TextBlock Text="{Binding Path=Res.User_Privacy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
</ui:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</WrapPanel>
|
||||
|
||||
<StackPanel
|
||||
|
||||
@@ -45,6 +45,17 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
this.ViewModel.Close(false);
|
||||
}
|
||||
}
|
||||
// 11.1 使用新 API https://docs.avaloniaui.net/docs/concepts/services/activatable-lifetime#handling-uri-activation
|
||||
//if (Application.Current.TryGetFeature<IActivatableLifetime>() is { } activatableLifetime)
|
||||
//{
|
||||
// activatableLifetime.Activated += (s, a) =>
|
||||
// {
|
||||
// if (a is ProtocolActivatedEventArgs protocolArgs && protocolArgs.Kind == ActivationKind.OpenUri)
|
||||
// {
|
||||
// Console.WriteLine($"App activated via Uri: {protocolArgs.Uri}");
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
Application.Current!.UrlsOpened += Current_UrlsOpened;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
|
||||
pageType = page.PageType;
|
||||
}
|
||||
}
|
||||
INavigationService.Instance.Navigate(pageType ?? typeof(HomePage));
|
||||
INavigationService.Instance.Navigate(pageType ?? typeof(PluginStorePage));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
|
||||
#endif
|
||||
new MenuTabItemViewModel("Plugin_Store")
|
||||
{
|
||||
PageType = null,
|
||||
PageType = typeof(PluginStorePage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
|
||||
},
|
||||
|
||||
+1
@@ -123,6 +123,7 @@ public static class KestrelServerOptionsExtensions
|
||||
var httpsPort = IReverseProxyConfig.HttpsPort;
|
||||
options.Listen(IReverseProxyService.Constants.Instance.ProxyIp, httpsPort, listen =>
|
||||
{
|
||||
listen.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
||||
listen.UseFlowAnalyze();
|
||||
listen.UseTls();
|
||||
});
|
||||
|
||||
+32
@@ -450,4 +450,36 @@ sealed partial class CertificateManagerImpl : ICertificateManager
|
||||
var result = ICertificateManager.Constants.IsCertificateInstalled(platformService, packable);
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetCertificateInfo()
|
||||
{
|
||||
var rootCert = RootCertificate;
|
||||
if (rootCert == null)
|
||||
{
|
||||
GenerateCertificate();
|
||||
}
|
||||
rootCert = RootCertificate;
|
||||
|
||||
if (rootCert == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder b = new();
|
||||
b.AppendLine("Subject:");
|
||||
b.AppendLine(rootCert.Subject);
|
||||
b.AppendLine("SerialNumber:");
|
||||
b.AppendLine(rootCert.SerialNumber);
|
||||
b.AppendLine("PeriodValidity:");
|
||||
b.Append(rootCert.GetEffectiveDateString());
|
||||
b.Append(" ~ ");
|
||||
b.Append(rootCert.GetExpirationDateString());
|
||||
b.AppendLine();
|
||||
b.AppendLine("SHA256:");
|
||||
b.AppendLine(rootCert.GetCertHashStringCompat(HashAlgorithmName.SHA256));
|
||||
b.AppendLine("SHA1:");
|
||||
b.AppendLine(rootCert.GetCertHashStringCompat(HashAlgorithmName.SHA1));
|
||||
|
||||
return b.ToString();
|
||||
}
|
||||
}
|
||||
+5
@@ -46,4 +46,9 @@ sealed class LazyCertificateManager : ICertificateManager
|
||||
{
|
||||
return impl().DeleteRootCertificate();
|
||||
}
|
||||
|
||||
public string GetCertificateInfo()
|
||||
{
|
||||
return impl().GetCertificateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
var tlsSniValue = domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom();
|
||||
request.SetRequestContext(new RequestContext(isHttps, tlsSniValue));
|
||||
|
||||
// 设置请求头 host,修改协议为 http
|
||||
// 设置请求头 host,因为连接中已带端口号故 修改协议为 http
|
||||
request.Headers.Host = uri.Host;
|
||||
request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
|
||||
|
||||
|
||||
+29
-2
@@ -80,7 +80,15 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
return;
|
||||
}
|
||||
|
||||
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, domainConfig.Destination);
|
||||
var destination = domainConfig.Destination;
|
||||
if (domainConfig.Destination?.AbsoluteUri.Contains("@") == true)
|
||||
{
|
||||
var newUrl = domainConfig.Destination.AbsoluteUri.Replace("@domain", context.Request.Host.Host);
|
||||
newUrl = newUrl.Replace("@uri", context.Request.RawUrl());
|
||||
destination = new Uri(newUrl);
|
||||
}
|
||||
|
||||
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, destination);
|
||||
var httpClient = httpClientFactory.CreateHttpClient(context.Request.Host.Host, domainConfig);
|
||||
if (!string.IsNullOrEmpty(domainConfig.UserAgent))
|
||||
{
|
||||
@@ -89,9 +97,19 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
|
||||
var forwarderRequestConfig = new ForwarderRequestConfig()
|
||||
{
|
||||
Version = context.Request.Protocol.StartsWith("HTTP/2") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
|
||||
Version = context.Request.Protocol switch
|
||||
{
|
||||
var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
|
||||
var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
|
||||
_ => System.Net.HttpVersion.Version11,
|
||||
},
|
||||
};
|
||||
|
||||
if (domainConfig.IsServerSideProxy)
|
||||
{
|
||||
SetWattHeaders(context, reverseProxyConfig.Service.ServerSideProxyToken);
|
||||
}
|
||||
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
|
||||
|
||||
if (error != ForwarderError.None)
|
||||
@@ -393,6 +411,15 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SetWattHeaders(HttpContext context, string? token)
|
||||
{
|
||||
context.Request.Headers.TryAdd("X-Watt-Origin-Dest-Scheme", context.Request.Scheme);
|
||||
context.Request.Headers.TryAdd("X-Watt-Origin-Dest-Host", context.Request.Host.ToString());
|
||||
context.Request.Headers.TryAdd("X-Watt-Origin-Dest-PathAndQuery", context.Request.GetEncodedPathAndQuery());
|
||||
|
||||
context.Request.Headers.TryAdd("X-Watt-Token", token ?? string.Empty);
|
||||
}
|
||||
|
||||
const int UTF8PreambleLength = 3;
|
||||
const byte UTF8PreambleByte2 = 0xBF;
|
||||
const int UTF8PreambleFirst2Bytes = 0xEFBB;
|
||||
|
||||
+3
-2
@@ -89,6 +89,8 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
|
||||
|
||||
public string? CustomDohAddres { get; set; }
|
||||
|
||||
public string? ServerSideProxyToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个随机的未使用的端口
|
||||
/// </summary>
|
||||
@@ -157,7 +159,7 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
|
||||
return StartProxyResultCode.DeserializeReverseProxySettingsFail;
|
||||
}
|
||||
|
||||
// Linux 如果是443 需要验证 是否允许使用
|
||||
// Linux 如果是443 需要验证 是否允许使用
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reverseProxySettings.ProxyIp))
|
||||
@@ -178,7 +180,6 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
|
||||
|
||||
protected virtual void CheckRootCertificate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected abstract Task<StartProxyResult> StartProxyImpl();
|
||||
|
||||
+9
@@ -193,6 +193,9 @@ public interface ICertificateManager
|
||||
var isRootCertificateInstalled = IsCertificateInstalled();
|
||||
if (!isRootCertificateInstalled)
|
||||
{
|
||||
// 生成证书
|
||||
certificateManager.GenerateCertificate();
|
||||
|
||||
// 安装证书
|
||||
ICertificateManager.Constants.TrustRootCertificate(
|
||||
GetCerFilePath, platformService, certificate2);
|
||||
@@ -271,4 +274,10 @@ public interface ICertificateManager
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
bool? GenerateCertificate();
|
||||
|
||||
/// <summary>
|
||||
/// 获取证书信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetCertificateInfo();
|
||||
}
|
||||
+16
-3
@@ -1,6 +1,9 @@
|
||||
using dotnetCampus.Ipc.CompilerServices.Attributes;
|
||||
|
||||
#if APP_REVERSE_PROXY
|
||||
|
||||
using InstanceReverseProxyService = BD.WTTS.Services.Implementation.YarpReverseProxyServiceImpl;
|
||||
|
||||
#else
|
||||
using InstanceReverseProxyService = BD.WTTS.Services.IReverseProxyService;
|
||||
#endif
|
||||
@@ -59,6 +62,7 @@ public partial interface IReverseProxyService : IDisposable
|
||||
Task StopProxyAsync();
|
||||
|
||||
#if DEBUG
|
||||
|
||||
string GetDebugString()
|
||||
{
|
||||
return $"Pid: {Environment.ProcessId}, Exe: {Environment.ProcessPath}, Asm: {Assembly.GetAssembly(GetType())?.FullName}";
|
||||
@@ -69,6 +73,7 @@ public partial interface IReverseProxyService : IDisposable
|
||||
StartProxyResult r = new(StartProxyResultCode.Exception, new Exception("aaaa"));
|
||||
return Task.FromResult(r);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -139,7 +144,7 @@ public partial interface IReverseProxySettings
|
||||
/// </summary>
|
||||
ushort Socks5ProxyPortId { get; set; }
|
||||
|
||||
#endregion
|
||||
#endregion Socks5
|
||||
|
||||
#region TwoLevelAgent(二级代理)
|
||||
|
||||
@@ -155,7 +160,7 @@ public partial interface IReverseProxySettings
|
||||
|
||||
string? TwoLevelAgentPassword { get; set; }
|
||||
|
||||
#endregion
|
||||
#endregion TwoLevelAgent(二级代理)
|
||||
|
||||
IPAddress? ProxyDNS { get; set; }
|
||||
|
||||
@@ -167,6 +172,11 @@ public partial interface IReverseProxySettings
|
||||
bool UseDoh { get; set; }
|
||||
|
||||
string? CustomDohAddres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务端代理令牌
|
||||
/// </summary>
|
||||
string? ServerSideProxyToken { get; set; }
|
||||
}
|
||||
|
||||
[MP2Obj(SerializeLayout.Explicit)]
|
||||
@@ -214,7 +224,9 @@ public readonly partial record struct ReverseProxySettings(
|
||||
[property:MP2Key(20)]
|
||||
bool UseDoh,
|
||||
[property:MP2Key(21)]
|
||||
string? CustomDohAddres)
|
||||
string? CustomDohAddres,
|
||||
[property:MP2Key(22)]
|
||||
string? ServerSideProxyToken)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal IPAddress GetProxyIp() => GetProxyIp(ProxyIp);
|
||||
@@ -267,6 +279,7 @@ public readonly partial record struct ReverseProxySettings(
|
||||
settings.IsSupportIpv6 = IsSupportIpv6;
|
||||
settings.UseDoh = UseDoh;
|
||||
settings.CustomDohAddres = CustomDohAddres;
|
||||
settings.ServerSideProxyToken = ServerSideProxyToken;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -7,6 +7,13 @@ public partial class XunYouGameViewModel : ReactiveSerializationObject, IReactiv
|
||||
[MP2Key(0), JsonPropertyOrder(0)]
|
||||
public XunYouGame Model { get; set; } = model;
|
||||
|
||||
/// <summary>
|
||||
/// PicInfo
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public XunYouPicInfo? PicInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加速延迟
|
||||
/// </summary>
|
||||
@@ -41,12 +48,26 @@ public partial class XunYouGameViewModel : ReactiveSerializationObject, IReactiv
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public float PingSpeedLoss { get; set; }
|
||||
|
||||
XunYouGameInfo? _GameInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏信息
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public XunYouGameInfo? GameInfo { get; set; }
|
||||
public XunYouGameInfo? GameInfo
|
||||
{
|
||||
get => _GameInfo;
|
||||
set
|
||||
{
|
||||
_GameInfo = value;
|
||||
this.RaisePropertyChanged();
|
||||
this.RaisePropertyChanged(nameof(ShowStart));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="XunYouGameInfo.ShowStart"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public bool ShowStart => IsAccelerated && (GameInfo != null && GameInfo.ShowStart);
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在加速等待
|
||||
@@ -62,12 +83,25 @@ public partial class XunYouGameViewModel : ReactiveSerializationObject, IReactiv
|
||||
[MP2Ignore, SystemTextJsonIgnore]
|
||||
public int AcceleratingProgress { get; set; }
|
||||
|
||||
bool _IsAccelerated;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已加速
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Ignore, SystemTextJsonIgnore]
|
||||
public bool IsAccelerated { get; set; }
|
||||
public bool IsAccelerated
|
||||
{
|
||||
get => _IsAccelerated;
|
||||
set
|
||||
{
|
||||
if (_IsAccelerated != value)
|
||||
{
|
||||
_IsAccelerated = value;
|
||||
this.RaisePropertyChanged();
|
||||
this.RaisePropertyChanged(nameof(ShowStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在停止加速等待
|
||||
|
||||
+8
@@ -189,4 +189,12 @@ partial class BackendAcceleratorServiceImpl
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<XunYouStartGameCode>> XY_StartGame(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.StartGame();
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
}
|
||||
await TryReadFileAsync(item, true);
|
||||
//清理 脚本内容 后续 IPC 传递 插件进程读取。
|
||||
item.Content = string.Empty;
|
||||
//item.Content = string.Empty;
|
||||
await scriptRepository.SaveScriptCachePathAsync(item, default);
|
||||
}
|
||||
else
|
||||
@@ -422,7 +422,7 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
file.Delete();
|
||||
await TryReadFileAsync(item, true);
|
||||
//清理 脚本内容 后续 IPC 传递 插件进程读取。
|
||||
item.Content = string.Empty;
|
||||
//item.Content = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,7 +446,7 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
|
||||
if (File.Exists(cachePath))
|
||||
{
|
||||
//item.Content = File.ReadAllText(cachePath);
|
||||
item.Content = File.ReadAllText(cachePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -81,6 +81,12 @@ partial interface IAcceleratorService // XunYou
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<XunYouIsRunningCode>> XY_IsRunning(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 迅游加速器启动游戏
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<XunYouStartGameCode>> XY_StartGame(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
partial interface IXunYouAccelStateToFrontendCallback
|
||||
|
||||
@@ -38,12 +38,25 @@ public sealed partial class GameAcceleratorService
|
||||
|
||||
public ICommand GameAcceleratorCommand { get; }
|
||||
|
||||
public ICommand GameLaunchCommand { get; }
|
||||
|
||||
public ICommand InstallAcceleratorCommand { get; }
|
||||
|
||||
public ICommand UninstallAcceleratorCommand { get; }
|
||||
|
||||
public ICommand AcceleratorChangeAreaCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// VIP 到期时间
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public DateTime? VipEndTime { get; set; }
|
||||
|
||||
const string VipEndTimeStringDef = "新用户免费试用 2 天";
|
||||
|
||||
[Reactive]
|
||||
public string? VipEndTimeString { get; set; } = VipEndTimeStringDef;
|
||||
|
||||
GameAcceleratorService()
|
||||
{
|
||||
Ioc.Get<IAcceleratorService>().InitStateSubscribe(); // 仅旧项目(WattToolkit)上监听
|
||||
@@ -52,6 +65,7 @@ public sealed partial class GameAcceleratorService
|
||||
|
||||
DeleteMyGameCommand = ReactiveCommand.Create<XunYouGameViewModel>(DeleteMyGame);
|
||||
GameAcceleratorCommand = ReactiveCommand.CreateFromTask<XunYouGameViewModel>(GameAccelerator);
|
||||
GameLaunchCommand = ReactiveCommand.CreateFromTask<XunYouGameViewModel>(GameLaunch);
|
||||
InstallAcceleratorCommand = ReactiveCommand.CreateFromTask(InstallAccelerator);
|
||||
UninstallAcceleratorCommand = ReactiveCommand.Create(UninstallAccelerator);
|
||||
AcceleratorChangeAreaCommand = ReactiveCommand.Create<XunYouGameViewModel>(AcceleratorChangeArea);
|
||||
@@ -139,6 +153,12 @@ public sealed partial class GameAcceleratorService
|
||||
case XunYouState.启动中:
|
||||
case XunYouState.启动成功:
|
||||
break;
|
||||
case XunYouState.启动游戏成功:
|
||||
Toast.Show(ToastIcon.Success, "启动游戏成功");
|
||||
break;
|
||||
case XunYouState.启动游戏失败:
|
||||
Toast.Show(ToastIcon.Error, "启动游戏失败");
|
||||
break;
|
||||
default:
|
||||
Games.Items.Where(s => s.IsAccelerating || s.IsAccelerated).ForEach(s =>
|
||||
{
|
||||
@@ -155,6 +175,58 @@ public sealed partial class GameAcceleratorService
|
||||
}
|
||||
});
|
||||
|
||||
UserService.Current.WhenAnyValue(static x => x.User)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(async x =>
|
||||
{
|
||||
string vipEndTimeString = VipEndTimeStringDef;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(x?.WattOpenId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vipEndTimeResult = await IMicroServiceClient.Instance.Manage.GetXunYouVipEndTime();
|
||||
if (vipEndTimeResult == null)
|
||||
{
|
||||
Log.Error(nameof(GameAcceleratorService),
|
||||
"XunYouSDK.GetVipEndTime is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vipEndTimeResult.IsSuccess || vipEndTimeResult.Content == null)
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, "获取会员时间失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (vipEndTimeResult.Content.Code == XunYouBaseResponseCode.成功)
|
||||
{
|
||||
var etime = vipEndTimeResult.Content.Data?.ExpireTime;
|
||||
if (etime.HasValue)
|
||||
{
|
||||
VipEndTime = etime.Value.ToDateTime(UnixTimestampType.Seconds);
|
||||
if (VipEndTime.Value > DateTime.Now)
|
||||
{
|
||||
vipEndTimeString = $"游戏加速会员有效期 {VipEndTime.Value:yyyy-MM-dd}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error(nameof(GameAcceleratorService),
|
||||
"XunYouSDK.GetVipEndTime is fail, message: {message}, code: {code}.",
|
||||
vipEndTimeResult.Content.Message,
|
||||
vipEndTimeResult.Content.Code);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
VipEndTimeString = vipEndTimeString;
|
||||
}
|
||||
});
|
||||
|
||||
RefreshAllGames();
|
||||
LoadGames();
|
||||
}
|
||||
@@ -183,6 +255,10 @@ public sealed partial class GameAcceleratorService
|
||||
CurrentAcceleratorGame.IsAccelerated = true;
|
||||
CurrentAcceleratorGame.LastAccelerateTime = DateTimeOffset.Now;
|
||||
|
||||
if (CurrentAcceleratorGame.PicInfo == null)
|
||||
{
|
||||
CurrentAcceleratorGame.PicInfo = XunYouSDK.GetPicInfo(CurrentAcceleratorGame.Id);
|
||||
}
|
||||
if (CurrentAcceleratorGame.SelectedArea?.Id != areaId)
|
||||
{
|
||||
var gameInfo = XunYouSDK.GetGameInfo(game.Id);
|
||||
@@ -205,6 +281,7 @@ public sealed partial class GameAcceleratorService
|
||||
var gameInfo = XunYouSDK.GetGameInfo(game.Id);
|
||||
cApp.SelectedArea = gameInfo?.Areas?.FirstOrDefault(s => s.Id == areaId);
|
||||
cApp.SelectedServer = cApp.SelectedArea?.Servers?.FirstOrDefault(s => s.Id == serverId);
|
||||
cApp.PicInfo = XunYouSDK.GetPicInfo(game.Id);
|
||||
|
||||
CurrentAcceleratorGame = cApp;
|
||||
}
|
||||
@@ -403,6 +480,8 @@ public sealed partial class GameAcceleratorService
|
||||
GameAcceleratorSettings.MyGames.Remove(app.Id);
|
||||
if (GameAcceleratorSettings.MyGames.TryAdd(app.Id, app))
|
||||
{
|
||||
if (app.PicInfo == null)
|
||||
app.PicInfo = XunYouSDK.GetPicInfo(app.Id);
|
||||
Current.Games.AddOrUpdate(app);
|
||||
Toast.Show(ToastIcon.Success, "已添加到游戏列表");
|
||||
}
|
||||
@@ -424,6 +503,11 @@ public sealed partial class GameAcceleratorService
|
||||
if (GameAcceleratorSettings.MyGames.Any_Nullable())
|
||||
{
|
||||
Games.Clear();
|
||||
foreach (var item in GameAcceleratorSettings.MyGames.Value!.Values)
|
||||
{
|
||||
if (item.PicInfo == null)
|
||||
item.PicInfo = XunYouSDK.GetPicInfo(item.Id);
|
||||
}
|
||||
Games.AddOrUpdate(GameAcceleratorSettings.MyGames.Value!.Values);
|
||||
}
|
||||
else
|
||||
@@ -433,6 +517,11 @@ public sealed partial class GameAcceleratorService
|
||||
{
|
||||
Games.Clear();
|
||||
GameAcceleratorSettings.MyGames.AddRange(games.Select(s => new KeyValuePair<int, XunYouGameViewModel>(s.Id, new XunYouGameViewModel(s))));
|
||||
foreach (var item in GameAcceleratorSettings.MyGames.Value!.Values)
|
||||
{
|
||||
if (item.PicInfo == null)
|
||||
item.PicInfo = XunYouSDK.GetPicInfo(item.Id);
|
||||
}
|
||||
Games.AddOrUpdate(GameAcceleratorSettings.MyGames.Value!.Values);
|
||||
}
|
||||
}
|
||||
@@ -643,4 +732,16 @@ public sealed partial class GameAcceleratorService
|
||||
XYAccelState = content;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GameLaunch(XunYouGameViewModel app)
|
||||
{
|
||||
var result = await Ioc.Get<IAcceleratorService>().XY_StartGame();
|
||||
if (result.HandleUI(out var r))
|
||||
{
|
||||
if (r == XunYouStartGameCode.失败)
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, $"启动 {app.Name} 失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ partial class ProxyService
|
||||
const ushort httpsPort = 443;
|
||||
ProxyMode proxyMode = defaultProxyMode;
|
||||
|
||||
static string? proxyTokenCache = null;
|
||||
|
||||
/// <summary>
|
||||
/// 启动代理服务
|
||||
/// </summary>
|
||||
@@ -126,7 +128,6 @@ partial class ProxyService
|
||||
//{
|
||||
// //if (string.IsNullOrWhiteSpace(Plugin.Instance.SubProcessPath))
|
||||
// //{
|
||||
|
||||
// //}
|
||||
// // var processPath = Environment.ProcessPath;
|
||||
// // processPath.ThrowIsNull();
|
||||
@@ -156,6 +157,7 @@ partial class ProxyService
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ProxyMode.System:
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
proxyIp_.Value.Equals(IPAddress.Any))
|
||||
@@ -170,6 +172,7 @@ partial class ProxyService
|
||||
return Strings.CommunityFix_SetAsSystemProxyFail;
|
||||
}
|
||||
break;
|
||||
|
||||
case ProxyMode.PAC:
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
proxyIp_.Value.Equals(IPAddress.Any))
|
||||
@@ -203,12 +206,15 @@ partial class ProxyService
|
||||
}
|
||||
}
|
||||
|
||||
string? proxyToken = proxyDomains.Any_Nullable(s => s.ProxyType == ProxyType.ServerAccelerate) ?
|
||||
await TryRequestServerSideProxyToken() : null;
|
||||
|
||||
ReverseProxySettings reverseProxySettings = new(proxyDomains, scripts,
|
||||
isEnableScript, isOnlyWorkSteamBrowser, proxyPort,
|
||||
proxyIp, proxyMode, isProxyGOG, onlyEnableProxyScript,
|
||||
enableHttpProxyToHttps, socks5ProxyEnable, socks5ProxyPortId,
|
||||
twoLevelAgentEnable, twoLevelAgentProxyType, twoLevelAgentIp,
|
||||
twoLevelAgentPortId, twoLevelAgentUserName, twoLevelAgentPassword, proxyDNS, isSupportIpv6, useDoh, customDohAddres);
|
||||
twoLevelAgentPortId, twoLevelAgentUserName, twoLevelAgentPassword, proxyDNS, isSupportIpv6, useDoh, customDohAddres, proxyToken);
|
||||
byte[] reverseProxySettings_ = Serializable.SMP2(reverseProxySettings);
|
||||
var startProxyResult = await reverseProxyService.StartProxyAsync(reverseProxySettings_);
|
||||
if (startProxyResult)
|
||||
@@ -331,12 +337,14 @@ partial class ProxyService
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ProxyMode.System:
|
||||
if (callSet)
|
||||
{
|
||||
await platformService.SetAsSystemProxyAsync(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case ProxyMode.PAC:
|
||||
if (callSet)
|
||||
{
|
||||
@@ -361,6 +369,34 @@ partial class ProxyService
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取服务端代理token
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
static async Task<string?> TryRequestServerSideProxyToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(proxyTokenCache))
|
||||
return proxyTokenCache;
|
||||
|
||||
var resp = await IMicroServiceClient.Instance.Account
|
||||
.GenerateServerSideProxyToken();
|
||||
|
||||
if (resp != null && !string.IsNullOrEmpty(resp.Content?.AccessToken))
|
||||
{
|
||||
proxyTokenCache = resp.Content.AccessToken;
|
||||
}
|
||||
|
||||
return proxyTokenCache;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.LogAndShowT();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作代理服务返回结果
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using BD.Common;
|
||||
using System.Linq;
|
||||
using KeyValuePair = System.Collections.Generic.KeyValuePair;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace BD.WTTS.Services;
|
||||
|
||||
public sealed partial class ProxyService
|
||||
@@ -31,35 +29,35 @@ public sealed partial class ProxyService
|
||||
.Bind(out _ProxyDomainsList)
|
||||
.Subscribe(_ => SelectGroup = ProxyDomains.Items.FirstOrDefault());
|
||||
|
||||
this.WhenValueChanged(x => x.ProxyStatus, false)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(async proxyStatusLeft =>
|
||||
{
|
||||
bool proxyStatusRight;
|
||||
if (proxyStatusLeft)
|
||||
{
|
||||
var reuslt = await StartProxyServiceAsync();
|
||||
proxyStatusRight = reuslt.OnStartedShowToastReturnProxyStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
var reuslt = await StopProxyServiceAsync();
|
||||
proxyStatusRight = reuslt.OnStopedShowToastReturnProxyStatus();
|
||||
}
|
||||
if (proxyStatusLeft != proxyStatusRight)
|
||||
{
|
||||
ProxyStatus = proxyStatusRight;
|
||||
//this.WhenValueChanged(x => x.ProxyStatus, false)
|
||||
// .ObserveOn(RxApp.MainThreadScheduler)
|
||||
// .Subscribe(async proxyStatusLeft =>
|
||||
// {
|
||||
// bool proxyStatusRight;
|
||||
// if (proxyStatusLeft)
|
||||
// {
|
||||
// var reuslt = await StartProxyServiceAsync();
|
||||
// proxyStatusRight = reuslt.OnStartedShowToastReturnProxyStatus();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var reuslt = await StopProxyServiceAsync();
|
||||
// proxyStatusRight = reuslt.OnStopedShowToastReturnProxyStatus();
|
||||
// }
|
||||
// if (proxyStatusLeft != proxyStatusRight)
|
||||
// {
|
||||
// ProxyStatus = proxyStatusRight;
|
||||
|
||||
//UpdateProxyTrayMenuItems();
|
||||
//if (Steamworks.SteamClient.IsValid)
|
||||
//{
|
||||
// if (ProxyStatus)
|
||||
// Steamworks.SteamFriends.SetRichPresence("steam_display", "#Status_Accelerator");
|
||||
// else
|
||||
// Steamworks.SteamFriends.ClearRichPresence();
|
||||
//}
|
||||
}
|
||||
});
|
||||
// //UpdateProxyTrayMenuItems();
|
||||
// //if (Steamworks.SteamClient.IsValid)
|
||||
// //{
|
||||
// // if (ProxyStatus)
|
||||
// // Steamworks.SteamFriends.SetRichPresence("steam_display", "#Status_Accelerator");
|
||||
// // else
|
||||
// // Steamworks.SteamFriends.ClearRichPresence();
|
||||
// //}
|
||||
// }
|
||||
// });
|
||||
|
||||
this.WhenAnyValue(v => v.ProxyDomainsList)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
@@ -113,6 +111,47 @@ public sealed partial class ProxyService
|
||||
|
||||
public SourceList<ScriptDTO> ProxyScripts { get; }
|
||||
|
||||
public async Task StartOrStopProxyService(bool startOrStop)
|
||||
{
|
||||
if (startOrStop == ProxyStatus)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!ProxyStarting)
|
||||
{
|
||||
ProxyStarting = true;
|
||||
|
||||
bool proxyStatusRight;
|
||||
if (startOrStop)
|
||||
{
|
||||
var reuslt = await StartProxyServiceAsync();
|
||||
proxyStatusRight = reuslt.OnStartedShowToastReturnProxyStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
var reuslt = await StopProxyServiceAsync();
|
||||
proxyStatusRight = reuslt.OnStopedShowToastReturnProxyStatus();
|
||||
}
|
||||
|
||||
if (startOrStop != proxyStatusRight)
|
||||
{
|
||||
startOrStop = proxyStatusRight;
|
||||
}
|
||||
|
||||
//UpdateProxyTrayMenuItems();
|
||||
//if (Steamworks.SteamClient.IsValid)
|
||||
//{
|
||||
// if (startOrStop)
|
||||
// Steamworks.SteamFriends.SetRichPresence("steam_display", "#Status_Accelerator");
|
||||
// else
|
||||
// Steamworks.SteamFriends.ClearRichPresence();
|
||||
//}
|
||||
|
||||
ProxyStarting = false;
|
||||
ProxyStatus = startOrStop;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AccelerateProjectDTO>? GetEnableProxyDomains()
|
||||
{
|
||||
if (!ProxyDomains.Items.Any_Nullable())
|
||||
@@ -206,17 +245,20 @@ public sealed partial class ProxyService
|
||||
// return r;
|
||||
//}
|
||||
|
||||
#endregion
|
||||
#endregion HOSTS_PROXY_RUNNING_STATUS
|
||||
|
||||
#region 代理状态启动退出
|
||||
private bool _ProxyStatus;
|
||||
|
||||
public bool ProxyStatus
|
||||
{
|
||||
get { return _ProxyStatus; }
|
||||
set => this.RaiseAndSetIfChanged(ref _ProxyStatus, value);
|
||||
}
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 代理启动中
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public bool ProxyStarting { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool ProxyStatus { get; set; }
|
||||
|
||||
#endregion 代理状态启动退出
|
||||
|
||||
static bool IsProgramStartupRunProxy()
|
||||
{
|
||||
@@ -275,7 +317,8 @@ public sealed partial class ProxyService
|
||||
}
|
||||
else
|
||||
{
|
||||
ProxyStatus = true;
|
||||
//ProxyStatus = true;
|
||||
await StartOrStopProxyService(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,10 +343,30 @@ public sealed partial class ProxyService
|
||||
{
|
||||
new TrayMenuItem
|
||||
{
|
||||
Name = "启动/停止",
|
||||
Command = ReactiveCommand.Create(() =>
|
||||
Name = "启动",
|
||||
//IsEnabled = new Binding()
|
||||
//{
|
||||
// Source = ProxyService.Current,
|
||||
// Mode = BindingMode.OneWay,
|
||||
// Path = "!" + nameof(ProxyStatus),
|
||||
//},
|
||||
Command = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
ProxyStatus = !ProxyStatus;
|
||||
await StartOrStopProxyService(true);
|
||||
}),
|
||||
},
|
||||
new TrayMenuItem
|
||||
{
|
||||
Name = "停止",
|
||||
//IsEnabled = new Binding()
|
||||
//{
|
||||
// Source = ProxyService.Current,
|
||||
// Mode = BindingMode.OneWay,
|
||||
// Path = nameof(ProxyStatus),
|
||||
//},
|
||||
Command = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await StartOrStopProxyService(false);
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -632,7 +695,8 @@ public sealed partial class ProxyService
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion 脚本相关
|
||||
|
||||
public async void FixNetwork()
|
||||
{
|
||||
@@ -654,7 +718,6 @@ public sealed partial class ProxyService
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -15,12 +15,13 @@ public sealed partial class AcceleratorPageViewModel
|
||||
|
||||
public AcceleratorPageViewModel()
|
||||
{
|
||||
StartProxyCommand = ReactiveCommand.Create(() =>
|
||||
StartProxyCommand = ReactiveCommand.CreateFromTask(async _ =>
|
||||
{
|
||||
#if LINUX
|
||||
if (!EnvironmentCheck()) return;
|
||||
#endif
|
||||
ProxyService.Current.ProxyStatus = !ProxyService.Current.ProxyStatus;
|
||||
//ProxyService.Current.ProxyStatus = !ProxyService.Current.ProxyStatus;
|
||||
await ProxyService.Current.StartOrStopProxyService(!ProxyService.Current.ProxyStatus);
|
||||
});
|
||||
|
||||
RefreshCommand = ReactiveCommand.Create(async () =>
|
||||
@@ -58,6 +59,7 @@ public sealed partial class AcceleratorPageViewModel
|
||||
ResetHostsFileCommand = ReactiveCommand.CreateFromTask(hostsFileService.ResetFile);
|
||||
NetworkFixCommand = ReactiveCommand.Create(ProxyService.Current.FixNetwork);
|
||||
TrustCerCommand = ReactiveCommand.Create(TrustCer_OnClick);
|
||||
ShowCertificateCommand = ReactiveCommand.Create(ShowCertificate_OnClick);
|
||||
OpenCertificateDirCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
certificateManager.GetCerFilePathGeneratedWhenNoFileExists();
|
||||
@@ -125,4 +127,10 @@ public sealed partial class AcceleratorPageViewModel
|
||||
Toast.Show(ToastIcon.Error, Strings.CommunityFix_DeleteCertificate_Fail);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowCertificate_OnClick()
|
||||
{
|
||||
var certInfo = certificateManager.GetCertificateInfo();
|
||||
MessageBox.Show(certInfo, "证书信息");
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ public sealed partial class AcceleratorPageViewModel : TabItemViewModel
|
||||
|
||||
public ICommand? DeleteCertificateCommand { get; }
|
||||
|
||||
public ICommand? ShowCertificateCommand { get; }
|
||||
|
||||
public ICommand? OpenCertificateDirCommand { get; }
|
||||
|
||||
public ICommand? EditHostsFileCommand { get; }
|
||||
|
||||
@@ -93,13 +93,13 @@
|
||||
Grid.ColumnSpan="2"
|
||||
Stretch="UniformToFill">
|
||||
<spp:Image2
|
||||
DecodeWidth="600"
|
||||
DecodeWidth="300"
|
||||
FallbackSource="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/defaultBackground.png"
|
||||
Source="{Binding PicUrl}"
|
||||
Source="{Binding PicInfo.Small.Url}"
|
||||
Stretch="UniformToFill" />
|
||||
</Viewbox>
|
||||
|
||||
<spp:Image2
|
||||
<!--<spp:Image2
|
||||
Grid.RowSpan="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="10,0,50,0"
|
||||
@@ -107,7 +107,7 @@
|
||||
VerticalAlignment="Bottom"
|
||||
DecodeWidth="250"
|
||||
Source="{Binding LogoUrl}"
|
||||
Stretch="Uniform" />
|
||||
Stretch="Uniform" />-->
|
||||
|
||||
<Border
|
||||
Name="ShadowBox"
|
||||
@@ -180,7 +180,7 @@
|
||||
VerticalAlignment="Center"
|
||||
DecodeWidth="20"
|
||||
IsVisible="{Binding !$self.IsFailed}"
|
||||
Source="{Binding IconUrl}">
|
||||
Source="{Binding PicInfo.Icon.Url}">
|
||||
<spp:Image2.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
Classes="Scale"
|
||||
DecodeWidth="800"
|
||||
FallbackSource="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/defaultBackground.png"
|
||||
Source="{Binding PicUrl}" />
|
||||
Source="{Binding PicInfo.Large.Url}" />
|
||||
</Viewbox>
|
||||
|
||||
<spp:Image2
|
||||
@@ -46,7 +46,7 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
DecodeWidth="400"
|
||||
Source="{Binding LogoUrl}"
|
||||
Source="{Binding PicInfo.Logo.Url}"
|
||||
Stretch="Uniform" />
|
||||
|
||||
<Panel
|
||||
@@ -68,7 +68,7 @@
|
||||
VerticalAlignment="Center"
|
||||
DecodeWidth="36"
|
||||
IsVisible="{Binding !$self.IsFailed}"
|
||||
Source="{Binding IconUrl}">
|
||||
Source="{Binding PicInfo.Icon.Url}">
|
||||
<spp:Image2.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition
|
||||
@@ -163,15 +163,28 @@
|
||||
Foreground="White"
|
||||
Text="KB" />-->
|
||||
</Grid>
|
||||
<Button
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Width="150"
|
||||
Height="42"
|
||||
Classes="accent"
|
||||
Command="{Binding GameAcceleratorCommand, Source={x:Static s:GameAcceleratorService.Current}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="停止加速"
|
||||
FontSize="18" />
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Width="150"
|
||||
Height="42"
|
||||
Classes="accent"
|
||||
Command="{Binding GameLaunchCommand, Source={x:Static s:GameAcceleratorService.Current}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="启动游戏"
|
||||
FontSize="18"
|
||||
IsVisible="{Binding ShowStart}" />
|
||||
|
||||
<Button
|
||||
Width="150"
|
||||
Height="42"
|
||||
Command="{Binding GameAcceleratorCommand, Source={x:Static s:GameAcceleratorService.Current}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="停止加速"
|
||||
FontSize="18" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Panel>
|
||||
|
||||
@@ -297,6 +297,10 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding DeleteCertificateCommand}"
|
||||
Content="{Binding Path=Res.CommunityFix_DeleteCertificate, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding ShowCertificateCommand}"
|
||||
Content="查看证书信息" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding OpenCertificateDirCommand}"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:enum="using:BD.WTTS.Enums"
|
||||
xmlns:i="using:Avalonia.Xaml.Interactivity"
|
||||
xmlns:ia="using:Avalonia.Xaml.Interactions.Core"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -25,26 +26,25 @@
|
||||
</spp:PageBase.PreviewImage>
|
||||
<spp:PageBase.ActionContent>
|
||||
<StackPanel Orientation="Horizontal" Spacing="2">
|
||||
<Button
|
||||
<SplitButton
|
||||
Padding="8,4"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Command="{Binding StartProxyCommand}"
|
||||
DockPanel.Dock="Right"
|
||||
IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=!ProxyStatus, Mode=OneWay}"
|
||||
Theme="{StaticResource TransparentButton}">
|
||||
<!--<SplitButton.Flyout>
|
||||
<ui:FAMenuFlyout>
|
||||
<ui:MenuFlyoutSubItem ItemsSource="{Binding Source={x:Static ms:ProxySettings.ProxyModes}, Path=., Mode=OneWay}" Text="加速模式">
|
||||
IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=!ProxyStatus, Mode=OneWay}">
|
||||
<SplitButton.Flyout>
|
||||
<ui:FAMenuFlyout Placement="BottomEdgeAlignedRight">
|
||||
<!--<ui:MenuFlyoutSubItem ItemsSource="{Binding Source={x:Static ms:ProxySettings.ProxyModes}, Path=., Mode=OneWay}" Text="加速模式">
|
||||
<ui:MenuFlyoutSubItem.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ui:RadioMenuFlyoutItem GroupName="ProxyMode" Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ui:MenuFlyoutSubItem.ItemTemplate>
|
||||
</ui:MenuFlyoutSubItem>
|
||||
<ui:ToggleMenuFlyoutItem Text="启用脚本" />
|
||||
</ui:MenuFlyoutSubItem>-->
|
||||
<ui:ToggleMenuFlyoutItem IsChecked="{Binding Source={x:Static s:ProxyService.Current}, Path=IsEnableScript, Mode=TwoWay}" Text="启用脚本" />
|
||||
</ui:FAMenuFlyout>
|
||||
</SplitButton.Flyout>-->
|
||||
</SplitButton.Flyout>
|
||||
<Panel>
|
||||
<Viewbox
|
||||
Width="16"
|
||||
@@ -52,13 +52,34 @@
|
||||
HorizontalAlignment="Left">
|
||||
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource SpeedHigh}" />
|
||||
</Viewbox>
|
||||
<TextBlock
|
||||
<ContentControl
|
||||
Margin="28,4,0,4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<ContentControl.Content>
|
||||
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
Path="ProxyStarting"
|
||||
Source="{x:Static s:ProxyService.Current}" />
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
Path="Res.CommunityFix_Starting"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
Path="Res.CommunityFix_StartProxy"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
</MultiBinding>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
<!--<TextBlock
|
||||
Margin="28,4,0,4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static spp:Strings.CommunityFix_StartProxy}" />
|
||||
Text="{x:Static spp:Strings.CommunityFix_StartProxy}" />-->
|
||||
</Panel>
|
||||
</Button>
|
||||
</SplitButton>
|
||||
|
||||
<Button
|
||||
Padding="8,4"
|
||||
@@ -291,6 +312,10 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding DeleteCertificateCommand}"
|
||||
Content="{x:Static spp:Strings.CommunityFix_DeleteCertificate}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding ShowCertificateCommand}"
|
||||
Content="查看证书信息" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding OpenCertificateDirCommand}"
|
||||
@@ -353,10 +378,52 @@
|
||||
VerticalAlignment="Top"
|
||||
ItemContainerTheme="{StaticResource TabItemStyle}"
|
||||
Theme="{StaticResource TabStripStyle}">
|
||||
<TabStripItem Content="平台加速 (免费)" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=!ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="平台加速" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=!ProxyStatus, Mode=OneWay}">
|
||||
<WrapPanel>
|
||||
<TextBlock Text="平台加速" />
|
||||
<Border
|
||||
Margin="5,0,0,0"
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#48C6EF" />
|
||||
<GradientStop Offset="1.0" Color="#6F86D6" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
Text="免费" />
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
</TabStripItem>
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_FlowStatistics}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Name="GameAccTab" Content="游戏加速" />
|
||||
<TabStripItem Name="GameAccTab">
|
||||
<WrapPanel>
|
||||
<TextBlock Text="游戏加速" />
|
||||
<Border
|
||||
Margin="5,0,0,0"
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#EEAD92" />
|
||||
<GradientStop Offset="1.0" Color="#6018DC" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
Text="收费" />
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
</TabStripItem>
|
||||
</TabStrip>
|
||||
<Carousel
|
||||
Grid.Row="1"
|
||||
@@ -404,7 +471,25 @@
|
||||
HorizontalAlignment="Left"
|
||||
DockPanel.Dock="Left"
|
||||
IsChecked="{Binding ThreeStateEnable, Mode=TwoWay}">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
|
||||
<Border
|
||||
Padding="5,0"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ProxyType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:ProxyType.ServerAccelerate}}"
|
||||
ToolTip.Tip="Beta 测试加速项,不稳定且随时可能调整">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#43cbff" />
|
||||
<GradientStop Offset="1.0" Color="#9708cc" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Text="Beta" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
@@ -502,13 +587,13 @@
|
||||
IsVisible="{Binding !!Games.Count}"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<ItemsRepeater ItemsSource="{Binding Games}" VerticalCacheLength="50">
|
||||
<ItemsRepeater ItemsSource="{Binding Games}" VerticalCacheLength="30">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout
|
||||
ItemsJustification="Start"
|
||||
ItemsStretch="Uniform"
|
||||
MinColumnSpacing="10"
|
||||
MinItemHeight="180"
|
||||
MinItemHeight="240"
|
||||
MinItemWidth="200"
|
||||
MinRowSpacing="10" />
|
||||
</ItemsRepeater.Layout>
|
||||
@@ -553,53 +638,83 @@
|
||||
</TabItem>-->
|
||||
</Carousel>
|
||||
|
||||
<AutoCompleteBox
|
||||
Name="SearchGameBox"
|
||||
<StackPanel
|
||||
Grid.ColumnSpan="2"
|
||||
Width="240"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Classes="Search"
|
||||
FilterMode="ContainsOrdinal"
|
||||
IsVisible="{Binding #GameAccTab.IsSelected}"
|
||||
ItemsSource="{Binding Source={x:Static s:GameAcceleratorService.Current}, Path=AllGames}"
|
||||
MaxDropDownHeight="300"
|
||||
ValueMemberBinding="{Binding Name, x:DataType=spp:XunYouGame}"
|
||||
Watermark="{x:Static spp:Strings.GameAccelerator_SearchGame}">
|
||||
<AutoCompleteBox.ItemTemplate>
|
||||
<DataTemplate DataType="spp:XunYouGame">
|
||||
<WrapPanel>
|
||||
<spp:Image2
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="0,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
DecodeWidth="20"
|
||||
Source="{Binding IconUrl}">
|
||||
<spp:Image2.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition
|
||||
Easing="0,0 0,1"
|
||||
Property="Width"
|
||||
Duration="00:00:00.25" />
|
||||
<DoubleTransition
|
||||
Easing="0,0 0,1"
|
||||
Property="Height"
|
||||
Duration="00:00:00.25" />
|
||||
</Transitions>
|
||||
</spp:Image2.Transitions>
|
||||
</spp:Image2>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
MaxLines="2"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</WrapPanel>
|
||||
</DataTemplate>
|
||||
</AutoCompleteBox.ItemTemplate>
|
||||
</AutoCompleteBox>
|
||||
Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<Border
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsVisible="{Binding Source={x:Static s:GameAcceleratorService.Current}, Path=VipEndTimeString, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#453a94" />
|
||||
<GradientStop Offset="1.0" Color="#f43b47" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="White"
|
||||
Text="{Binding Source={x:Static s:GameAcceleratorService.Current}, Path=VipEndTimeString}" />
|
||||
</Border>
|
||||
<Button
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
CommandParameter="https://steampp.net/customer/service"
|
||||
Cursor="Hand"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
ToolTip.Tip="联系客服">
|
||||
<ui:FontIcon FontSize="22" Glyph="" />
|
||||
</Button>
|
||||
<AutoCompleteBox
|
||||
Name="SearchGameBox"
|
||||
Width="240"
|
||||
VerticalAlignment="Center"
|
||||
Classes="Search"
|
||||
FilterMode="ContainsOrdinal"
|
||||
ItemsSource="{Binding Source={x:Static s:GameAcceleratorService.Current}, Path=AllGames}"
|
||||
MaxDropDownHeight="300"
|
||||
ValueMemberBinding="{Binding Name, x:DataType=spp:XunYouGame}"
|
||||
Watermark="{x:Static spp:Strings.GameAccelerator_SearchGame}">
|
||||
<AutoCompleteBox.ItemTemplate>
|
||||
<DataTemplate DataType="spp:XunYouGame">
|
||||
<WrapPanel>
|
||||
<spp:Image2
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="0,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
DecodeWidth="20"
|
||||
Source="{Binding IconUrl}">
|
||||
<spp:Image2.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition
|
||||
Easing="0,0 0,1"
|
||||
Property="Width"
|
||||
Duration="00:00:00.25" />
|
||||
<DoubleTransition
|
||||
Easing="0,0 0,1"
|
||||
Property="Height"
|
||||
Duration="00:00:00.25" />
|
||||
</Transitions>
|
||||
</spp:Image2.Transitions>
|
||||
</spp:Image2>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
MaxLines="2"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</WrapPanel>
|
||||
</DataTemplate>
|
||||
</AutoCompleteBox.ItemTemplate>
|
||||
</AutoCompleteBox>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<StackPanel
|
||||
Grid.ColumnSpan="2"
|
||||
|
||||
@@ -30,7 +30,19 @@
|
||||
</On>
|
||||
</OnFormFactor>
|
||||
</TabStrip.ItemsPanel>
|
||||
<TabStripItem Content="{Binding Path=Res.CommunityFix, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<TabStripItem>
|
||||
<WrapPanel>
|
||||
<Border
|
||||
Margin="-5,0,3,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2">
|
||||
<ui:ImageIcon
|
||||
Width="24"
|
||||
Height="24"
|
||||
Source="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/accelerator.ico" />
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Res.CommunityFix, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</WrapPanel>
|
||||
<TabStripItem.Tag>
|
||||
<x:Type TypeName="spp:AcceleratorPage2" />
|
||||
</TabStripItem.Tag>
|
||||
@@ -40,7 +52,19 @@
|
||||
<x:Type TypeName="spp:AcceleratorPage" />
|
||||
</TabStripItem.Tag>
|
||||
</TabStripItem>-->
|
||||
<TabStripItem Content="{Binding Path=Res.ScriptConfig, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<TabStripItem>
|
||||
<WrapPanel>
|
||||
<Border
|
||||
Margin="-5,0,3,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2">
|
||||
<ui:ImageIcon
|
||||
Width="24"
|
||||
Height="24"
|
||||
Source="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/script.ico" />
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Res.ScriptConfig, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</WrapPanel>
|
||||
<TabStripItem.Tag>
|
||||
<x:Type TypeName="spp:ScriptPage" />
|
||||
</TabStripItem.Tag>
|
||||
|
||||
@@ -54,13 +54,6 @@
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_ProxyOnlyOnScript, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.OnlyEnableProxyScript}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_EnableHttpProxyToHttps, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
@@ -95,6 +88,13 @@
|
||||
<Label Content="自定义根证书" ></Label>
|
||||
<Button Content="选择证书"/>
|
||||
</DockPanel>-->
|
||||
|
||||
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速完全不生效,请确认是仅需要脚本功能后再启用">
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_ProxyOnlyOnScript, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.OnlyEnableProxyScript}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
<DataTemplate DataType="spp:ScriptDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxHeight="200"
|
||||
Description="{Binding Describe}"
|
||||
ImageMargin="30"
|
||||
ImageStretch="None">
|
||||
@@ -159,31 +158,31 @@
|
||||
Margin="0,0,3,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<Button
|
||||
<Button
|
||||
Command="{Binding $parent[spp:PageBase].((spp:ScriptPageViewModel)DataContext).DownloadScriptItemCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding IsUpdate}"
|
||||
Theme="{DynamicResource TransparentButton}">
|
||||
<ToolTip.Tip>
|
||||
<MultiBinding StringFormat="{}{0} v{1}">
|
||||
<CompiledBinding
|
||||
<ToolTip.Tip>
|
||||
<MultiBinding StringFormat="{}{0} v{1}">
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
Path="Res.Script_Update"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
<CompiledBinding Mode="OneWay" Path="NewVersion" />
|
||||
</MultiBinding>
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal" Spacing="3">
|
||||
<Viewbox Width="16" Height="16">
|
||||
<ui:SymbolIcon Symbol="Download" />
|
||||
</Viewbox>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding NewVersion}" />
|
||||
<Ellipse
|
||||
<CompiledBinding Mode="OneWay" Path="NewVersion" />
|
||||
</MultiBinding>
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal" Spacing="3">
|
||||
<Viewbox Width="16" Height="16">
|
||||
<ui:SymbolIcon Symbol="Download" />
|
||||
</Viewbox>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding NewVersion}" />
|
||||
<Ellipse
|
||||
Width="6"
|
||||
Height="6"
|
||||
Fill="#dd5a5a" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button
|
||||
Command="{Binding $parent[spp:PageBase].((spp:ScriptPageViewModel)DataContext).EditScriptItemCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Mobius.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// 调用加速器默认启动当前加速的游戏返回值
|
||||
/// <para>23.接口 xunyou_start_game</para>
|
||||
/// </summary>
|
||||
public enum XunYouStartGameCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送失败
|
||||
/// </summary>
|
||||
失败 = -1,
|
||||
|
||||
/// <summary>
|
||||
/// 发送命令到客户端成功
|
||||
/// </summary>
|
||||
成功 = 0,
|
||||
}
|
||||
@@ -23,4 +23,6 @@ public enum XunYouState
|
||||
停止加速中 = 403,
|
||||
加速已断开 = 404,
|
||||
加速失败 = 405,
|
||||
启动游戏失败 = 406,
|
||||
启动游戏成功 = 407,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Mobius;
|
||||
|
||||
partial class XunYouSDK // 1.接口 xunyou_install
|
||||
partial class XunYouSDK // 21.接口 xunyou_install_ex
|
||||
{
|
||||
/// <summary>
|
||||
/// 迅游下载进度回调函数委托
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ partial class XunYouSDK // 10.接口 xunyou_accel_state_ex
|
||||
/// <summary>
|
||||
/// 获取当前加速器的加速状态。
|
||||
/// </summary>
|
||||
/// <param name="id">合作 id,由迅游给出明确值</param>
|
||||
/// <param name="gameid">当前加速的游戏 id。</param>
|
||||
/// <param name="areaid">当前加速的游戏区 id。</param>
|
||||
/// <param name="serverid">当前加速游戏的服 id,没有则为 0。</param>
|
||||
|
||||
@@ -6,7 +6,7 @@ partial class XunYouSDK // 12.接口 xunyou_start_ex2
|
||||
|
||||
/// <summary>
|
||||
/// <list type="table">
|
||||
/// <item>此接口支持自动登录,对指定启动游戏,并对传入的游戏分区进行加速。</item>
|
||||
/// <item>此接口支持自动登录,对指定启动游戏,并对传入的游戏分区进行加速(OpenId)。</item>
|
||||
/// <item>如果没有安装迅游,则先下载并安装,然后启动迅游或启动加速。</item>
|
||||
/// <item>如果 xunyou_callback 为空,则该接口走同步的方式;</item>
|
||||
/// <item>如果 xunyou_callback 不为空,则该接口走异步的方式。</item>
|
||||
|
||||
+16
-15
@@ -2,22 +2,22 @@ namespace Mobius;
|
||||
|
||||
partial class XunYouSDK // 15.接口 xunyou_get_gameinfo
|
||||
{
|
||||
static XunYouGameInfo? SortGameInfo(XunYouGameInfo? gameInfo)
|
||||
{
|
||||
if (gameInfo?.Areas != null)
|
||||
{
|
||||
foreach (var item in gameInfo.Areas)
|
||||
{
|
||||
if (item.Servers == null || item.Servers.Count == 0)
|
||||
continue;
|
||||
item.Servers = item.Servers.OrderBy(x => x.Id).ToList();
|
||||
}
|
||||
//static XunYouGameInfo? SortGameInfo(XunYouGameInfo? gameInfo)
|
||||
//{
|
||||
// if (gameInfo?.Areas != null)
|
||||
// {
|
||||
// foreach (var item in gameInfo.Areas)
|
||||
// {
|
||||
// if (item.Servers == null || item.Servers.Count == 0)
|
||||
// continue;
|
||||
// item.Servers = item.Servers.OrderBy(x => x.Id).ToList();
|
||||
// }
|
||||
|
||||
gameInfo.Areas = gameInfo.Areas.OrderBy(x => x.Id).ToList();
|
||||
}
|
||||
// gameInfo.Areas = gameInfo.Areas.OrderBy(x => x.Id).ToList();
|
||||
// }
|
||||
|
||||
return gameInfo;
|
||||
}
|
||||
// return gameInfo;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对应游戏 Id 的游戏信息(平台信息,区服信息)
|
||||
@@ -43,7 +43,8 @@ partial class XunYouSDK // 15.接口 xunyou_get_gameinfo
|
||||
//return Encoding.UTF8.GetString(gamelist);
|
||||
var jObj = SystemTextJsonSerializer.Deserialize(gamelist,
|
||||
SystemTextJsonSerializerContext_XunYouSDK.Default.xunyou_get_gameinfo_resultXunYouGameInfo);
|
||||
return SortGameInfo(jObj?.GameInfo);
|
||||
//return SortGameInfo(jObj?.GameInfo);
|
||||
return jObj?.GameInfo; // 由 SDK 返回排序后的数据
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
namespace Mobius;
|
||||
|
||||
partial class XunYouSDK // 22.接口 xunyou_start_ex3
|
||||
{
|
||||
/// <summary>
|
||||
/// <list type="table">
|
||||
/// <item>此接口支持自动登录,对指定启动游戏,并对传入的游戏分区进行加速(Jwt)。</item>
|
||||
/// <item>如果没有安装迅游,则先下载并安装,然后启动迅游或启动加速。</item>
|
||||
/// <item>如果 xunyou_callback 为空,则该接口走同步的方式;</item>
|
||||
/// <item>如果 xunyou_callback 不为空,则该接口走异步的方式。</item>
|
||||
/// <item>异步启动是直接启动线程操作,返回值在回调函数里面调用;</item>
|
||||
/// <item>同步是直接返回相关结果。异步回调的方式会返回一个系统默认值 1。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="jwt">登录 token,由调用方提供</param>
|
||||
/// <param name="nickname">账号昵称</param>
|
||||
/// <param name="gameid">游戏 id,由迅游给出明确值,或传 0,如果传 0,表示只启动迅游,不自动加速。</param>
|
||||
/// <param name="area">游戏分区 id ,由游戏给出区服名和 id 对照表,或传 0,如果传 0,表示只启动迅游,不自动加速。</param>
|
||||
/// <param name="server">游戏服 id</param>
|
||||
/// <param name="callback"></param>
|
||||
/// <param name="ptr"></param>
|
||||
/// <param name="installPath">迅游加速器安装路径,不指定,则默认安装在 xunyoucall.dll 所在目录</param>
|
||||
/// <param name="areaName">游戏区名</param>
|
||||
/// <param name="svrName">游戏服名</param>
|
||||
/// <returns></returns>
|
||||
public static int StartEx3(
|
||||
string jwt,
|
||||
string nickname,
|
||||
int gameid = 0,
|
||||
int area = 0,
|
||||
int server = 0,
|
||||
XunYouDownLoadCallback? callback = null,
|
||||
nint ptr = default,
|
||||
string? installPath = default,
|
||||
string? areaName = default,
|
||||
string? svrName = default)
|
||||
{
|
||||
int result;
|
||||
unsafe
|
||||
{
|
||||
var xunyou_callback = callback is null ? null :
|
||||
(delegate* unmanaged[Stdcall]<int, nint, int>)Marshal.GetFunctionPointerForDelegate(
|
||||
globalXunYouDownLoadCallback_xunyou_start_ex2 = callback);
|
||||
int install_path_len = installPath?.Length ?? 0;
|
||||
int area_name_len = areaName?.Length ?? 0;
|
||||
int svr_name_len = svrName?.Length ?? 0;
|
||||
int jwt_len = jwt?.Length ?? 0;
|
||||
int nickname_len = jwt?.Length ?? 0;
|
||||
|
||||
result = xunyou_start_ex2(appId, jwt, jwt_len,
|
||||
userType, channel_no, channel_no.Length,
|
||||
nickname, nickname_len, XunYouShowCommands.SW_HIDE,
|
||||
gameid,
|
||||
area, server, xunyou_callback, ptr,
|
||||
installPath, install_path_len, areaName,
|
||||
area_name_len, svrName, svr_name_len);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[LibraryImport(libraryName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
|
||||
private static unsafe partial int xunyou_start_ex3(
|
||||
int id,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string? jwt,
|
||||
int jwt_len,
|
||||
int user_type,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string? channel_no,
|
||||
int channel_no_len,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string? nickname,
|
||||
int nickname_len,
|
||||
XunYouShowCommands show = XunYouShowCommands.SW_HIDE,
|
||||
int gameid = 0,
|
||||
int area = 0,
|
||||
int server = 0,
|
||||
delegate* unmanaged[Stdcall]<int, nint, int> callback = null,
|
||||
nint ptr = default,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string? install_path = default,
|
||||
int install_path_len = 0,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string? area_name = default,
|
||||
int area_name_len = 0,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string? svr_name = default,
|
||||
int svr_name_len = 0);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Mobius;
|
||||
|
||||
partial class XunYouSDK // 23.接口 xunyou_start_game
|
||||
{
|
||||
/// <summary>
|
||||
/// 调用加速器默认启动当前加速的游戏
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static XunYouStartGameCode StartGame()
|
||||
{
|
||||
int result = xunyou_start_game();
|
||||
return (XunYouStartGameCode)result;
|
||||
}
|
||||
|
||||
[LibraryImport(libraryName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
|
||||
private static unsafe partial int xunyou_start_game();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace Mobius;
|
||||
|
||||
partial class XunYouSDK // 24.接口 xunyou_get_picinfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 同步获取对应游戏 id 的图片信息
|
||||
/// </summary>
|
||||
/// <param name="game_id">迅游对应的游戏 id</param>
|
||||
/// <returns></returns>
|
||||
public static XunYouPicInfo? GetPicInfo(int game_id)
|
||||
{
|
||||
try
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
int picinfo_size = 0;
|
||||
var result = xunyou_get_picinfo(game_id, default, ref picinfo_size);
|
||||
if (result == 0)
|
||||
{
|
||||
Span<byte> span = new byte[picinfo_size];
|
||||
fixed (byte* span_intptr = span)
|
||||
{
|
||||
result = xunyou_get_picinfo(game_id, span_intptr, ref picinfo_size);
|
||||
if (result == 0) // 获取成功
|
||||
{
|
||||
var jObj = SystemTextJsonSerializer.Deserialize(span,
|
||||
SystemTextJsonSerializerContext_XunYouSDK.Default.XunYouPicInfo);
|
||||
return jObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(XunYouSDK), ex, "GetPicInfo fail.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步获取对应游戏 id 的图片信息
|
||||
/// </summary>
|
||||
/// <param name="game_id">迅游对应的游戏 id</param>
|
||||
/// <param name="picinfo">游戏 id 对应的游戏信息,为空则 picinfo_size 会返回所需的空间大小</param>
|
||||
/// <param name="picinfo_size">会返回数据的大小</param>
|
||||
/// <returns></returns>
|
||||
[LibraryImport(libraryName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
|
||||
private static unsafe partial int xunyou_get_picinfo(int game_id, byte* picinfo,
|
||||
ref int picinfo_size);
|
||||
}
|
||||
+3
@@ -6,6 +6,9 @@ namespace Mobius.Models;
|
||||
[JsonSerializable(typeof(xunyou_get_gamelist_result<XunYouSteamGame>))]
|
||||
[JsonSerializable(typeof(xunyou_get_gamelist_result<XunYouGame>))]
|
||||
[JsonSerializable(typeof(xunyou_get_gameinfo_result<XunYouGameInfo>))]
|
||||
[JsonSerializable(typeof(XunYouPicInfo))]
|
||||
[JsonSerializable(typeof(XunYouVipEndTimeRequest))]
|
||||
[JsonSerializable(typeof(XunYouVipEndTimeResponse))]
|
||||
[JsonSourceGenerationOptions(
|
||||
AllowTrailingCommas = true)]
|
||||
sealed partial class SystemTextJsonSerializerContext_XunYouSDK : SystemTextJsonSerializerContext
|
||||
|
||||
@@ -16,6 +16,12 @@ public sealed record class XunYouGameInfo
|
||||
[JsonPropertyName("game_operator_id")]
|
||||
public XunYouGameOperatorId OperatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示启动游戏按钮
|
||||
/// </summary>
|
||||
[JsonPropertyName("show_start")]
|
||||
public bool ShowStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 迅游游戏区服信息
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Mobius.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 同步获取对应游戏 id 的图片信息
|
||||
/// <para>24.接口 xunyou_get_picinfo</para>
|
||||
/// </summary>
|
||||
public sealed record class XunYouPicInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public XunYouPicInfoUrl? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 大图
|
||||
/// </summary>
|
||||
[JsonPropertyName("large")]
|
||||
public XunYouPicInfoUrl? Large { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo
|
||||
/// </summary>
|
||||
[JsonPropertyName("logo")]
|
||||
public XunYouPicInfoUrl? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 小图
|
||||
/// </summary>
|
||||
[JsonPropertyName("small")]
|
||||
public XunYouPicInfoUrl? Small { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Mobius.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 同步获取对应游戏 id 的图片信息 Url
|
||||
/// <para>24.接口 xunyou_get_picinfo</para>
|
||||
/// </summary>
|
||||
public sealed record class XunYouPicInfoUrl
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片资源的 crc 哈希值
|
||||
/// </summary>
|
||||
[JsonPropertyName("crc")]
|
||||
public string? Crc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片资源的地址
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string? Url { get; set; }
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
using WinAuth;
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 添加 Steam 账号手机号页面 VM
|
||||
/// </summary>
|
||||
public class AddSteamAccountPhoneNumberPageViewModel : WindowViewModel
|
||||
{
|
||||
#region 步骤 Index 定义
|
||||
|
||||
const int TAB_INDEX_BINDING = 0;
|
||||
|
||||
const int TAB_INDEX_CONFIRMATION_Email = 1;
|
||||
|
||||
const int TAB_INDEX_CONFIRMATION_SMS = 2;
|
||||
|
||||
#endregion 步骤 Index 定义
|
||||
|
||||
/// <summary>
|
||||
/// 手机号文本
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public string? PhoneNumberText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证码文本
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public string? CodeText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 步骤 Index
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public int SelectIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示加载中
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
private readonly SteamAuthenticator.EnrollState _enrollState;
|
||||
private readonly SteamAuthenticator _steamAuthenticator;
|
||||
|
||||
public AddSteamAccountPhoneNumberPageViewModel(
|
||||
SteamAuthenticator.EnrollState enrollState,
|
||||
SteamAuthenticator steamAuthenticator
|
||||
)
|
||||
{
|
||||
SelectIndex = TAB_INDEX_BINDING;
|
||||
|
||||
_steamAuthenticator = steamAuthenticator;
|
||||
_enrollState = enrollState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始绑定手机号
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task StartBindingAsync()
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Warning_DoNotOperateFrequently);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(PhoneNumberText))
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Warning_PleaseEnterTel);
|
||||
return;
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
string? msg = await _steamAuthenticator.AddPhoneNumberAsync(_enrollState, PhoneNumberText);
|
||||
|
||||
Toast.Show(ToastIcon.Info, msg ?? string.Empty);
|
||||
|
||||
if (_enrollState.RequiresEmailConfirmPhone)
|
||||
{
|
||||
SelectIndex = TAB_INDEX_CONFIRMATION_Email;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectIndex = TAB_INDEX_CONFIRMATION_SMS;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认邮箱链接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task ConfirmBindingEmailAsync()
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Warning_DoNotOperateFrequently);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(PhoneNumberText))
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Warning_PleaseEnterTel);
|
||||
return;
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
var reslut = await _steamAuthenticator.AddPhoneNumberAsync(_enrollState, PhoneNumberText!);
|
||||
|
||||
if (_enrollState.RequiresEmailConfirmPhone == false && reslut == null)
|
||||
{
|
||||
SelectIndex = TAB_INDEX_CONFIRMATION_SMS;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验收短信验证码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task VerifyAccountPhoneWithCodeAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(PhoneNumberText))
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Warning_PleaseEnterTel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(CodeText))
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Error_PleaseEnterTelCode);
|
||||
return;
|
||||
}
|
||||
|
||||
var verified = await _steamAuthenticator
|
||||
.VerifyPhoneNumberAsync(PhoneNumberText!, CodeText!, _enrollState.AccessToken!);
|
||||
|
||||
if (verified)
|
||||
{
|
||||
Close?.Invoke(verified);
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
-16
@@ -1,16 +1,22 @@
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
using BD.SteamClient.Models;
|
||||
using BD.SteamClient.Services;
|
||||
using BD.WTTS.UI.Views.Controls;
|
||||
using WinAuth;
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
#if !ANDROID
|
||||
using ToastLength = BD.Common.Enums.ToastLength;
|
||||
|
||||
#endif
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
{
|
||||
const int TAB_INDEX_LOGIN = 0;
|
||||
const int TAB_INDEX_LOGINCONFIRM = 1;
|
||||
const int TAB_INDEX_VERIFYCODE = 2;
|
||||
const int TAB_INDEX_DONE = 3;
|
||||
|
||||
public static string Name => AppResources.Auth_SteamLoginImport;
|
||||
|
||||
readonly ISteamAccountService _steamAccountService = Ioc.Get<ISteamAccountService>();
|
||||
@@ -94,7 +100,7 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
{
|
||||
if (string.IsNullOrEmpty(PhoneCodeText))
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, AppResources.Error_PleaseEnterTelCode);
|
||||
Toast.Show(ToastIcon.Error, AppResources.Error_PleaseEnterCode);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -115,7 +121,7 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
Created = DateTimeOffset.Now,
|
||||
};
|
||||
await AuthenticatorHelper.SaveAuthenticator(iADTO);
|
||||
SelectIndex = 4;
|
||||
SelectIndex = TAB_INDEX_DONE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,15 +142,23 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
if (_enrollState.NoPhoneNumber == true)
|
||||
{
|
||||
_enrollState.Error = null;
|
||||
SelectIndex = 2;
|
||||
//SelectIndex = 2;
|
||||
// 正常情况下不会走到这里,因为已经不需要绑定手机的步骤了
|
||||
return;
|
||||
}
|
||||
|
||||
//导入最后一步,需要账号绑定的手机验证码确认
|
||||
// 导入最后一步,需要账号绑定的手机验证码确认
|
||||
// 2024-04-23 Steam绑定令牌不强制手机绑定 可以输入 邮箱者手机验证码
|
||||
if (_enrollState.RequiresActivation == true)
|
||||
{
|
||||
// 查询账号手机号绑定状态
|
||||
// IsVerifyAccountPhone = await _steamAccountService.CheckAccountPhoneStatus(_enrollState.AccessToken!) ?? false;
|
||||
|
||||
await CheckAccountPhoneStatus();
|
||||
|
||||
_enrollState.Error = null;
|
||||
RevocationCodeText = _enrollState.RevocationCode;
|
||||
SelectIndex = 3;
|
||||
SelectIndex = TAB_INDEX_VERIFYCODE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +175,7 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
//Toast.Show(ToastIcon.Error, Strings.LocalAuth_SteamUser_Requires2FA);
|
||||
//Reset();
|
||||
Requires2FA = _steamLoginState.Requires2FA;
|
||||
SelectIndex = 1;
|
||||
SelectIndex = TAB_INDEX_LOGINCONFIRM;
|
||||
return false;
|
||||
}
|
||||
//需要邮箱验证码
|
||||
@@ -170,7 +184,7 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
EmailDomainText = string.IsNullOrEmpty(_steamLoginState.EmailDomain) == false
|
||||
? $"******@{_steamLoginState.EmailDomain}"
|
||||
: string.Empty;
|
||||
SelectIndex = 1;
|
||||
SelectIndex = TAB_INDEX_LOGINCONFIRM;
|
||||
return false;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_steamLoginState.Message))
|
||||
@@ -189,6 +203,12 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
return !string.IsNullOrEmpty(_steamLoginState.AccessToken);
|
||||
}
|
||||
|
||||
async ValueTask CheckAccountPhoneStatus()
|
||||
{
|
||||
// 查询账号手机号绑定状态
|
||||
IsVerifyAccountPhone = await _steamAccountService.CheckAccountPhoneStatus(_enrollState.AccessToken!) ?? false;
|
||||
}
|
||||
|
||||
public async Task LoginSteamImport()
|
||||
{
|
||||
if (IsLoading)
|
||||
@@ -200,7 +220,7 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
|
||||
try
|
||||
{
|
||||
if (SelectIndex == 0)
|
||||
if (SelectIndex == TAB_INDEX_LOGIN)
|
||||
{
|
||||
await LoginSteamFirstAsync();
|
||||
}
|
||||
@@ -224,22 +244,63 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
//已有令牌情况下执行替换令牌逻辑
|
||||
if (_enrollState.ReplaceAuth)
|
||||
{
|
||||
if (SelectIndex == 1 && await steamAuthenticator.RemoveAuthenticatorViaChallengeStartSync(_enrollState.AccessToken!))
|
||||
if (SelectIndex == TAB_INDEX_LOGINCONFIRM && await steamAuthenticator.RemoveAuthenticatorViaChallengeStartSync(_enrollState.AccessToken!))
|
||||
{
|
||||
SelectIndex = 3;
|
||||
await CheckAccountPhoneStatus();
|
||||
|
||||
SelectIndex = TAB_INDEX_VERIFYCODE;
|
||||
|
||||
if (!IsVerifyAccountPhone)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "只绑定邮箱的情况下无法直接替换令牌");
|
||||
|
||||
// 弹出绑定手机号流程
|
||||
var added = await IWindowManager.Instance.ShowTaskDialogAsync(
|
||||
viewModel: new AddSteamAccountPhoneNumberPageViewModel(_enrollState, steamAuthenticator),
|
||||
pageContent: new AddSteamAccountPhoneNumberPage(),
|
||||
isCancelButton: false,
|
||||
isOkButton: false,
|
||||
isRetryButton: false
|
||||
);
|
||||
|
||||
// 查询账号手机号绑定状态
|
||||
await CheckAccountPhoneStatus();
|
||||
|
||||
// 如果通过弹出的绑定手机号绑定成功 开始执行替换令牌
|
||||
if (added && IsVerifyAccountPhone)
|
||||
{
|
||||
/*
|
||||
* 绑定手机号会发送验证码
|
||||
* 执行开始替换令牌操作也会发送验证码
|
||||
* 如果两个操作间隔太短会导致 Steam 不发送验证码
|
||||
*/
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
|
||||
await steamAuthenticator.RemoveAuthenticatorViaChallengeStartSync(_enrollState.AccessToken!);
|
||||
}
|
||||
// 取消了绑定手机号流程 无法执行替换令牌操作
|
||||
else
|
||||
{
|
||||
SelectIndex = TAB_INDEX_LOGIN;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (SelectIndex == 3)
|
||||
else if (SelectIndex == TAB_INDEX_VERIFYCODE)
|
||||
{
|
||||
if (string.IsNullOrEmpty(PhoneCodeText))
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, Strings.Error_PleaseEnterTelCode);
|
||||
Toast.Show(ToastIcon.Error, Strings.Error_PleaseEnterCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await steamAuthenticator.RemoveAuthenticatorViaChallengeContinueSync(PhoneCodeText, _enrollState.AccessToken!))
|
||||
{
|
||||
await SaveAuthenticatorAsync();
|
||||
RevocationCodeText = steamAuthenticator.RecoveryCode;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -319,4 +380,4 @@ public sealed partial class SteamLoginImportPageViewModel : ViewModelBase
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -1,9 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
public sealed partial class SteamLoginImportPageViewModel
|
||||
@@ -67,4 +61,10 @@ public sealed partial class SteamLoginImportPageViewModel
|
||||
|
||||
[Reactive]
|
||||
public string? PhoneNumberText { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否已验证账号的手机号
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
public bool IsVerifyAccountPhone { get; set; }
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
d:DesignWidth="400"
|
||||
d:DesignHeight="250"
|
||||
x:DataType="spp:AddSteamAccountPhoneNumberPageViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="BD.WTTS.UI.Views.Controls.AddSteamAccountPhoneNumberPage"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
|
||||
<spp:Stepper SelectedIndex="{Binding SelectIndex}">
|
||||
<spp:StepperItem Content="{Binding Path=Res.Steam_BindTel, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.Steam_ConfirmEmailLink, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.TelCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</spp:Stepper>
|
||||
|
||||
<spp:ContentLoader
|
||||
Grid.Row="1"
|
||||
MinWidth="400"
|
||||
MinHeight="250"
|
||||
IsLoading="{Binding IsLoading}"
|
||||
IsShowNoResultText="False">
|
||||
<Carousel IsVisible="{Binding !IsLoading}" SelectedIndex="{Binding SelectIndex}">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.User_Phone, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PhoneNumberText"
|
||||
MaxWidth="300"
|
||||
Text="{Binding PhoneNumberText}"
|
||||
Watermark="+8601234567890" />
|
||||
</DockPanel>
|
||||
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding StartBindingAsync}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding ConfirmBindingEmailAsync}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.Error_PleaseEnterTelCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="CodeText"
|
||||
MaxWidth="300"
|
||||
Text="{Binding CodeText}" />
|
||||
</DockPanel>
|
||||
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding VerifyAccountPhoneWithCodeAsync}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Carousel>
|
||||
</spp:ContentLoader>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public partial class AddSteamAccountPhoneNumberPage : UserControl
|
||||
{
|
||||
public AddSteamAccountPhoneNumberPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
Severity="Warning">
|
||||
<spp:Snackbar.ActionButton>
|
||||
<StackPanel>
|
||||
<ui:HyperlinkButton Content="[Rooted Android] 3.x 新版导出令牌的另一种思路" NavigateUri="https://github.com/BeyondDimension/SteamTools/issues/2276" />
|
||||
<ui:HyperlinkButton Content="[Rooted Android] 3.x 新版导出令牌的另另一种思路" NavigateUri="https://github.com/BeyondDimension/SteamTools/issues/2598" />
|
||||
<HyperlinkButton Content="[Rooted Android] 3.x 新版导出令牌的另一种思路" NavigateUri="https://github.com/BeyondDimension/SteamTools/issues/2276" />
|
||||
<HyperlinkButton Content="[Rooted Android] 3.x 新版导出令牌的另另一种思路" NavigateUri="https://github.com/BeyondDimension/SteamTools/issues/2598" />
|
||||
</StackPanel>
|
||||
</spp:Snackbar.ActionButton>
|
||||
</spp:Snackbar>
|
||||
|
||||
+205
-187
@@ -11,85 +11,86 @@
|
||||
x:CompileBindings="True"
|
||||
x:DataType="spp:SteamLoginImportPageViewModel"
|
||||
mc:Ignorable="d">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<spp:Stepper SelectedIndex="{Binding SelectIndex}">
|
||||
<spp:StepperItem Content="{Binding Path=Res.Steam_Login, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.LoginVerify, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.Steam_BindTel, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.TelCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.Btn_Text_Complete, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</spp:Stepper>
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<spp:Stepper SelectedIndex="{Binding SelectIndex}">
|
||||
<spp:StepperItem Content="{Binding Path=Res.Steam_Login, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.LoginVerify, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<!--隐藏绑定手机号步骤-->
|
||||
<!--<spp:StepperItem IsVisible="false" Content="{Binding Path=Res.Steam_BindTel, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
|
||||
<spp:StepperItem Content="{Binding Path=Res.LocalAuth_VerifyCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<spp:StepperItem Content="{Binding Path=Res.Btn_Text_Complete, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</spp:Stepper>
|
||||
|
||||
<spp:ContentLoader
|
||||
Grid.Row="1"
|
||||
MinWidth="600"
|
||||
MinHeight="400"
|
||||
IsLoading="{Binding IsLoading}"
|
||||
IsShowNoResultText="False">
|
||||
<Carousel IsVisible="{Binding !IsLoading}" SelectedIndex="{Binding SelectIndex}">
|
||||
<!-- 登录 -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<PathIcon
|
||||
Width="88"
|
||||
Height="88"
|
||||
Data="{StaticResource Steam}" />
|
||||
<TextBox
|
||||
MaxWidth="280"
|
||||
Text="{Binding UserNameText}"
|
||||
Watermark="{Binding Path=Res.Steam_User, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PasswordText"
|
||||
MaxWidth="280"
|
||||
Classes="revealPasswordButton"
|
||||
PasswordChar="{StaticResource PasswordChar}"
|
||||
Text="{Binding PasswordText}"
|
||||
Watermark="{Binding Path=Res.Steam_Password, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Login, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
<spp:ContentLoader
|
||||
Grid.Row="1"
|
||||
MinWidth="600"
|
||||
MinHeight="400"
|
||||
IsLoading="{Binding IsLoading}"
|
||||
IsShowNoResultText="False">
|
||||
<Carousel IsVisible="{Binding !IsLoading}" SelectedIndex="{Binding SelectIndex}">
|
||||
<!-- 登录 -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<PathIcon
|
||||
Width="88"
|
||||
Height="88"
|
||||
Data="{StaticResource Steam}" />
|
||||
<TextBox
|
||||
MaxWidth="280"
|
||||
Text="{Binding UserNameText}"
|
||||
Watermark="{Binding Path=Res.Steam_User, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PasswordText"
|
||||
MaxWidth="280"
|
||||
Classes="revealPasswordButton"
|
||||
PasswordChar="{StaticResource PasswordChar}"
|
||||
Text="{Binding PasswordText}"
|
||||
Watermark="{Binding Path=Res.Steam_Password, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Login, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- 邮箱验证 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{Binding !Requires2FA}"
|
||||
Message="{Binding Path=Res.LocalAuth_EmailCodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{Binding Requires2FA}"
|
||||
Message="{Binding Path=Res.LocalAuth_Steam2FACodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<TextBlock IsVisible="{Binding !Requires2FA}" Text="{Binding Path=Res.EmailCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBlock IsVisible="{Binding Requires2FA}" Text="{Binding Path=Res.Steam2FACode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="EmailAuthText"
|
||||
MinWidth="300"
|
||||
FontSize="24"
|
||||
Text="{Binding EmailAuthText}" />
|
||||
<!--<TextBlock
|
||||
<!-- 邮箱验证 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{Binding !Requires2FA}"
|
||||
Message="{Binding Path=Res.LocalAuth_EmailCodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{Binding Requires2FA}"
|
||||
Message="{Binding Path=Res.LocalAuth_Steam2FACodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<TextBlock IsVisible="{Binding !Requires2FA}" Text="{Binding Path=Res.EmailCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBlock IsVisible="{Binding Requires2FA}" Text="{Binding Path=Res.Steam2FACode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="EmailAuthText"
|
||||
MinWidth="300"
|
||||
FontSize="24"
|
||||
Text="{Binding EmailAuthText}" />
|
||||
<!--<TextBlock
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding EmailDomainText}" />-->
|
||||
<!--<TextBlock
|
||||
<!--<TextBlock
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Path=Res.LocalAuth_EmailCodeTip,
|
||||
@@ -97,121 +98,138 @@
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="WrapWithOverflow" />-->
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
<!-- 绑定手机 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.Warning_SteamAuthPhoneBind, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<!-- 绑定手机 -->
|
||||
<!--
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.Warning_SteamAuthPhoneBind, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.User_Phone, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PhoneNumberText"
|
||||
MaxWidth="300"
|
||||
Text="{Binding PhoneNumberText}"
|
||||
Watermark="+8601234567890" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.User_Phone, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PhoneNumberText"
|
||||
MaxWidth="300"
|
||||
Text="{Binding PhoneNumberText}"
|
||||
Watermark="+8601234567890" />
|
||||
</DockPanel>
|
||||
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>-->
|
||||
|
||||
<!-- 验证手机 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.LocalAuth_SMSCodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<DockPanel IsVisible="{Binding RevocationCodeText, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.RecoveryCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
MaxWidth="300"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding RevocationCodeText, Mode=OneWay}" />
|
||||
</DockPanel>
|
||||
<!--<CheckBox Content="{Binding Path=Res.RecoveryCode_Remember, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.SMSCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PhoneCodeText"
|
||||
MaxWidth="300"
|
||||
Text="{Binding PhoneCodeText}" />
|
||||
</DockPanel>
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
<!-- 验证手机 或 邮箱 -->
|
||||
<!-- 2024-04-23 Steam绑定令牌不强制手机绑定 可以输入 邮箱者手机验证码 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsVisible="{Binding IsVerifyAccountPhone}"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.LocalAuth_SMSCodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsVisible="{Binding !IsVerifyAccountPhone}"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.LocalAuth_EmailCodeTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Warning" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<DockPanel IsVisible="{Binding RevocationCodeText, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.RecoveryCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
MaxWidth="300"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding RevocationCodeText, Mode=OneWay}" />
|
||||
</DockPanel>
|
||||
<!--<CheckBox Content="{Binding Path=Res.RecoveryCode_Remember, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
IsVisible="{Binding IsVerifyAccountPhone}"
|
||||
Text="{Binding Path=Res.SMSCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
IsVisible="{Binding !IsVerifyAccountPhone}"
|
||||
Text="{Binding Path=Res.EmailCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Name="PhoneCodeText"
|
||||
MaxWidth="300"
|
||||
Text="{Binding PhoneCodeText}" />
|
||||
</DockPanel>
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding LoginSteamImport}"
|
||||
Content="{Binding Path=Res.Continue, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
<!-- 完成 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.RecoveryCode_RememberTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Success" />
|
||||
<StackPanel VerticalAlignment="Center" Spacing="10">
|
||||
<DockPanel IsVisible="{Binding RevocationCodeText, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.RecoveryCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
MaxWidth="300"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding RevocationCodeText, Mode=OneWay}" />
|
||||
</DockPanel>
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding Reset}"
|
||||
Content="{Binding Path=Res.Confirm, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Carousel>
|
||||
</spp:ContentLoader>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
<!-- 完成 -->
|
||||
<Panel>
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{Binding Path=Res.RecoveryCode_RememberTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Severity="Success" />
|
||||
<StackPanel VerticalAlignment="Center" Spacing="10">
|
||||
<DockPanel IsVisible="{Binding RevocationCodeText, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,6"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding Path=Res.RecoveryCode, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
MaxWidth="300"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding RevocationCodeText, Mode=OneWay}" />
|
||||
</DockPanel>
|
||||
<Button
|
||||
MinWidth="280"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding Reset}"
|
||||
Content="{Binding Path=Res.Confirm, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Carousel>
|
||||
</spp:ContentLoader>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
+2
-2
@@ -14,7 +14,7 @@ public partial class SteamLoginImportPage : ReactiveUserControl<SteamLoginImport
|
||||
DataContext ??= new SteamLoginImportPageViewModel();
|
||||
PasswordText.KeyUp += PasswordTextOnKeyUp;
|
||||
EmailAuthText.KeyUp += PasswordTextOnKeyUp;
|
||||
PhoneNumberText.KeyUp += PasswordTextOnKeyUp;
|
||||
//PhoneNumberText.KeyUp += PasswordTextOnKeyUp;
|
||||
PhoneCodeText.KeyUp += PasswordTextOnKeyUp;
|
||||
EmailAuthText.TextInput += EmailAuthText_TextInput;
|
||||
PhoneCodeText.TextInput += EmailAuthText_TextInput;
|
||||
@@ -34,4 +34,4 @@ public partial class SteamLoginImportPage : ReactiveUserControl<SteamLoginImport
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,4 +21,6 @@ public interface IAccount : IReactiveObject
|
||||
ThirdpartyPlatform Platform { get; init; }
|
||||
|
||||
string? PlatformName { get; init; }
|
||||
|
||||
string? AvatarMedium { get; set; }
|
||||
}
|
||||
|
||||
@@ -30,4 +30,6 @@ public sealed class BasicAccount : ReactiveObject, IAccount
|
||||
|
||||
[Reactive]
|
||||
public bool MostRecent { get; set; }
|
||||
|
||||
public string? AvatarMedium { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
using Avalonia.Platform;
|
||||
using BD.WTTS.Properties;
|
||||
using FluentAvalonia.Interop;
|
||||
using SkiaSharp;
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
namespace BD.WTTS.Models;
|
||||
@@ -19,10 +23,10 @@ public sealed partial class PlatformAccount
|
||||
_ => platformSwitchers.OfType<BasicPlatformSwitcher>().First(),
|
||||
};
|
||||
|
||||
SwapToAccountCommand = ReactiveCommand.Create<IAccount>(acc =>
|
||||
SwapToAccountCommand = ReactiveCommand.Create<IAccount>(async acc =>
|
||||
{
|
||||
platformSwitcher.SwapToAccount(acc, this);
|
||||
Toast.Show(ToastIcon.Success, AppResources.Success_SwitchAccount__.Format(FullName, acc.DisplayName));
|
||||
if (await platformSwitcher.SwapToAccount(acc, this))
|
||||
Toast.Show(ToastIcon.Success, AppResources.Success_SwitchAccount__.Format(FullName, acc.DisplayName));
|
||||
});
|
||||
|
||||
OpenUrlToBrowserCommand = ReactiveCommand.Create<IAccount>(acc =>
|
||||
@@ -32,8 +36,7 @@ public sealed partial class PlatformAccount
|
||||
|
||||
DeleteAccountCommand = ReactiveCommand.Create<IAccount>(async acc =>
|
||||
{
|
||||
var r = await platformSwitcher.DeleteAccountInfo(acc, this);
|
||||
if (r)
|
||||
if (await platformSwitcher.DeleteAccountInfo(acc, this))
|
||||
Toast.Show(ToastIcon.Success, Strings.Success_DeletePlatformAccount__.Format(FullName, acc.DisplayName));
|
||||
});
|
||||
|
||||
@@ -72,6 +75,8 @@ public sealed partial class PlatformAccount
|
||||
if (!Directory.Exists(PlatformLoginCache))
|
||||
Directory.CreateDirectory(PlatformLoginCache);
|
||||
|
||||
CreateShortcutCommand = ReactiveCommand.Create<IAccount>(acc => CreateShortcut(acc));
|
||||
|
||||
//LoadUsers();
|
||||
}
|
||||
|
||||
@@ -120,4 +125,91 @@ public sealed partial class PlatformAccount
|
||||
}
|
||||
return await platformSwitcher.CurrnetUserAdd(name, this);
|
||||
}
|
||||
|
||||
public async void CreateShortcut(IAccount acc)
|
||||
{
|
||||
#if WINDOWS
|
||||
var gear = new[] { 512, 256, 128, 96, 64, 48, 32, 24, 16 };
|
||||
using var avatarImgBitmap = await Decode(acc.AvatarMedium);
|
||||
var iconSize = GetImgResolutionPower(Math.Min(avatarImgBitmap.Width, avatarImgBitmap.Height), gear);
|
||||
using var fBitmap = DrawIcon(avatarImgBitmap, SKBitmap.Decode(IApplication.Login_512), iconSize.Max());
|
||||
SKBitmap[]? bitmaps = new[] { fBitmap }.Concat(iconSize.Where(x => x != iconSize.Max())
|
||||
.Select(x => fBitmap.Resize(new SKSizeI { Height = x, Width = x }, SKFilterQuality.High)))
|
||||
.ToArray();
|
||||
try
|
||||
{
|
||||
var localCachePath = Path.Combine(PlatformLoginCache, acc.AccountName!, "Icon");
|
||||
IOPath.DirTryDelete(localCachePath);
|
||||
Directory.CreateDirectory(localCachePath);
|
||||
|
||||
var savePath = Path.Combine(localCachePath, $"{Guid.NewGuid()}.ico");
|
||||
using (var fs = new FileStream(savePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete))
|
||||
{
|
||||
IcoEncoder.Encode(fs, bitmaps);
|
||||
fs.Flush();
|
||||
}
|
||||
var deskTopPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
||||
$"{acc.DisplayName ?? acc.AccountName ?? acc.AccountId}.lnk");
|
||||
|
||||
var processPath = Environment.ProcessPath;
|
||||
processPath.ThrowIsNull();
|
||||
if (!DesktopBridge.IsRunningAsUwp)
|
||||
{
|
||||
platformSwitcher.CreateSystemProtocol(processPath);
|
||||
}
|
||||
var args = $"-clt steam -account {acc.AccountName}";
|
||||
await platformSwitcher.CreateLoginShortcut(
|
||||
deskTopPath,
|
||||
$"{Constants.CUSTOM_URL_SCHEME}args/{HttpUtility.UrlEncode(args)}",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
savePath,
|
||||
null);
|
||||
Toast.Show(ToastIcon.Success, Strings.CreateShortcutInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bitmaps != null)
|
||||
bitmaps.ForEach(x => x.Dispose());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
async Task<SKBitmap> Decode(string? avatarImgPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(avatarImgPath))
|
||||
return SKBitmap.Decode(AssetLoader.Open(new Uri("avares://BD.WTTS.Client.Avalonia/UI/Assets/avatar.jpg")));
|
||||
if (avatarImgPath.StartsWith("https"))
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
using var rspimg = await client.GetStreamAsync(avatarImgPath);
|
||||
return SKBitmap.Decode(rspimg);
|
||||
}
|
||||
return SKBitmap.Decode(avatarImgPath);
|
||||
}
|
||||
|
||||
SKBitmap DrawIcon(SKBitmap originalBitmap, SKBitmap loginIcon, int iconSize)
|
||||
{
|
||||
loginIcon = loginIcon.Resize(new SKSizeI(iconSize / 3, iconSize / 3), SKFilterQuality.High);
|
||||
SKBitmap avatarImgBitmap = new(iconSize, iconSize);
|
||||
|
||||
using SKCanvas canvas = new(avatarImgBitmap);
|
||||
SKPaint paint = new SKPaint();
|
||||
paint.FilterQuality = SKFilterQuality.High;
|
||||
canvas.DrawBitmap(originalBitmap.Resize(new SKSizeI(iconSize, iconSize), SKFilterQuality.High),
|
||||
new SKRect(0, 0, iconSize, iconSize), paint);
|
||||
|
||||
canvas.DrawBitmap(loginIcon, new SKRect(0, 0, loginIcon.Width, loginIcon.Height));
|
||||
return avatarImgBitmap;
|
||||
}
|
||||
|
||||
IEnumerable<int> GetImgResolutionPower(int size, int[] rp)
|
||||
{
|
||||
foreach (var item in rp)
|
||||
{
|
||||
if (item <= size) yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using SystemPlatform = System.Runtime.Devices.Platform;
|
||||
|
||||
namespace BD.WTTS.Models;
|
||||
|
||||
public sealed partial class PlatformAccount : ReactiveObject
|
||||
@@ -71,6 +73,9 @@ public sealed partial class PlatformAccount : ReactiveObject
|
||||
[S_JsonIgnore, MP2Ignore, N_JsonIgnore]
|
||||
public bool IsSteamPlatform => Platform == ThirdpartyPlatform.Steam;
|
||||
|
||||
[S_JsonIgnore, MP2Ignore, N_JsonIgnore]
|
||||
public bool IsWindwos => IsSteamPlatform && DeviceInfo2.Platform() == SystemPlatform.Windows;
|
||||
|
||||
public string RegJsonPath(string accName) => Path.Combine(PlatformLoginCache, accName, "reg.json");
|
||||
|
||||
public ICommand SwapToAccountCommand { get; }
|
||||
@@ -86,4 +91,6 @@ public sealed partial class PlatformAccount : ReactiveObject
|
||||
public ICommand CopyCommand { get; }
|
||||
|
||||
public ICommand OpenLinkCommand { get; }
|
||||
|
||||
public ICommand CreateShortcutCommand { get; }
|
||||
}
|
||||
|
||||
@@ -87,4 +87,9 @@ public sealed class SteamAccount : ReactiveObject, IAccount
|
||||
|
||||
public string? PlatformName { get; init; }
|
||||
|
||||
public string? AvatarMedium
|
||||
{
|
||||
get => SteamUser.AvatarMedium;
|
||||
set => SteamUser.AvatarMedium = value;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -102,13 +102,13 @@ public sealed class BasicPlatformSwitcher : IPlatformSwitcher
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask SwapToAccount(IAccount? account, PlatformAccount platform)
|
||||
public async ValueTask<bool> SwapToAccount(IAccount? account, PlatformAccount platform)
|
||||
{
|
||||
//LoadAccountIds();
|
||||
|
||||
var killResult = await KillPlatformProcess(platform);
|
||||
if (!killResult)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Add currently logged in account if there is a way of checking unique ID.
|
||||
// If saved, and has unique key: Update
|
||||
@@ -125,7 +125,7 @@ public sealed class BasicPlatformSwitcher : IPlatformSwitcher
|
||||
{
|
||||
RunPlatformProcess(platform, true);
|
||||
Toast.Show(ToastIcon.Info, AppResources.Info_AlreadyTheCurrentAccount);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
await CurrnetUserAdd(platform.Accounts.First(acc => acc.AccountId == uniqueId).AccountName ?? "Unknown", platform);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public sealed class BasicPlatformSwitcher : IPlatformSwitcher
|
||||
// Copy saved files in
|
||||
if (!string.IsNullOrEmpty(account?.AccountId))
|
||||
{
|
||||
if (!await BasicCopyInAccount(account.AccountId, platform)) return;
|
||||
if (!await BasicCopyInAccount(account.AccountId, platform)) return false;
|
||||
//Globals.AddTrayUser(platform.SafeName, $"+{platform.PrimaryId}:" + accId, accName, BasicSettings.TrayAccNumber); // Add to Tray list, using first Identifier
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ public sealed class BasicPlatformSwitcher : IPlatformSwitcher
|
||||
//{
|
||||
// //
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> ClearCurrentLoginUser(PlatformAccount platform)
|
||||
|
||||
+11
-3
@@ -11,7 +11,7 @@ public sealed class SteamPlatformSwitcher : IPlatformSwitcher
|
||||
this.swWebService = swWebService;
|
||||
}
|
||||
|
||||
public async ValueTask SwapToAccount(IAccount? account, PlatformAccount platform)
|
||||
public async ValueTask<bool> SwapToAccount(IAccount? account, PlatformAccount platform)
|
||||
{
|
||||
await KillPlatformProcess(platform);
|
||||
var users = platform.Accounts?.Cast<SteamAccount>().Select(s => s.SteamUser).ToArray();
|
||||
@@ -59,6 +59,7 @@ public sealed class SteamPlatformSwitcher : IPlatformSwitcher
|
||||
Toast.Show(ToastIcon.Error, Strings.Error_SteamGetUserInfo);
|
||||
}
|
||||
RunPlatformProcess(platform, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
async ValueTask<bool> IPlatformSwitcher.ClearCurrentLoginUser(PlatformAccount platform)
|
||||
@@ -98,8 +99,13 @@ public sealed class SteamPlatformSwitcher : IPlatformSwitcher
|
||||
|
||||
public async Task<IEnumerable<IAccount>?> GetUsers(PlatformAccount platform, Action? refreshUsers = null)
|
||||
{
|
||||
//var users = steamService.GetRememberUserList();
|
||||
await SteamConnectService.Current.RefreshSteamUsers();
|
||||
SteamConnectService.Current.RefreshSteamUsers();
|
||||
Task2.InBackground(async () =>
|
||||
{
|
||||
await SteamConnectService.Current.RefreshSteamUsersInfo();
|
||||
refreshUsers?.Invoke();
|
||||
});
|
||||
|
||||
var users = SteamConnectService.Current.SteamUsers.Items;
|
||||
|
||||
if (!users.Any_Nullable())
|
||||
@@ -130,6 +136,7 @@ public sealed class SteamPlatformSwitcher : IPlatformSwitcher
|
||||
Items = trayMenus,
|
||||
});
|
||||
|
||||
await Task.CompletedTask;
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@@ -161,4 +168,5 @@ public sealed class SteamPlatformSwitcher : IPlatformSwitcher
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace BD.WTTS.Services;
|
||||
|
||||
public interface IPlatformSwitcher
|
||||
{
|
||||
ValueTask SwapToAccount(IAccount? account, PlatformAccount platform);
|
||||
ValueTask<bool> SwapToAccount(IAccount? account, PlatformAccount platform);
|
||||
|
||||
ValueTask<bool> ClearCurrentLoginUser(PlatformAccount platform);
|
||||
|
||||
@@ -31,4 +31,34 @@ public interface IPlatformSwitcher
|
||||
Task<bool> DeleteAccountInfo(IAccount account, PlatformAccount platform);
|
||||
|
||||
Task<IEnumerable<IAccount>?> GetUsers(PlatformAccount platform, Action? refreshUsers = null);
|
||||
|
||||
public void CreateSystemProtocol(string targetPath)
|
||||
{
|
||||
using var key = Registry.ClassesRoot.CreateSubKey(Constants.CUSTOM_URL_SCHEME_NAME);
|
||||
key.SetValue("URL Protocol", "");
|
||||
using var shellKey = key.CreateSubKey("shell");
|
||||
using RegistryKey openKey = shellKey.CreateSubKey("open");
|
||||
using RegistryKey commandKey = openKey.CreateSubKey("command");
|
||||
commandKey.SetValue("", "\"" + targetPath + "\" \"%1\"");
|
||||
}
|
||||
|
||||
public async Task<bool> CreateLoginShortcut(
|
||||
string pathLink,
|
||||
string targetPath,
|
||||
string? arguments,
|
||||
string? description,
|
||||
string? hotkey,
|
||||
string? iconLocation,
|
||||
string? workingDirectory,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
#if WINDOWS
|
||||
var s = await IPlatformService.IPCRoot.Instance;
|
||||
s.CreateShortcut(pathLink, targetPath, arguments, description, hotkey, iconLocation, workingDirectory);
|
||||
return true;
|
||||
#else
|
||||
await Task.CompletedTask;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,11 @@
|
||||
IsVisible="{Binding !$parent[UserControl].((spp:PlatformAccount)DataContext).IsSteamPlatform}"
|
||||
Text="修改头像图片" />
|
||||
<ui:MenuFlyoutSeparator />
|
||||
<!--<ui:MenuFlyoutItem Text="创建桌面快捷方式" />-->
|
||||
<ui:MenuFlyoutItem
|
||||
Command="{Binding $parent[UserControl].((spp:PlatformAccount)DataContext).CreateShortcutCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding $parent[UserControl].((spp:PlatformAccount)DataContext).IsWindwos}"
|
||||
Text="创建桌面快捷方式" />
|
||||
<ui:MenuFlyoutItem
|
||||
Click="OpenUserDataFolderMenuItem_Click"
|
||||
IsVisible="{Binding $parent[UserControl].((spp:PlatformAccount)DataContext).IsSteamPlatform}"
|
||||
@@ -219,6 +223,8 @@
|
||||
Tags="{Binding AliasName}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock>
|
||||
<Run Text="双击可快速切换到此账号登录" />
|
||||
<LineBreak />
|
||||
<Run FontSize="16" Text="{Binding DisplayName}" />
|
||||
<LineBreak />
|
||||
<Run Text="{Binding AliasName}" />
|
||||
@@ -228,6 +234,11 @@
|
||||
<Border Classes="Status" IsVisible="{Binding MostRecent}">
|
||||
<TextBlock Text="{Binding Path=Res.UserChange_RecentLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</Border>
|
||||
<!--<Button
|
||||
Classes="accent"
|
||||
Command="{Binding $parent[UserControl].((spp:PlatformAccount)DataContext).SwapToAccountCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="一键切换" />-->
|
||||
</spp:AppItem.Status>
|
||||
<i:Interaction.Behaviors>
|
||||
<ia:EventTriggerBehavior EventName="DoubleTapped" SourceObject="Item">
|
||||
|
||||
@@ -183,6 +183,8 @@ public sealed class EditAppInfoPageViewModel : WindowViewModel
|
||||
//await MessageBox.ShowAsync("保存成功但还不会直接写入Steam文件, 请打开[保存Steam游戏自定义信息窗口]保存所有更改信息到Steam文件中。",
|
||||
// ThisAssembly.AssemblyTrademark, MessageBox.Button.OK, MessageBox.Image.None, MessageBox.DontPromptType.SaveEditAppInfo);
|
||||
|
||||
Toast.Show(ToastIcon.Info, "保存成功但还不会直接写入 Steam 文件,请到右上角编辑列表中保存所有更改信息到 Steam 文件中,即可生效!");
|
||||
|
||||
this.Close?.Invoke(false);
|
||||
}
|
||||
|
||||
@@ -199,7 +201,7 @@ public sealed class EditAppInfoPageViewModel : WindowViewModel
|
||||
App.RefreshEditImage();
|
||||
if (App.OriginalData != null)
|
||||
{
|
||||
using BinaryReader reader = new BinaryReader(new MemoryStream(App.OriginalData));
|
||||
using BinaryReader reader = new BinaryReader(new MemoryStream(App.OriginalData), Encoding.UTF8, true);
|
||||
reader.BaseStream.Seek(40L, SeekOrigin.Current);
|
||||
var table = reader.ReadPropertyTable();
|
||||
|
||||
|
||||
@@ -77,14 +77,14 @@
|
||||
<Border Classes="Item" IsVisible="{Binding App.IsInstalled}">
|
||||
<DockPanel>
|
||||
<TextBlock Text="{Binding Path=Res.InstalledPath, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Right"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Command="{Binding OpenFolder}"
|
||||
CommandParameter="{Binding App.InstalledDir}"
|
||||
ToolTip.Tip="{Binding Path=Res.Settings_General_BrowseCustomFolder, Mode=OneWay, Source={x:Static s:ResourceService.Current}, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Null}}">
|
||||
<TextBlock Text="{Binding App.InstalledDir}" TextWrapping="Wrap" />
|
||||
</ui:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
@@ -524,7 +524,7 @@
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="{Binding Path=Res.DirPath, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Right"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{Binding $parent[UserControl].((spp:EditAppInfoPageViewModel)DataContext).OpenFolder}"
|
||||
@@ -534,7 +534,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FormatDirPath}"
|
||||
TextWrapping="Wrap" />
|
||||
</ui:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using BD.SteamClient.Helpers;
|
||||
using BD.SteamClient.Models;
|
||||
using BD.SteamClient.Models.Idle;
|
||||
using BD.SteamClient.Services;
|
||||
using BD.WTTS.UI.Views.Pages;
|
||||
using BD.SteamClient.Constants;
|
||||
using AngleSharp.Common;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
@@ -12,6 +9,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
{
|
||||
readonly ISteamService SteamTool = ISteamService.Instance;
|
||||
readonly ISteamIdleCardService IdleCard = ISteamIdleCardService.Instance;
|
||||
readonly ISteamSessionService steamSession = ISteamSessionService.Instance;
|
||||
|
||||
private SteamLoginState SteamLoginState = new();
|
||||
|
||||
@@ -54,6 +52,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
await ISecureStorage.Instance.RemoveAsync(ISteamSessionService.CurrentSteamUserKey);
|
||||
steamSession.RemoveSession(SteamLoginState.SteamId.ToString());
|
||||
SteamLoginState = new();
|
||||
IsLogin = false;
|
||||
}
|
||||
@@ -120,11 +119,13 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
//return;
|
||||
}
|
||||
|
||||
//await SteamConnectService.Current.RefreshGamesListAsync();
|
||||
RunState = await ReadyToGoIdle(true);
|
||||
PauseAutoNext(true);
|
||||
RunOrStopAutoNext(true);
|
||||
RunOrStopAutoCardDropCheck(true);
|
||||
if (RunState = await ReadyToGoIdle(true))
|
||||
{
|
||||
RunOrStopAutoNext(true);
|
||||
RunOrStopAutoCardDropCheck(true);
|
||||
}
|
||||
else
|
||||
Toast.Show(ToastIcon.Error, Strings.Idle_StartError);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -224,10 +225,13 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
if (!SteamLoginState.Success)
|
||||
{
|
||||
LoginViewModel = new IdleSteamLoginPageViewModel(ref SteamLoginState);
|
||||
LoginViewModel.Close += _ =>
|
||||
LoginViewModel.Close += async _ =>
|
||||
{
|
||||
ViewState = 1;
|
||||
IsLogin = SteamLoginState.Success;
|
||||
await LoadBadges();
|
||||
SteamAppsSort();
|
||||
|
||||
ViewState = 1;
|
||||
};
|
||||
ViewState = 0;
|
||||
//var vm = new IdleSteamLoginPageViewModel(ref SteamLoginState);
|
||||
@@ -325,16 +329,38 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
}
|
||||
async Task RunLoadBadges()
|
||||
{
|
||||
IEnumerable<Badge> badges;
|
||||
IEnumerable<Badge>? badges;
|
||||
HttpStatusCode status;
|
||||
|
||||
(UserIdleInfo, badges) = await IdleCard.GetBadgesAsync(SteamLoginState.SteamId.ToString(), true);
|
||||
GetBadges:
|
||||
var steam_id = SteamLoginState.SteamId.ToString();
|
||||
(UserIdleInfo, badges, status) = await IdleCard.GetBadgesAsync(steam_id, true);
|
||||
if (status == HttpStatusCode.Forbidden)
|
||||
{
|
||||
var result = await TextBoxWindowViewModel.ShowDialogAsync(new TextBoxWindowViewModel
|
||||
{
|
||||
Title = Strings.Idle_NeedParentalPIN,
|
||||
Placeholder = "PIN CODE",
|
||||
InputType = TextBoxWindowViewModel.TextBoxInputType.Password,
|
||||
});
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
await steamSession.UnlockParental(steam_id, result);
|
||||
goto GetBadges;
|
||||
}
|
||||
throw new ArgumentNullException(Strings.Idle_PIN_NotBeNull);
|
||||
}
|
||||
else if (status != HttpStatusCode.OK)
|
||||
throw new HttpRequestException($"{Strings.Idle_GetBadgesError} status code: {status}");
|
||||
goto HandleBadges;
|
||||
|
||||
HandleBadges:
|
||||
Badges.Clear();
|
||||
Badges.Add(badges);
|
||||
Badges.Add(badges!);
|
||||
TotalCardsRemaining = 0;
|
||||
TotalCardsAvgPrice = 0;
|
||||
|
||||
badges = badges.Where(w => w.CardsRemaining != 0);
|
||||
badges = badges!.Where(w => w.CardsRemaining != 0);
|
||||
|
||||
foreach (var badge in badges)
|
||||
{
|
||||
@@ -352,6 +378,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
IdleGameList.Clear();
|
||||
var badges = Badges.Where(w => w.CardsRemaining != 0);
|
||||
var apps = SteamIdleSettings.IdleSequentital.Value switch
|
||||
{
|
||||
@@ -360,14 +387,13 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
IdleSequentital.Mostvalue => badges.OrderByDescending(o => o.RegularAvgPrice).Select(s => new IdleApp(s)),
|
||||
_ => badges.Select(s => new IdleApp(s)),
|
||||
};
|
||||
|
||||
IdleGameList = new ObservableCollection<IdleApp>(apps);
|
||||
|
||||
foreach (var app in apps)
|
||||
IdleGameList.Add(app);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Toast.LogAndShowT(ex);
|
||||
ex.LogAndShowT();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -399,42 +425,59 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
return;
|
||||
}
|
||||
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.OnlyOneGame)
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.FastMode)
|
||||
{
|
||||
idleApp = VerifyIsNext(IdleGameList, isNext);
|
||||
StartSoloIdle(idleApp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.OneThenMany || SteamIdleSettings.IdleRule.Value == IdleRule.FastMode)
|
||||
{
|
||||
var multi = IdleGameList.Where(z => z.Badge.HoursPlayed >= SteamIdleSettings.MinRunTime.Value).ToList();
|
||||
var multi = IdleGameList.Where(z => z.Badge.HoursPlayed >= SteamIdleSettings.MinRunTime.Value).ToList();
|
||||
|
||||
if (multi.Count >= 1)
|
||||
{
|
||||
idleApp = VerifyIsNext(multi, isNext);
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.FastMode)
|
||||
PauseAutoNext(false);
|
||||
StartSoloIdle(idleApp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.FastMode)
|
||||
PauseAutoNext(true);
|
||||
StartMultipleIdle();
|
||||
}
|
||||
if (multi.Count > 1)
|
||||
{
|
||||
idleApp = VerifyIsNext(multi, isNext);
|
||||
PauseAutoNext(false);
|
||||
StartSoloIdle(idleApp);
|
||||
}
|
||||
else
|
||||
{
|
||||
var multi = IdleGameList.Where(z => z.Badge.HoursPlayed < SteamIdleSettings.MinRunTime.Value).ToList();
|
||||
if (multi.Count >= 2)
|
||||
PauseAutoNext(true);
|
||||
StartMultipleIdle();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
PauseAutoNext(true);
|
||||
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.OnlyOneGame)
|
||||
{
|
||||
idleApp = VerifyIsNext(IdleGameList, isNext);
|
||||
StartSoloIdle(idleApp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SteamIdleSettings.IdleRule.Value == IdleRule.OneThenMany)
|
||||
{
|
||||
StartMultipleIdle();
|
||||
var multi_Idles = IdleGameList.Where(z => z.Badge.HoursPlayed >= SteamIdleSettings.MinRunTime.Value).ToList();
|
||||
if (multi_Idles.Count >= 1)
|
||||
{
|
||||
idleApp = VerifyIsNext(multi_Idles, isNext);
|
||||
StartSoloIdle(idleApp);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartMultipleIdle();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
idleApp = VerifyIsNext(multi, isNext);
|
||||
StartSoloIdle(idleApp);
|
||||
var multi_AFKs = IdleGameList.Where(z => z.Badge.HoursPlayed < SteamIdleSettings.MinRunTime.Value).ToList();
|
||||
if (multi_AFKs.Count >= 2)
|
||||
{
|
||||
StartMultipleIdle();
|
||||
}
|
||||
else
|
||||
{
|
||||
idleApp = VerifyIsNext(multi_AFKs.Count <= 0 ? IdleGameList : multi_AFKs, isNext);
|
||||
StartSoloIdle(idleApp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,7 +491,6 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
ResetCurrentIdle();
|
||||
return idles.First();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,7 +745,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
if (currentIdleAppId.HasValue)
|
||||
{
|
||||
|
||||
if (!Badges.Any(x => x.AppId == currentIdleAppId && x.CardsRemaining > 0)) // 当前休息挂卡完成
|
||||
if (!Badges.Any(x => x.AppId == currentIdleAppId && x.CardsRemaining > 0)) // 当前游戏挂卡完成
|
||||
{
|
||||
if (nextIdleAppId.HasValue) // 挂卡下一个游戏
|
||||
idleApp = IdleGameList.Where(x => x.AppId == nextIdleAppId).FirstOrDefault();
|
||||
@@ -732,8 +774,9 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
private void IdleComplete()
|
||||
{
|
||||
RunState = false;
|
||||
Toast.Show(ToastIcon.Success, Strings.Idle_Complete);
|
||||
INotificationService.Instance.Notify(Strings.Idle_Complete, NotificationType.Message);
|
||||
var message = Strings.Idle_Complete.Format(DropCardsCount, IdleTime.TotalHours.ToInt32());
|
||||
Toast.Show(ToastIcon.Success, message);
|
||||
INotificationService.Instance.Notify(message, NotificationType.Message);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -107,11 +107,11 @@
|
||||
</i:Interaction.Behaviors>
|
||||
</ui:SymbolIcon>
|
||||
</Viewbox>
|
||||
<TextBlock
|
||||
<ContentControl
|
||||
Margin="28,4,0,4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<ContentControl.Content>
|
||||
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
|
||||
<CompiledBinding Mode="OneWay" Path="RunState" />
|
||||
<CompiledBinding
|
||||
@@ -123,8 +123,8 @@
|
||||
Path="Res.Launch"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
</Panel>
|
||||
<ProgressBar
|
||||
Width="28"
|
||||
@@ -245,8 +245,8 @@
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>-->
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<ContentControl>
|
||||
<ContentControl.Content>
|
||||
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
|
||||
<CompiledBinding Mode="OneWay" Path="IsLogin" />
|
||||
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UserName" />
|
||||
@@ -255,8 +255,8 @@
|
||||
Path="Res.Idle_NeedLoginSteam"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
</spp:OptionsDisplayItem.Header>
|
||||
<spp:OptionsDisplayItem.Description>
|
||||
<Panel>
|
||||
@@ -294,8 +294,8 @@
|
||||
<Button VerticalAlignment="Center" Command="{Binding LoginSteamCommand}">
|
||||
<WrapPanel>
|
||||
<ui:SymbolIcon Margin="0,0,5,0" Symbol="AddFriend" />
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<ContentControl>
|
||||
<ContentControl.Content>
|
||||
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
|
||||
<CompiledBinding Mode="OneWay" Path="IsLogin" />
|
||||
<CompiledBinding
|
||||
@@ -307,8 +307,8 @@
|
||||
Path="Res.User_Login"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
</WrapPanel>
|
||||
</Button>
|
||||
</spp:OptionsDisplayItem.ActionButton>
|
||||
@@ -385,8 +385,8 @@
|
||||
<EllipseGeometry Rect="0,0,24,24" />
|
||||
</spp:Image2.Clip>
|
||||
</spp:Image2>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<ContentControl VerticalAlignment="Center">
|
||||
<ContentControl.Content>
|
||||
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
@@ -401,8 +401,8 @@
|
||||
Path="Res.SteamAppType_Unknown"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</spp:OptionsDisplayItem.Content>
|
||||
@@ -414,8 +414,8 @@
|
||||
Expands="True"
|
||||
IsExpanded="True">
|
||||
<spp:OptionsDisplayItem.Header>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<ContentControl>
|
||||
<ContentControl.Content>
|
||||
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
|
||||
<CompiledBinding Mode="OneWay" Path="RunState" />
|
||||
<CompiledBinding
|
||||
@@ -427,8 +427,8 @@
|
||||
Path="Res.SteamIdle_IdleNotRuning"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
</spp:OptionsDisplayItem.Header>
|
||||
<spp:OptionsDisplayItem.Description>
|
||||
<Panel>
|
||||
@@ -610,7 +610,7 @@
|
||||
DecodeWidth="120"
|
||||
FallbackSource="avares://BD.WTTS.Client.Plugins.GameList/UI/Assets/defaultappimage.png"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding App.LibraryGridStream^}"
|
||||
Source="{Binding App.LibraryGridUrl}"
|
||||
Stretch="UniformToFill" />
|
||||
<TextBlock
|
||||
Margin="8,0"
|
||||
|
||||
@@ -172,6 +172,9 @@ bundle /v /bv {version4} /d "{dirPath}" /p "{msixPath}"
|
||||
{
|
||||
// https://learn.microsoft.com/zh-cn/windows/msix/desktop/desktop-to-uwp-manual-conversion
|
||||
|
||||
// 处理 URI 激活
|
||||
// https://learn.microsoft.com/zh-cn/windows/uwp/launch-resume/handle-uri-activation
|
||||
|
||||
var xmlString =
|
||||
$"""
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
@@ -212,6 +215,12 @@ Version="{version4}" ProcessorArchitecture="{processorArchitecture.ToString().To
|
||||
<desktop:Extension Category="windows.startupTask" Executable="Steam++.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run"/>
|
||||
</desktop:Extension>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="spp">
|
||||
<uap:Logo>Images\Square44x44Logo.png</uap:Logo>
|
||||
<uap:DisplayName>Steam++ URI Scheme</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
@@ -145,6 +145,8 @@ public partial interface IApplication
|
||||
return default;
|
||||
});
|
||||
|
||||
static byte[] Login_512 => Properties.Resources.AppLogo_512;
|
||||
|
||||
static ClientPlatform ClientPlatform => clientPlatform.Value;
|
||||
|
||||
System.Drawing.Size? GetScreenSize() => null;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user