Compare commits

...
Author SHA1 Message Date
wangyingpan bc19a02f4f 🐛 修复替换令牌时恢复代码不显示的问题
调整添加令牌时的提示信息
2024-04-29 18:07:05 +08:00
Aigio Liu d59d63d57e 🎨 vipEndTimeString 2024-04-29 16:13:40 +08:00
yingpanwang 9ad6e8fa8d Fix/authenticator verifycode UI (#3313)
* 🐛 修复添加令牌邮箱/短信验证ui显示

* 🎨 删除绑定手机号步骤
🐛 修复替换令牌逻辑验证码信息展示错误的问题

* 🐛 修复添加令牌邮箱/短信验证ui显示

* 🎨 删除绑定手机号步骤
🐛 修复替换令牌逻辑验证码信息展示错误的问题

* ✨ 替换令牌中添加绑定手机号窜哦做
2024-04-29 15:27:28 +08:00
Aigio Liu 45ef15b220 🎨 VipEndTimeString 2024-04-29 15:04:47 +08:00
Aigio Liu 25dfd1f888 🐛 VipEndTimeString 2024-04-29 15:01:57 +08:00
Aigio Liu 0495cd3079 💚 CI 2024-04-29 13:14:45 +08:00
Aigio Liu ea9c45d78b 🎨 VipEndTimeString 2024-04-29 13:11:51 +08:00
Aigio Liu 4427fd1a5f 🐛 VipEndTimeString 2024-04-29 12:18:03 +08:00
RMBGAME edf3c3a72c ⬆️ SteamClient 2024-04-29 12:10:10 +08:00
Aigio Liu 55490d2351 🐛 VipEndTimeString 2024-04-29 11:59:22 +08:00
Aigio Liu 17ae6655b4 🐛 VipEndTimeString 2024-04-29 11:55:34 +08:00
john.jiang 342d25deac 🎨 受限账户徽章页处理 2024-04-29 10:59:58 +08:00
RMBGAME f5616dac2e 🐛💄 UI改进,修复加速可能导致的循环修改hosts问题 2024-04-28 22:15:29 +08:00
john.jiang 28914c53f6 🎨 fixs 2024-04-28 19:24:41 +08:00
john.jiang f0cbe8ef88 🎨 会员时长文本显示调整 2024-04-28 19:11:01 +08:00
RMBGAME e0b45bf8af 💄🐛 修复可能导致启动加速无限修改hosts的错误 2024-04-28 17:43:30 +08:00
RMBGAME 854fe420fd 🐛💄 Misc 2024-04-28 17:43:30 +08:00
Aigio Liu c95ce1087a 🐛 Shortcut 2024-04-28 17:29:44 +08:00
Aigio Liu 14ae662675 ✨ URI 激活 2024-04-28 16:18:58 +08:00
RMBGAME 1594bd250e 🐛💄 UI错误修复 2024-04-28 16:09:32 +08:00
Aigio Liu c83ab4fb08 🐛 VipEndTime 2024-04-28 15:35:45 +08:00
Aigio Liu e690454e0c 🐛 VipEndTime 2024-04-28 15:29:13 +08:00
john.jiang 27b4f4bb10 🎨 会员时间显示格式调整 2024-04-28 15:03:42 +08:00
john.jiang 70315c3a99 🎨 fixs 2024-04-28 11:13:45 +08:00
Aigio Liu b8e49a5332 🎨 XY.GetVipEndTime 2024-04-28 10:38:15 +08:00
RMBGAME 1374dc52b8 🐛 2024-04-27 17:40:46 +08:00
RMBGAME 150f93e7b7 ⬆️ Avalonia 11.1.0-beta2 2024-04-27 16:17:59 +08:00
RMBGAME d1aea71f67 🐛 修复一些图片加载问题 2024-04-25 18:51:36 +08:00
RMBGAME 37a522ec7f ⬆️ Image2 2024-04-25 17:43:03 +08:00
RMBGAME 8b816b9787 💄 UI调整优化
现在不需要赞助也可以设置启动默认页
2024-04-25 17:41:49 +08:00
Aigio Liu 4f487a5ca8 🐛 OpenBrowserCommand 2024-04-22 17:03:53 +08:00
Aigio Liu 6c7a547a83 🎨 GetVipEndTime 2024-04-22 17:00:47 +08:00
Aigio Liu bde5751d69 ✨ 会员时间显示与联系客服 2024-04-22 16:45:37 +08:00
Aigio Liu 23544b6c39 🎨 XunYouSDK.GetVipEndTime 2024-04-22 16:13:12 +08:00
RMBGAME 2c60a9fb69 💄 feedback 2024-04-22 16:03:10 +08:00
RMBGAME 4ce67c8f1a 💄 Misc 2024-04-22 11:22:35 +08:00
Aigio Liu e8a1a6153e 🎨 OpenBrowserCommand 2024-04-22 10:26:58 +08:00
ff m 452fa2ac84 🐛 离线安装也支持选择初始化位置 2024-04-20 18:51:23 +08:00
ff m 60e6d71d48 ✨ 安装脚本支持安装到指定目录,支持选择desktop创建至 $HOME/.local/share/applications/ or $HOME/Desktop
The most significant changes to the `online_install.sh` script involve the addition of user prompts for customization of the installation path and the location of the desktop file. The script now includes a loop to allow the user to input a custom installation path or use the default one. If the user provides an invalid path, they are prompted to input again. The script also creates the installation path and switches to it, outputting an error message and exiting with status 1 if it fails to do so. A new function, `InitDesktop`, has been added to prompt the user to choose where to add the desktop file. The user can choose between `$HOME/.local/share/applications/` and `$HOME/Desktop`. The script then removes any existing desktop file in the chosen location and creates a new one. If the user provides an invalid option, they are prompted to input again.

List of changes:

1. A loop has been added to prompt the user to input a custom installation path or use the default path. If the user provides an invalid path, they are prompted to input again (online_install.sh).

2. The script now creates the installation path and switches to it. If it fails to do either, it outputs an error message and exits with status 1 (online_install.sh).

3. A function `InitDesktop` has been added to prompt the user to choose where to add the desktop file. The user can choose between `$HOME/.local/share/applications/` and `$HOME/Desktop`. The script then removes any existing desktop file in the chosen location and creates a new one. If the user provides an invalid option, they are prompted to input again (online_install.sh).

4. The function `InitDesktop` is called to execute the above changes (online_install.sh).
2024-04-20 18:39:43 +08:00
RMBGAME ae0e2349e7 🐛 fix Errors caused by testing modifications 2024-04-20 17:28:47 +08:00
Aigio Liu 3a0f2a4950 🐛 ShowStart 2024-04-19 21:26:22 +08:00
Aigio Liu 5263863c23 🐛 ShowStart 2024-04-19 20:22:41 +08:00
RMBGAME c52f3c3e00 💄 测试加速项 Beta徽章 2024-04-18 20:39:51 +08:00
RMBGAME be61df890e 🐛 修复 隔行扫描gif图 动态头像显示错误 2024-04-17 23:05:49 +08:00
Aigio Liu 6a7c9113a0 ✨ XunYouState.启动游戏成功/启动游戏失败 2024-04-16 19:15:26 +08:00
Aigio Liu ac953af7a1 🎨 hosts mark duplicate auto reset 2024-04-16 14:15:31 +08:00
john.jiang 147a6b911f 🎨 家庭监护账号Cookie取消本地储存,有效期太短 2024-04-16 13:29:30 +08:00
wangyingpan 972054ca7c ⬆️ 更新 ClientSDK 2024-04-15 17:13:03 +08:00
RMBGAME 0cea809b40 🐛 fix 2024-04-15 16:24:23 +08:00
lxy829 7ec34e59a7 🎨 切换提示 2024-04-12 16:12:36 +08:00
RMBGAME 00d41dfbc8 🚧 Misc 2024-04-12 14:52:07 +08:00
RMBGAME 0272a34e5c ✨ 游戏加速 启动游戏按钮 2024-04-12 12:21:15 +08:00
wangyingpan d450753c92 添加代理token 缓存 2024-04-11 14:29:30 +08:00
wangyingpan 1e6263c241 🎨 调整尝试获取服务端代理token 方法 2024-04-11 13:51:41 +08:00
RMBGAME a4602d29a7 🐛 fix background img 2024-04-10 16:40:00 +08:00
john.jiang 7623788991 🎨 Idle挂卡家庭监护账号处理 2024-04-09 15:27:57 +08:00
Aigio Liu 8075183be1 ✨ XYSDK 2024-04-09 15:20:39 +08:00
ff m 058f1a9bf2 🐛 屏蔽 IPC 脚本内容优化 2024-04-08 20:52:09 +08:00
wangyingpan 196b7f0f5e 🐛 回撤ForwarderRequestConfig的错误修改 2024-04-03 18:29:14 +08:00
wangyingpan 99d24b2513 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2024-04-03 18:26:35 +08:00
wangyingpan 7e3fb6cf06 ✨ 启动加速时请求服务端代理token,代理设置添加服务端代理token属性 2024-04-03 18:26:29 +08:00
lxy829 a0a0dd9bc7 🎨 fix 2024-04-03 16:37:21 +08:00
lxy829 81f75e61ca 🎨 Shortcut 指向协议 2024-04-03 16:08:16 +08:00
john.jiang 11890c68a8 🎨 修复挂卡顺序切换图片不显示 2024-04-03 11:20:32 +08:00
RMBGAME be8621d4d5 ✨ 增加代理http3支持
修复twitch登录失效
修复twitch直播聊天失效
修复twtich面板内图片加载失败
修复Github Docs网站代理无效且异常重定向
修复Github education网站代理无效且异常重定向
修复Gravatar加速失效
新增Open Spotify加速
新增Github Dev加速
2024-04-02 19:07:16 +08:00
wangyingpan 511c84b2ba 添加服务端代理转发头 2024-04-02 18:31:07 +08:00
john.jiang 9e75da9e46 🎨 fixs 2024-04-02 10:57:53 +08:00
john.jiang 547b360db8 🎨 Idle 启动失败提示 2024-04-02 10:56:37 +08:00
john.jiang 9be21a8e0a 🎨 挂卡完成提示完善 2024-04-01 17:00:46 +08:00
john.jiang efbb41bf3f 🎨 快速挂卡模式优化 2024-04-01 14:31:45 +08:00
RMBGAME c2220359dd 🐛 fix 2024-03-29 22:16:46 +08:00
lxy829 a788afdeeb 🎨 Steam 账号加载方式优化 2024-03-28 11:19:34 +08:00
BuknSS b543a889a0 🎨 Remove temporary test code. 2024-03-27 16:08:52 +08:00
lxy829 478950b75f 🎨 GameAccountShortcut 2024-03-26 19:13:07 +08:00
Aigio Liu 27b4114d7d 🔖 3.0.0-rc.8 2024-03-26 15:30:41 +08:00
Aigio Liu 6ead1f3666 🐛 |Error|HostsFileS|TryReadHosts fail, methodName: ContainsHostsByTag. | System.IO.IOException: The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process.
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.File.ReadAllLines(String path, Encoding encoding)
2024-03-26 14:24:36 +08:00
Aigio Liu 22a015d8b9 ✨ hosts 文件监听 2024-03-26 12:02:59 +08:00
BuknSS f5b7cd6db8 🐛 Fix not regenerating a valid certificate after the certificate expires. 2024-03-26 11:02:33 +08:00
lxy829 5a397d1311 🎨 SteamSwitchShortcut 2024-03-25 18:59:03 +08:00
Aigio Liu 0c1bedaeb1 🔥 iotest 2024-03-22 21:50:32 +08:00
Aigio Liu e959dcc017 ✨ 查看证书信息 2024-03-22 19:05:31 +08:00
RMBGAME 41610453a0 🐛 fix 挂卡首次登录后数据加载问题 2024-03-20 20:10:21 +08:00
ff m adf3e0ca9d 🐛 UrlsOpened 11.1 使用新 API 2024-03-20 00:21:00 +08:00
Aigio Liu 21e80a5b7b 🎨 XunYouSDK.GetGameInfo 2024-03-18 13:57:58 +08:00
114 changed files with 4289 additions and 1759 deletions
+5 -5
View File
@@ -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
+24 -5
View File
@@ -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 "下载安装完成,是否启动程序?"
+58 -8
View File
@@ -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 "下载安装完成,是否启动程序?"
+2 -2
View File
@@ -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;
}
}
+10 -2
View File
@@ -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,
+17 -3
View File
@@ -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">&#xE112;</x:String>
<x:String x:Key="Sort">&#xE174;</x:String>
<x:String x:Key="Move">&#xE7C2;</x:String>
<x:String x:Key="Device">&#xE7FB;</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",
},
@@ -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();
});
@@ -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();
}
}
@@ -46,4 +46,9 @@ sealed class LazyCertificateManager : ICertificateManager
{
return impl().DeleteRootCertificate();
}
public string GetCertificateInfo()
{
return impl().GetCertificateInfo();
}
}
@@ -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;
@@ -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;
@@ -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();
@@ -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();
}
@@ -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>
/// 是否正在停止加速等待
@@ -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="&#xE95B;" />
</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>
/// 迅游下载进度回调函数委托
@@ -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>
@@ -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);
}
@@ -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; }
}
@@ -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);
}
}
}
@@ -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;
}
}
}
}
@@ -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; }
}
@@ -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>
@@ -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>
@@ -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>
@@ -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;
}
}
@@ -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,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>
+2
View File
@@ -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