Compare commits

...
Author SHA1 Message Date
ff m 4573a9b494 🐛 优化安装脚本 2024-10-21 17:32:54 +08:00
Aigio Liu 8f29d7b8ea 🔖 3.0.0-rc.12 2024-10-11 16:40:52 +08:00
RMBGAME 7156d90967 🐛 Windows 关机事件遗漏 2024-10-11 16:33:56 +08:00
RMBGAME ec726e8c48 💄 成就 受保护 文本调整 2024-10-10 15:55:08 +08:00
RMBGAME 488403a86b 🐛 修复 新版 Steam 客户端 编辑游戏功能保存不生效问题 2024-10-10 15:50:59 +08:00
Aigio Liu 59474d68cd Revert "🔥 Remove ProxyMode.DNSIntercept with REMOVE_DNS_INTERCEPT"
This reverts commit 7f7fde20bd.
2024-10-09 21:14:50 +08:00
RMBGAME 5ab308bb9b 🐛 修复 ASF 下载通知问题 2024-10-09 19:31:42 +08:00
RMBGAME bf3d6ad778 🐛 修复挂卡和令牌确认交易中可能出现系统代理更改时网络请求出错的问题 2024-10-09 18:33:02 +08:00
RMBGAME 59356f210e Merge pull request #3499 from BeyondDimension/feature/network_test_domain_validation
✨ 网络测试页面域名检测输入检查
2024-10-09 18:31:46 +08:00
k1mlka 42df02f718 🐛 网络测试页面清理 2024-10-09 15:22:33 +08:00
k1mlka 8728126421 Merge branch 'develop' into feature/network_test_domain_validation 2024-10-09 15:03:30 +08:00
RMBGAME e0c36a9545 Merge pull request #3495 from BeyondDimension/refactor_NetworkCheck
💄 网络测试子页面重构
2024-10-09 14:19:00 +08:00
k1mlka 9a2bd1a6cc 🐛 移除多余代码 2024-10-08 11:40:51 +08:00
k1mlka 499edb402c 💄 网络测试子页面ViewModel重构
💄 网络测试子页面NAT检查部分逻辑重构

💄 网络测试子页面ViewModel重构

💄 网络测试子页面NAT检查部分逻辑重构

💄代码Clean,没有引用到的名称空间与枚举类型

💄 网络测试子页面NAT检查部分逻辑重构

💄 网络测试子页面ViewModel重构

💄 网络测试子页面NAT检查部分逻辑重构

💄代码Clean,没有引用到的名称空间与枚举类型
2024-10-08 11:33:37 +08:00
k1mlka 14216ab98f ✨ 网络测试页面域名检测输入检查 2024-09-29 17:26:58 +08:00
john.jiang 95c10dacd4 🎨 ASF 插件 Release 版进程残留问题修复,启动优化,打开文件夹提示优化 2024-09-27 15:30:51 +08:00
Aigio Liu 2883405ace 🎨 ResetSettings Toast 2024-09-25 14:29:59 +08:00
Aigio Liu 75e5b6e897 🔖 3.0.0-rc.11 2024-09-25 10:04:42 +08:00
BuknSS 66f3aa22d6 🐛 Fix missing GetMessage parameters 2024-09-24 18:39:37 +08:00
Aigio Liu 7930120e0b 🐛 ResetProxySettings 2024-09-24 17:44:36 +08:00
RMBGAME aa44b137c4 Merge pull request #3471 from BeyondDimension/feature/ipv6check
Feature/ipv6check
2024-09-24 16:51:10 +08:00
RMBGAME 103918060a ✨ 代理设置增加重置按钮 2024-09-24 16:50:47 +08:00
RMBGAME 0644bbf924 ✨ 完善加速相关QA提示 2024-09-24 16:07:15 +08:00
RMBGAME 3853181d8a 🚧 Misc 2024-09-23 18:12:55 +08:00
RMBGAME de694024bb Merge pull request #3475 from BeyondDimension/fix_accelerator_by_startup
🐛 修复启用 “启动时自动加速” 后,测试页面不显示问题
2024-09-23 17:53:02 +08:00
RMBGAME 6ceaeb0710 🐛 通知消息弹出不再重复弹出同样消息 2024-09-23 17:45:46 +08:00
RMBGAME 71f104b947 🐛 修复默认doh地址导致加速失效的问题 2024-09-23 17:21:26 +08:00
k1mlka a9e913434a 🚧 重新实现EnableProxyDomainGroupVMs数据源订阅 2024-09-23 11:28:23 +08:00
RMBGAME f1ffae11aa 🐛 修复在启用了启动时最小化之后某些情况会导致Toast提示弹出失败的问题 2024-09-20 21:58:36 +08:00
Aigio Liu 7f7fde20bd 🔥 Remove ProxyMode.DNSIntercept with REMOVE_DNS_INTERCEPT 2024-09-20 21:13:49 +08:00
k1mlka 2e97c2edc2 🐛 修复任意输入错误域名,返回延迟和空ip问题 2024-09-12 17:03:44 +08:00
k1mlka 69b8590881 🐛 修复启用 “启动时自动加速” 后,测试页面不显示问题 2024-09-06 11:56:47 +08:00
ff m 7b9ba5e8a0 ➕ BsDiff 2024-09-04 13:30:44 +08:00
k1mlka d6ab3c7f03 💄 网路测试页面重写 2024-09-02 17:27:43 +08:00
k1mlka 12d062d4de ✨ ipv6检测 2024-09-02 17:19:07 +08:00
k1mlka f27d3c9111 Merge pull request #3469 from luojunyuan/develop
🐛 初次启动时"设置页面-启动页设置项"显示修复
2024-09-02 11:59:32 +08:00
k1mlka 6ab74d449e 🐛 初次启动时"设置页面-启动页设置项"显示修复 2024-09-02 11:54:17 +08:00
RMBGAME ceed57c0f2 🐛 Misc 2024-08-31 23:05:35 +08:00
RMBGAME 8ca203f34b 🐛 多个问题修复调整 2024-08-31 19:54:36 +08:00
RMBGAME d4de20aa96 🔖 3.0.0 rc.10 2024-08-31 19:54:36 +08:00
k1mlka c94dec40a1 💄 代理设置开关IsEnable一致性调整 2024-08-31 19:28:03 +08:00
k1mlka b829114c4f 💄 加速服务测试不展开小项目 2024-08-31 19:27:26 +08:00
k1mlka a3a32bf5fa 🐛 修复初次Doh测试回报error 2024-08-31 17:59:49 +08:00
k1mlka faa7cc5b13 💄 加速后延迟测试颜色调整 2024-08-31 17:36:57 +08:00
k1mlka 1e506f9930 🐛 Hack方式修正默认加速器页面动画移动方向 2024-08-31 17:12:38 +08:00
k1mlka 217449ba54 🚧 重构加速器页面部分xaml绑定 2024-08-31 16:55:18 +08:00
k1mlka 55b79c4d4e 💄 网络检测页面DNS与doh输入可编辑 2024-08-31 16:39:09 +08:00
Aigio Liu a6c5fed203 🐛 WinDivertInit 2024-08-31 14:23:47 +08:00
RMBGAME b7e31d1c9c Merge pull request #3461 from BeyondDimension/feature/networktestservice
Feature/networktestservice
2024-08-30 20:13:07 +08:00
RMBGAME 1d0b16895f 💄 UI Adaptive 2024-08-30 20:11:38 +08:00
k1mlka 65d73c2e50 🚧 重构加速器页面到单独的页面 2024-08-30 19:26:53 +08:00
k1mlka 83c390e446 🐛 通过脚本开关来确定是否显示脚本配置页面 2024-08-30 19:20:15 +08:00
k1mlka 31f5fa5ea6 🚧 DNS检测项目输入项调整 2024-08-30 17:19:44 +08:00
k1mlka f42c7cbd30 🐛 尝试修复STUN服务器初始不显示问题 2024-08-30 16:04:07 +08:00
k1mlka 3cb592ce7e 🚧 加速服务显示子项树形结构 2024-08-30 15:54:05 +08:00
k1mlka d5fa02e9a4 Merge remote-tracking branch 'origin/develop' into feature/networktestservice 2024-08-30 13:58:53 +08:00
k1mlka cbb2fac6ea 🐛 停止加速时,不显示脚本配置页面 2024-08-30 13:36:05 +08:00
RMBGAMEandAigio Liu 35747253b8 Feature/ipc console log (#3463)
* 🎨 LogConsole

* 🐛 ref

* 💄 ProxyLog UI

* 🐛 WebApplication Logging

* 🐛 Misc

* 🎨 Log format

* 🎨 LogConsoleService.ConsoleMaxCharCount

* 🐛 Misc

---------

Co-authored-by: Aigio Liu <lysoft.pw@foxmail.com>
2024-08-30 13:33:18 +08:00
k1mlka 17c45fdd65 🚧加速器页面Tab栏目调整,无脚本启动时隐藏 2024-08-30 13:26:23 +08:00
k1mlka 88f02e3faf 🚧 网络测试宽度与项目背景色调整 2024-08-29 18:05:41 +08:00
k1mlka 35a0c4d05f 🐛 google doh 地址修正 2024-08-29 18:04:49 +08:00
k1mlka ca7fa0bc97 Merge remote-tracking branch 'origin/develop' into feature/networktestservice 2024-08-29 14:34:51 +08:00
RMBGAME 6a3c8f6187 🐛 多平台账号切换暂只支持 Windows 2024-08-29 00:59:36 +08:00
Aigio Liu 24bb4bd625 ⬆️ Common 2024-08-28 19:30:19 +08:00
k1mlka a3811a97a0 🚧 清理未启用的代码3 2024-08-28 18:47:03 +08:00
k1mlka dc6723f2e5 🚧 清理未启用的代码2 2024-08-28 18:40:45 +08:00
k1mlka e14f169cb0 🚧 清理未启用的代码 2024-08-28 18:38:40 +08:00
k1mlka 235a1abf34 🚧 调整输入框宽度 2024-08-28 18:36:01 +08:00
RMBGAME c5edf0a8b5 ⬇️ Avalonia 11.1.3 2024-08-28 17:41:52 +08:00
Aigio Liu c0b6d9d8e2 🐛 build error 2024-08-28 16:31:44 +08:00
Aigio Liu eefd2fa705 🐛 WinAuth 2024-08-28 16:16:04 +08:00
Aigio Liu 1a4a5887f2 ⬆️ SDK 2024-08-28 16:07:50 +08:00
Aigio Liu 5a163ae124 🐛 fix build 2024-08-28 15:56:52 +08:00
k1mlka 95c5a9e143 🐛 doh服务器地址修正 2024-08-28 15:20:14 +08:00
k1mlka 94f47b0165 ✨ doh检测 2024-08-28 15:14:31 +08:00
k1mlka 08beb69865 🚧 加速器页面及逻辑调整 2024-08-28 11:38:53 +08:00
Aigio Liu 0e9def7e6a ⬆️ Common 2024-08-28 03:11:55 +08:00
Aigio Liu 6a307d97ab 🐛 try fix build 2024-08-28 02:40:36 +08:00
Aigio Liu 252eaa74c2 🐛 PackageVersion 2024-08-28 02:05:38 +08:00
RMBGAME 4115216a41 ⬆️ Avalonia 11.2.0-beta1 2024-08-28 01:02:12 +08:00
RMBGAME a99e410196 🐛 Misc 2024-08-27 19:01:28 +08:00
RMBGAME 4154b68c79 🐛 修复 RiotGames 账号切换无法添加账号的问题
✨账号切换新增 GOG Discord平台
2024-08-27 18:38:24 +08:00
k1mlka fa20e3a945 💄 窗口无边框化页面调整 2024-08-27 17:55:07 +08:00
ff m a15a166b5c ↑ 更新子模块 2024-08-26 18:41:19 +08:00
ff m 0fcd736259 ↑ 更新子模块修复报错 2024-08-26 18:38:22 +08:00
ff m 925c735557 ↑ 更新子模块 2024-08-26 17:59:00 +08:00
k1mlka 0505158281 ✨ 完善网络测试页面 2024-08-23 12:41:06 +08:00
RMBGAME 19651ddeb1 🐛 update afdian domain 2024-08-22 18:57:56 +08:00
k1mlka 1fe7986e08 🚧 业务功能实现 2024-08-22 15:11:25 +08:00
k1mlka a469e29438 🚧 网路检测页面 2024-08-20 17:58:58 +08:00
yingpanwang ec028a4a5c 🚧 调整代码,添加网络环境检查系列方法 2024-08-20 17:18:53 +08:00
RMBGAME 6ebbeb000f 🐛 账号切换 修正在某些情况下也应该尝试正常打开新账号登录窗口 2024-08-19 21:08:45 +08:00
RMBGAME e3cf3195a8 🐛 FixedWrapPanel adaptive width 2024-08-19 16:52:02 +08:00
Aigio Liu 1734a03d41 IPCSubProcessFileSystem.GetLogDirPath 2024-08-19 16:03:08 +08:00
RMBGAME 7c47f01a77 🚧 GameAccelerator 2024-08-17 05:00:30 +08:00
RMBGAME dd622fb5e9 🚧 GameAccelerator 2024-08-16 20:50:06 +08:00
Aigio Liu 59636940c5 ➕ xunyou_show 2024-08-16 18:24:25 +08:00
Aigio Liu c7922c915b 🚧 XYShow 2024-08-16 17:51:59 +08:00
yingpanwang b2031998d8 🎨 调整命名 2024-08-16 16:38:27 +08:00
Aigio Liu 7d3a74daf2 🎨 Safe P/Invoke 2024-08-16 14:28:46 +08:00
k1mlka dccce87293 💄 无边框窗口页面UI改进 2024-08-09 15:29:31 +08:00
Aigio Liu 442ad2dac7 Revert "🔥 Remove ProxyMode.DNSIntercept"
This reverts commit 3eae8a8c84.
2024-08-08 17:03:03 +08:00
RMBGAME ae9a6350a7 🐛 修复 TaskDialog 在主窗口为null时不在抛出异常
修复加速脚本url匹配问题
2024-08-08 02:38:23 +08:00
Aigio Liu d49e38f314 🐛 ISettings 0kb 2024-08-08 01:14:06 +08:00
lxy829 5244009b02 🎨 cs:go vac 修复功能调整 2024-08-06 17:25:36 +08:00
Aigio Liu eef7478585 🐛 CheckUpdateAsync 2024-08-06 14:19:01 +08:00
Aigio Liu 3eae8a8c84 🔥 Remove ProxyMode.DNSIntercept 2024-08-06 12:24:13 +08:00
Aigio Liu c8509e94cf 🔖 3.0.909 rc.9 ms-store Attempt to fix the audit 2024-08-06 11:48:38 +08:00
RMBGAME 7140fc79c9 🐛 修复最新 SteamBeta 版在某些情况下管理成就无限加载问题 2024-08-06 11:44:58 +08:00
Mossimo 7d25393d6e ✨ 程序版本差异 二进制修补 发布工具 打包 上传 发布差异包 ( br 压缩 ) 修补测试 2024-08-05 18:37:49 +08:00
RMBGAME 149c5bbfd3 fix #3412 #2695 #3115
修复某些情况下加速后会将非加速网站自动跳转至加速页的BUG
修复在开启失效代理设置的情况下 加速进程依然错误的使用失效代理解析doh导致加速失败的问题
2024-08-02 00:49:36 +08:00
RMBGAME 38485f1e24 Revert "🐛 DomainMatch"
This reverts commit 008ab5fd4f.
2024-08-01 15:00:47 +08:00
Aigio Liu 008ab5fd4f 🐛 DomainMatch 2024-07-31 02:58:35 +08:00
john.jiang 178c074eb6 🎨 ASF 增加一键下载功能 2024-07-30 19:17:52 +08:00
yingpanwang c3279e78c2 ✨ 添加网络测试服务 NetworkTestService (#3401)
* ✨ 添加网络测试服务 NetworkTestService

* 🚧 清理日志

* ✨ 添加dns 测试

* ⚡️ 添加 DNS Over Https 测试方式, 调整 DNS解析格式
2024-07-18 11:25:28 +08:00
lxy829 7c8f8448af 🎨 cs:go vac repair 2024-07-18 10:18:48 +08:00
wangyingpan 8affd6a3f5 ⚡️ 添加 DNS Over Https 测试方式, 调整 DNS解析格式 2024-07-17 15:36:59 +08:00
RMBGAME 66fdfb728e ⬆️ Avalonia 11.1.0-rc2 2024-07-17 06:01:11 +08:00
RMBGAME 9261344a3c 🐛 修复高dpi下窗口大小及位置变化问题 2024-07-17 05:47:00 +08:00
wangyingpan 923f2eaf94 ✨ 添加dns 测试 2024-07-15 15:59:43 +08:00
RMBGAME 95190efa5c 🐛 doh默认地址调整
加速项目默认选中问题修复
2024-07-12 19:36:04 +08:00
ff m dd71972adc ✨ macOS 支持从 dock 栏 打开窗口显示 2024-07-11 15:43:34 +08:00
wangyingpan 3a48c00ec6 🚧 清理日志 2024-07-11 14:09:01 +08:00
wangyingpan e3f40a3e40 ✨ 添加网络测试服务 NetworkTestService 2024-07-11 13:56:57 +08:00
wangyingpan 6c1e6d40e9 🎨 优化令牌重置 client 代码 2024-07-02 18:07:34 +08:00
wangyingpan 187c7f3b58 🐛 刷新 access_token 时重置对应 client 状态 2024-07-02 17:16:34 +08:00
john.jiang 4e3c86d009 🎨 挂卡剩最后一个游戏不自动切换 2024-07-02 13:40:10 +08:00
RMBGAME ed5cc929d3 📃 Text 2024-07-02 09:51:11 +08:00
Aigio Liu 213b2c2644 🔖 3.0.0 rc.9 2024-07-01 16:45:04 +08:00
RMBGAME e1adf07789 🐛 加速数据树结构某些情况下选中状态也异常问题修复 2024-07-01 16:38:08 +08:00
john.jiang 83f0634863 🎨 挂卡自动刷新token,bug修复 2024-07-01 16:16:18 +08:00
yingpanwang f08122769f 🐛 本地令牌 "确认交易" 时,当access_token 过期时使用refresh_token 刷新当前 access_token 以保持会话 (#3381)
* 🐛 本地令牌 "确认交易" 时,当access_token 过期时使用refresh_token 刷新当前 access_token 以保持会话

* 🎨 调整代码排版
2024-07-01 16:15:51 +08:00
RMBGAME 49fdeadfd2 🐛 HttpVersion 因按原本逻辑转发 2024-07-01 15:08:46 +08:00
RMBGAME bca80245bb 🐛 Misc 2024-06-30 19:00:17 +08:00
RMBGAME 0f6c127ad3 🐛 Misc 2024-06-30 16:00:46 +08:00
Aigio L c087a02303 Update CI.yml 2024-06-29 17:01:31 +08:00
Aigio Liu 6c84a876ff 🐛 ClrearLastLookNoticeDateTime 2024-06-29 16:52:52 +08:00
ff m df27068329 🐛 防止 添加 cookie 报错 2024-06-29 16:41:52 +08:00
ff m f10df31edd 🐛 更新子模块 适配转发请求头 2024-06-29 16:30:35 +08:00
RMBGAME 902919cf98 💄 Misc 2024-06-29 15:55:11 +08:00
RMBGAME a6aec5ad62 🐛 fixed Misc 2024-06-29 15:20:02 +08:00
RMBGAME 2fd651fd25 🐛 窗口偏移位置调整 2024-06-29 13:52:03 +08:00
RMBGAME 67615028e1 ✨🐛 新增 网络加速 二级代理支持
修复 Request Header 中存在非 ASCII 字符导致代理异常的状况
close #2753 #2523 #1661 #488
fix #1825
2024-06-29 00:43:17 +08:00
RMBGAME 1eca159e23 💄 Misc 2024-06-28 16:32:31 +08:00
RMBGAME d1d9ca82df 🐛 fix #3360
修复重启程序窗口增高问题
修复挂卡登录状态丢失挂卡依然继续问题
2024-06-28 16:02:06 +08:00
RMBGAME 88bd2dbfe9 🚑 库存游戏新 appinfo 解析 2024-06-28 13:57:44 +08:00
Mossimo ae2b2970db 🐛 修复 Linux 非 Host 模式不检测 443 占用 2024-06-27 16:32:47 +08:00
RMBGAME 67209f3861 🚧 Misc 2024-06-25 14:16:31 +08:00
RMBGAME 110c92d909 🐛⬆️ Avalonia 11.0-rc1 2024-06-24 14:26:48 +08:00
Aigio Liu cdc773ba74 📝 System.ArgumentException: Decoded string is not a valid IDN name. (Parameter 'unicode') 2024-06-16 00:48:32 +08:00
Aigio Liu 8ea8b73aab 🐛 System.ArgumentException: Decoded string is not a valid IDN name. (Parameter 'unicode')
at System.Globalization.IdnMapping.IcuGetAsciiCore(String unicodeString, Char* unicode, Int32 count)
   at System.Globalization.IdnMapping.GetAscii(String unicode, Int32 index, Int32 count)
   at System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder.AddDnsName(String dnsName)
   at BD.WTTS.Services.Implementation.CertGenerator.Add(SubjectAlternativeNameBuilder builder, String name)
2024-06-16 00:46:51 +08:00
RMBGAME 0614e172b8 ✨ 账号切换新增显示用户名设置项 2024-06-07 23:58:49 +08:00
yingpanwang 4c73a5b622 Feautre/joinsteamauthenticator (#3362)
* ✨ 导入steam令牌 新增添加 联合steam 令牌方式
2024-06-06 15:01:15 +08:00
RMBGAME 34341172b8 🚧 ItemsRepeater 2024-05-29 19:15:59 +08:00
RMBGAME 6813ed9ece ⬆️ FA 2.1.0-preview5 2024-05-27 18:23:02 +08:00
RMBGAME 3eb916e78a 🐛 蒸汽平台切换账号支持 2024-05-18 21:37:25 +08:00
RMBGAME 7424d05f54 💄 Avalonia_Edit 2024-05-17 20:27:04 +08:00
RMBGAME 76829b0ca2 🐛 fixed 挂时长启动暂停按钮显示问题 2024-05-10 17:24:43 +08:00
RMBGAME bbe98f68f6 🐛 游戏已加速情况下启动程序闪退问题修复 2024-05-06 19:04:46 +08:00
john.jiang 96025c9ef5 🎨 fixs 2024-05-06 17:12:18 +08:00
john.jiang 2fb12f1b7f 🎨 修复快速掉卡模式剩最后一个游戏闪退Bug 2024-05-06 17:07:07 +08:00
Mossimo 542b528b93 🐛 修复 Ubuntu 版本文件不存在中断问题 2024-04-30 16:28:23 +08:00
RMBGAME 0a4326924d 💄 修复加速模式文本显示错误 2024-04-30 16:13:49 +08:00
john.jiang e8a9990354 🎨 挂卡列表虚拟化缓存加长 2024-04-30 15:25:43 +08:00
Aigio Liu 4e72f9ccf9 🐛 ClickAdvertisement 2024-04-30 13:54:13 +08:00
Aigio Liu 6df6ed93cb 🐛 ClickAdvertisement 2024-04-30 13:42:09 +08:00
Aigio Liu 605beeb918 🐛 CopyToClipboardCommand 2024-04-30 13:38:13 +08:00
Aigio Liu 2bdfbefda9 🐛 OpenAuthUrl 2024-04-30 13:30:34 +08:00
Aigio Liu 6747549cbc 🐛 App.OpenBrowserCommand & IPartialGameAccountSettings null 2024-04-30 12:43:47 +08:00
Mossimo 0d4c0b27f7 🐛 fix 修复2月与新版本差异 2024-04-30 11:58:31 +08:00
Mossimo c501066a3d 🐛 fix 回退 websocket macos 使用 url 协议 2024-04-30 11:47:02 +08:00
john.jiang 57a6b24dad 🎨 修复挂卡价格显示 2024-04-30 11:40:37 +08:00
john.jiang 143011fbd0 🎨 挂卡帐号不一致 Toast 警告,账号登录页登陆成功先跳转主页再获取数据 2024-04-30 11:23:34 +08:00
RMBGAME 11bc9cd440 💄 略微缩减标题栏高度 2024-04-30 00:02:06 +08:00
RMBGAME 72e9068571 💄 简单的加速疑难解答 2024-04-29 19:28:28 +08:00
yingpanwang 5842d5f80f Merge pull request #3321 from BeyondDimension/fix/authenticator-ui
🐛 修复替换令牌时恢复代码不显示的问题
2024-04-29 18:08:06 +08:00
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
267 changed files with 12039 additions and 2780 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 --property: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 --property: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 --property: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 --property: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 --property:DebugType=pdbonly
# release:
# name: Release
+1 -1
View File
@@ -16,7 +16,7 @@
[![NET 7.0](https://img.shields.io/badge/dotnet-7.0-purple.svg?style=flat-square&color=512bd4)](https://learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-7)
[![C# 11](https://img.shields.io/badge/c%23-11-brightgreen.svg?style=flat-square&color=6da86a)](https://learn.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-11)
[![爱发电](https://img.shields.io/badge/爱发电-软妹币玩家-blue.svg?style=flat-square&color=ea4aaa&logo=github-sponsors)](https://afdian.net/@rmbgame)
[![爱发电](https://img.shields.io/badge/爱发电-软妹币玩家-blue.svg?style=flat-square&color=ea4aaa&logo=github-sponsors)](https://afdian.com/@rmbgame)
[![Kofi](https://img.shields.io/badge/Kofi-RMBGAME-orange.svg?style=flat-square&logo=kofi)](https://ko-fi.com/rmbgame)
[![Patreon](https://img.shields.io/badge/Patreon-RMBGAME-red.svg?style=flat-square&logo=patreon)](https://www.patreon.com/rmbgame)
+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 "下载安装完成,是否启动程序?"
+116 -10
View File
@@ -1,7 +1,44 @@
#!/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"
# 检查是否以root身份执行
if [ "$EUID" -eq 0 ]; then
echo "此脚本不能以root身份执行。"
exit 1
fi
# 循环直到用户输入有效路径或直接回车
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"
@@ -10,7 +47,45 @@ base_url="https://api.steampp.net"
architecture=1
app_name="Watt Toolkit"
PROCESS_NAMES=("$exec_name" "$app_name")
export LC_ALL=en_US.UTF-8
Check_LC_Code() {
# 检查当前 LANG 环境变量
current_lang="${LC_ALL:-$LANG}"
echo "当前语言环境: $current_lang"
# 检查是否为 C 或 C.UTF-8
if [ "$current_lang" = "C" ] || [ "$current_lang" = "C.UTF-8" ]; then
echo "当前语言环境是 C 或 C.UTF-8,需要修改。"
# 列出系统支持的语言环境
supported_locales=$(locale -a)
if [ -z "$supported_locales" ]; then
echo "未能获取到支持的语言环境列表。请检查系统配置。"
return 1
fi
# 遍历支持的语言环境列表,找到第一个不是 C 或 C.UTF-8 的项
first_non_c_locale=""
for locale in $supported_locales; do
if [ "$locale" != "C" ] && [ "$locale" != "C.UTF-8" ]; then
first_non_c_locale="$locale"
break
fi
done
# 检查是否找到了非 C/C.UTF-8 的语言环境
if [ -n "$first_non_c_locale" ]; then
echo "修改语言环境为支持列表中的第一个非 C/C.UTF-8 项:$first_non_c_locale"
export LC_ALL="$first_non_c_locale"
echo "语言环境已修改为 $first_non_c_locale。"
else
echo "未能找到非 C/C.UTF-8 的语言环境。请检查系统配置。"
return 1
fi
else
echo "当前语言环境不是 C 或 C.UTF-8,无需修改。"
fi
}
Check_LC_Code
Install_certutil() {
# 判断发行版类型
if command -v certutil &>/dev/null; then
@@ -182,7 +257,9 @@ Get_NewVer() {
sleep 1
#本地版本 Hash
o_sha384=$(cat "AppVer")
if [ -f "AppVer" ]; then
o_sha384=$(cat "AppVer")
fi
if [ -e "AppVer" ]; then
if [ "${o_sha384,,}" = "${n_sha384,,}" ]; then
Show_Run "已是最新版本,是否启动程序?"
@@ -326,17 +403,46 @@ 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() {
# 检查XDG_DESKTOP_DIR环境变量,如果未设置则使用默认值
XDG_DESKTOP_DIR="${XDG_DESKTOP_DIR:-$HOME/Desktop}"
while true; do
# 使用 zenity 提示用户选择安装路径或使用默认路径
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" \
--column="选择" --column="路径" \
TRUE "$XDG_DESKTOP_DIR" \
FALSE "$HOME/.local/share/applications/")
# 检查用户输入
if [ "$choice" == "$HOME/.local/share/applications/" ]; then
target_dir="$HOME/.local/share/applications/"
break
elif [ "$choice" == "$XDG_DESKTOP_DIR" ]; then
target_dir="$XDG_DESKTOP_DIR"
break
else
# 无效选项时给出提示,并继续循环
zenity --info --text="无效选项,请重新选择。"
fi
done
# 添加桌面文件
rm -rf "$target_dir/Watt Toolkit.desktop" 2>/dev/null
cat <<EOT > "$target_dir/Watt Toolkit.desktop"
[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
EOT
chmod +x "$target_dir/Watt Toolkit.desktop"
}
InitDesktop
# update-desktop-database ~/.local/share/applications
#运行程序
Show_Run "下载安装完成,是否启动程序?"
+28 -1
View File
@@ -496,6 +496,15 @@
"Summary": "账号平台设置集合",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "bool",
"PropertyName": "IsShowAccountName",
"DefaultValue": "false",
"DefaultValueIsConst": false,
"Summary": "是否显示账号用户名",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"Usings": ""
@@ -964,12 +973,30 @@
},
{
"TypeName": "string",
"PropertyName": "CustomDohAddres",
"PropertyName": "CustomDohAddres2",
"DefaultValue": "null",
"DefaultValueIsConst": true,
"Summary": "自定义 DNS over HTTPS 地址",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "int",
"PropertyName": "AcceleratorTabsSelectedIndex",
"DefaultValue": "0",
"DefaultValueIsConst": false,
"Summary": "加速页面 Tab 选项卡选中下标",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "bool",
"PropertyName": "AutoShowWattAcceleratorWindow",
"DefaultValue": "true",
"DefaultValueIsConst": true,
"Summary": "加速后自动唤起 Watt 加速器界面",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"Usings": ""
+5
View File
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<CheckEolWorkloads>false</CheckEolWorkloads>
</PropertyGroup>
</Project>
BIN
View File
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
static void WinDivert()
{
var dllWinDivert = Path.Combine(ProjectUtils.ProjPath, "ref", "WinDivert.dll");
var sysWinDivert64 = Path.Combine(ProjectUtils.ProjPath, "ref", "WinDivert64.sys");
byte[][] data = [File.ReadAllBytes(dllWinDivert), File.ReadAllBytes(sysWinDivert64)];
var bytes = MemoryPackSerializer.Serialize(data);
bytes.AsSpan().Reverse();
File.WriteAllBytes(Path.Combine(ProjectUtils.ProjPath, "ref", "WinDivert.mpo"), bytes);
}
+3 -3
View File
@@ -16,14 +16,14 @@ public static partial class AssemblyInfo
/// <summary>
/// 预览版本号,范围 1~9,只增不减
/// </summary>
const string ver_for_preview = "7";
const string ver_for_preview = "9";
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "7";
const string ver_for_rc = "12";
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
public const string FileVersion = $"{Version2}.{ver_for_rc}1{ver_for_preview}.0";
//public const string InformationalVersion = Version;
//public const string InformationalVersion = $"{Version}-preview.{ver_for_preview}";
@@ -7,6 +7,7 @@
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>$(MSBuildProjectDirectory)\..\..\avalonia.snk</AssemblyOriginatorKeyFile>
<!--https://github.com/AvaloniaUI/Avalonia/blob/0.10.10/build/SharedVersion.props#L18-->
<AvaloniaAccessUnstablePrivateApis>true</AvaloniaAccessUnstablePrivateApis>
</PropertyGroup>
<ItemGroup>
@@ -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>
@@ -91,9 +91,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BD.Common.XmlnsDefinition.Avalonia">
<Aliases>BD_Common_XmlnsDefinition_Avalonia</Aliases>
</PackageReference>
<ProjectReference Include="..\BD.WTTS.Client.Avalonia\BD.WTTS.Client.Avalonia.csproj" />
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
</ItemGroup>
@@ -68,9 +68,6 @@
<ItemGroup>
<PackageReference Include="Moq" />
<PackageReference Include="BD.Common.XmlnsDefinition.Avalonia">
<Aliases>BD_Common_XmlnsDefinition_Avalonia</Aliases>
</PackageReference>
<ProjectReference Include="..\BD.WTTS.Client.Avalonia\BD.WTTS.Client.Avalonia.csproj" />
<ProjectReference Include="..\BD.WTTS.Client.Plugins.Accelerator\BD.WTTS.Client.Plugins.Accelerator.csproj" />
<ProjectReference Include="..\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus.csproj" />
@@ -4,6 +4,7 @@
<RootNamespace>BD.WTTS</RootNamespace>
<DefineConstants>AVALONIA;$(DefineConstants)</DefineConstants>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AvaloniaAccessUnstablePrivateApis>true</AvaloniaAccessUnstablePrivateApis>
</PropertyGroup>
<ItemGroup>
@@ -72,17 +73,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BD.Common.XmlnsDefinition.Avalonia">
<Aliases>BD_Common_XmlnsDefinition_Avalonia</Aliases>
</PackageReference>
<PackageReference Include="BD.Common" />
<PackageReference Include="BD.Common.Primitives" />
<PackageReference Include="BD.Common.Essentials" />
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" />
<PackageReference Include="Avalonia.Xaml.Behaviors" />
<!--<PackageReference Include="Avalonia.AvaloniaEdit" />-->
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="AvaloniaEdit.TextMate" />
<!--<PackageReference Include="Markdown.Avalonia" />-->
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
@@ -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,203 @@
using Avalonia.Reactive;
using Avalonia.Xaml.Interactions.Responsive;
using Avalonia.Xaml.Interactivity;
namespace BD.WTTS.Behaviors;
/// <summary>
/// Observes <see cref="Behavior{T}.AssociatedObject"/> control or <see cref="SourceControl"/> control <see cref="Visual.Bounds"/> property changes and if triggered sets or removes style classes when conditions from <see cref="AdaptiveClassSetter"/> are met.
/// </summary>
public class AdaptiveBehavior : Behavior<Control>
{
private IDisposable? _disposable;
private AvaloniaList<AdaptiveClassSetter>? _setters;
/// <summary>
/// Identifies the <seealso cref="SourceControl"/> avalonia property.
/// </summary>
public static readonly StyledProperty<Control?> SourceControlProperty =
AvaloniaProperty.Register<AdaptiveBehavior, Control?>(nameof(SourceControl));
/// <summary>
/// Identifies the <seealso cref="TargetControl"/> avalonia property.
/// </summary>
public static readonly StyledProperty<Control?> TargetControlProperty =
AvaloniaProperty.Register<AdaptiveBehavior, Control?>(nameof(TargetControl));
/// <summary>
/// Identifies the <seealso cref="Setters"/> avalonia property.
/// </summary>
public static readonly DirectProperty<AdaptiveBehavior, AvaloniaList<AdaptiveClassSetter>> SettersProperty =
AvaloniaProperty.RegisterDirect<AdaptiveBehavior, AvaloniaList<AdaptiveClassSetter>>(nameof(Setters), t => t.Setters);
/// <summary>
/// Gets or sets the the source control that <see cref="Visual.BoundsProperty"/> property are observed from, if not set <see cref="Behavior{T}.AssociatedObject"/> is used. This is a avalonia property.
/// </summary>
[ResolveByName]
public Control? SourceControl
{
get => GetValue(SourceControlProperty);
set => SetValue(SourceControlProperty, value);
}
/// <summary>
/// Gets or sets the target control that class name that should be added or removed when triggered, if not set <see cref="Behavior{T}.AssociatedObject"/> is used or <see cref="AdaptiveClassSetter.TargetControl"/> from <see cref="AdaptiveClassSetter"/>. This is a avalonia property.
/// </summary>
[ResolveByName]
public Control? TargetControl
{
get => GetValue(TargetControlProperty);
set => SetValue(TargetControlProperty, value);
}
/// <summary>
/// Gets adaptive class setters collection. This is a avalonia property.
/// </summary>
[Content]
public AvaloniaList<AdaptiveClassSetter> Setters => _setters ??= new AvaloniaList<AdaptiveClassSetter>();
/// <inheritdoc/>
protected override void OnAttachedToVisualTree()
{
base.OnAttachedToVisualTree();
StopObserving();
StartObserving();
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree()
{
base.OnDetachedFromVisualTree();
StopObserving();
}
private void StartObserving()
{
var sourceControl = GetValue(SourceControlProperty) is not null
? SourceControl
: AssociatedObject;
if (sourceControl is not null)
{
_disposable = ObserveBounds(sourceControl);
}
}
private void StopObserving()
{
_disposable?.Dispose();
}
private IDisposable ObserveBounds(Control sourceControl)
{
if (sourceControl is null)
{
throw new ArgumentNullException(nameof(sourceControl));
}
return sourceControl.GetObservable(Visual.BoundsProperty)
.Subscribe(new AnonymousObserver<Rect>(bounds => ValueChanged(sourceControl, Setters, bounds)));
}
private void ValueChanged(Control? sourceControl, AvaloniaList<AdaptiveClassSetter>? setters, Rect bounds)
{
if (sourceControl is null || setters is null)
{
return;
}
foreach (var setter in setters)
{
var isMinOrMaxWidthSet = setter.IsSet(AdaptiveClassSetter.MinWidthProperty)
|| setter.IsSet(AdaptiveClassSetter.MaxWidthProperty);
var widthConditionTriggered = GetResult(setter.MinWidthOperator, bounds.Width, setter.MinWidth)
&& GetResult(setter.MaxWidthOperator, bounds.Width, setter.MaxWidth);
var isMinOrMaxHeightSet = setter.IsSet(AdaptiveClassSetter.MinHeightProperty)
|| setter.IsSet(AdaptiveClassSetter.MaxHeightProperty);
var heightConditionTriggered = GetResult(setter.MinHeightOperator, bounds.Height, setter.MinHeight)
&& GetResult(setter.MaxHeightOperator, bounds.Height, setter.MaxHeight);
var isAddClassTriggered = isMinOrMaxWidthSet switch
{
true when !isMinOrMaxHeightSet => widthConditionTriggered,
false when isMinOrMaxHeightSet => heightConditionTriggered,
true when isMinOrMaxHeightSet => widthConditionTriggered && heightConditionTriggered,
_ => false
};
var targetControl = setter.GetValue(AdaptiveClassSetter.TargetControlProperty) is not null
? setter.TargetControl
: GetValue(TargetControlProperty) is not null
? TargetControl
: AssociatedObject;
if (targetControl is not null)
{
var className = setter.ClassName;
var isPseudoClass = setter.IsPseudoClass;
if (isAddClassTriggered)
{
Add(targetControl, className, isPseudoClass);
}
else
{
Remove(targetControl, className, isPseudoClass);
}
}
}
}
private bool GetResult(ComparisonConditionType comparisonConditionType, double property, double value)
{
return comparisonConditionType switch
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
ComparisonConditionType.Equal => property == value,
// ReSharper disable once CompareOfFloatsByEqualityOperator
ComparisonConditionType.NotEqual => property != value,
ComparisonConditionType.LessThan => property < value,
ComparisonConditionType.LessThanOrEqual => property <= value,
ComparisonConditionType.GreaterThan => property > value,
ComparisonConditionType.GreaterThanOrEqual => property >= value,
_ => throw new ArgumentOutOfRangeException()
};
}
private static void Add(Control targetControl, string? className, bool isPseudoClass)
{
if (className is null || string.IsNullOrEmpty(className) || targetControl.Classes.Contains(className))
{
return;
}
if (isPseudoClass)
{
((IPseudoClasses)targetControl.Classes).Add(className);
}
else
{
targetControl.Classes.Add(className);
}
}
private static void Remove(Control targetControl, string? className, bool isPseudoClass)
{
if (className is null || string.IsNullOrEmpty(className) || !targetControl.Classes.Contains(className))
{
return;
}
if (isPseudoClass)
{
((IPseudoClasses)targetControl.Classes).Remove(className);
}
else
{
targetControl.Classes.Remove(className);
}
}
}
@@ -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;
}
}
@@ -13,6 +13,7 @@ public static class WindowTransparencyLevelExtensions
WindowBackgroundMaterial.Blur => WindowTransparencyLevel.Blur,
WindowBackgroundMaterial.AcrylicBlur => WindowTransparencyLevel.AcrylicBlur,
WindowBackgroundMaterial.Mica => WindowTransparencyLevel.Mica,
WindowBackgroundMaterial.Transparent => WindowTransparencyLevel.Transparent,
_ => WindowTransparencyLevel.None,
};
}
@@ -11,6 +11,7 @@
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.UI.Views.Windows")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Converters")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Markup")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Behaviors")]
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Services")]
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins")]
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins.Abstractions")]
@@ -95,7 +95,7 @@ public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
Func<bool>? cancelCloseAction = null,
bool disableScroll = false)
where TPageViewModel : ViewModelBase
{
{
var td = new TaskDialogEx
{
Title = title,
@@ -111,6 +111,12 @@ public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
XamlRoot = GetWindowTopLevel(),
};
if (td.XamlRoot == null)
{
Toast.LogAndShowT(new Exception("在 AppWindow 为 Null 时,无法弹出 Taskdialog."));
return false;
}
if (viewModel != null)
{
if (viewModel is IWindowViewModel window)
@@ -44,12 +44,14 @@ sealed class AvaloniaToastServiceImpl : IToastService
if (NotificationManager == null)
{
var host = AvaloniaWindowManagerImpl.GetWindowTopLevel();
NotificationManager = new SnackbarManager(host)
if (host != null)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
NotificationManager = new SnackbarManager(host)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
}
}
var notificationType = GetNotificationType(icon);
+51 -3
View File
@@ -63,10 +63,50 @@ partial class App
#else
s.HasTrayIcon ? ShutdownMode.OnExplicitShutdown : ShutdownMode.OnMainWindowClose;
#endif
#if MACOS
if (Current!.TryGetFeature<IActivatableLifetime>() is { } activatableLifetime)
{
activatableLifetime.Activated += ActivatableLifetimeOnActivated;
// macOS 中 右键隐藏 会触发目前不需要使用
//activatableLifetime.Deactivated += ActivatableLifetimeOnDeactivated;
}
#endif
}
}
#if MACOS
/// <summary>
/// 从 macOS Dock 栏恢复窗口显示
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ActivatableLifetimeOnActivated(object? sender, ActivatedEventArgs e)
{
switch (e.Kind)
{
case ActivationKind.Background:
case ActivationKind.Reopen:
RestoreMainWindow();
break;
}
}
///// <summary>
///// macOS 中 右键隐藏 会触发目前不需要使用
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private void ActivatableLifetimeOnDeactivated(object? sender, ActivatedEventArgs e)
//{
// switch (e.Kind)
// {
// case ActivationKind.Background:
// Console.WriteLine($"ActivatableLifetimeOnDeactivated {e.Kind}");
// break;
// }
//}
#endif
public void UpdateMenuItems()
{
MainThread2.BeginInvokeOnMainThread(() =>
@@ -125,12 +165,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,
+34 -4
View File
@@ -11,6 +11,12 @@ public sealed partial class App : Application
{
const string TAG = "AvaApp";
public App()
{
OpenBrowserCommand = ReactiveCommand.Create<object?>(OpenBrowserCommandCore);
CopyToClipboardCommand = ReactiveCommand.Create<object?>(CopyToClipboardCommandCore);
}
/// <summary>
/// 获取当前主窗口
/// </summary>
@@ -105,6 +111,12 @@ public sealed partial class App : Application
{
Task2.InBackground(() => appWindow.SplashScreen?.RunTasks(CancellationToken.None));
}
IPlatformService.Instance.SetSystemSessionEnding(() =>
{
Log.Info("System Shutdown", "Application SafeExit...");
Shutdown();
});
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
{
@@ -225,16 +237,32 @@ public sealed partial class App : Application
public static FontFamily DefaultFontFamily => _DefaultFontFamily.Value;
public void OpenBrowserCommand(object url)
public ICommand OpenBrowserCommand { get; }
public async void OpenBrowserCommandCore(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)
{
@@ -242,7 +270,9 @@ public sealed partial class App : Application
}
}
public async void CopyToClipboardCommand(object text)
public ICommand CopyToClipboardCommand { get; }
public async void CopyToClipboardCommandCore(object? text)
{
try
{
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

@@ -6,8 +6,8 @@
x:CompileBindings="True">
<Styles.Resources>
<ResourceDictionary>
<sys:Double x:Key="TitleBarHeight">60</sys:Double>
<Thickness x:Key="TitleBarAfterMargin">0,60,0,0</Thickness>
<sys:Double x:Key="TitleBarHeight">50</sys:Double>
<Thickness x:Key="TitleBarAfterMargin">0,50,0,0</Thickness>
<sys:Double x:Key="SplashScreenImageWidth">100</sys:Double>
<Thickness x:Key="PageMargin">10</Thickness>
@@ -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" />
@@ -65,7 +65,7 @@
</spp:AppItem>
<spp:AppItem
Title="Wo Long: Fallen Dynasty (卧龙:苍天陨落)"
Title="Wo Long: Fallen Dynasty (卧龙:苍天陨落)(卧龙:苍天陨落)(卧龙:苍天陨落)(卧龙:苍天陨落)"
MaxHeight="120"
Description="This is a description of whatver this does"
Tags="111">
@@ -181,7 +181,7 @@
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="0,5"
Margin="0,5,12,5"
VerticalAlignment="Top"
Classes="CaptionTextBlockStyle"
FontSize="12"
@@ -7,8 +7,13 @@
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
<StackPanel Spacing="10">
<ui:HyperlinkButton Content="更新日志" />
<HyperlinkButton Content="更新日志" />
<Button Content="更新日志" Theme="{StaticResource HyperlinkButton}" />
<Button Classes="TransparentAccent" Content="更新日志" />
<Button
Classes="TransparentAccent"
Content="更新日志"
IsEnabled="False" />
<Button
Content="更新日志"
IsEnabled="False"
@@ -87,7 +92,7 @@
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="Root"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
@@ -101,19 +106,19 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True" />
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -122,7 +127,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorDisabledBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -131,7 +136,7 @@
</Style>
<Style Selector="^:checked">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundChecked}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -140,7 +145,7 @@
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -149,7 +154,7 @@
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPressed}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -158,7 +163,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorDisabledBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -168,19 +173,19 @@
</Style>
<Style Selector="^:indeterminate">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTransparentBrush}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -189,7 +194,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorDisabledBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -214,10 +219,22 @@
<Style Selector="Button.Plain:pointerover">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.Plain:pointerover /template/ ui|FABorder#Root">
<Style Selector="Button.Plain:pointerover /template/ Border#Root">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.Plain:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.TransparentAccent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkButtonForeground}" />
</Style>
<Style Selector="Button.TransparentAccent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button.TransparentAccent:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkButtonForegroundPressed}" />
</Style>
</Styles>
@@ -63,14 +63,13 @@
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<ItemsRepeater Name="Swiper" IsVisible="False">
<ItemsRepeater.Layout>
<StackLayout
DisableVirtualization="True"
Orientation="Horizontal"
Spacing="8" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Name="Swiper" IsVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
@@ -91,8 +90,8 @@
StrokeThickness="0" />
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Panel>
</Grid>
</Border>
@@ -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>
@@ -46,19 +46,19 @@
<Setter Property="Template">
<ControlTemplate>
<StackPanel>
<ui:FABorder
<Border
Name="LayoutRoot"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Grid MinHeight="{TemplateBinding MinHeight}" ColumnDefinitions="Auto,*,Auto,Auto">
<Viewbox
Name="IconHost"
@@ -113,7 +113,7 @@
VerticalAlignment="Center"
FontSize="18" />
</Grid>
</ui:FABorder>
</Border>
<ContentPresenter
Name="ExpandedContentPresenter"
@@ -144,10 +144,10 @@
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="spp|OptionsDisplayItem /template/ ui|FABorder#LayoutRoot:pointerover">
<Style Selector="spp|OptionsDisplayItem /template/ Border#LayoutRoot:pointerover">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
</Style>
<Style Selector="spp|OptionsDisplayItem:pressed /template/ ui|FABorder#LayoutRoot:pointerover">
<Style Selector="spp|OptionsDisplayItem:pressed /template/ Border#LayoutRoot:pointerover">
<Setter Property="Background" Value="{DynamicResource ControlFillColorTertiaryBrush}" />
</Style>
@@ -179,7 +179,7 @@
<Setter Property="CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="spp|OptionsDisplayItem:expanded /template/ ui|FABorder#LayoutRoot">
<Style Selector="spp|OptionsDisplayItem:expanded /template/ Border#LayoutRoot">
<Setter Property="CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
</Style>
@@ -195,19 +195,19 @@
<Style Selector="spp|OptionsDisplayItem.Vertical">
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="LayoutRoot"
Padding="12"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Grid RowDefinitions="*,*">
<Border
Width="72"
@@ -241,7 +241,7 @@
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
</Style>
@@ -97,7 +97,7 @@
</Design.PreviewWith>
<Styles.Resources>
<Thickness x:Key="AppPageMargin">20</Thickness>
<Thickness x:Key="AppPageMargin">20,10,20,20</Thickness>
</Styles.Resources>
<Style Selector="spp|PageBase">
@@ -313,7 +313,7 @@
<Setter Property="Grid.Row" Value="2" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Margin" Value="0 12" />
<Setter Property="Margin" Value="0 12 0 0" />
</Style>
<Style Selector="^ /template/ Panel#PageDetails">
@@ -38,18 +38,18 @@
<Setter Property="MinHeight" Value="{DynamicResource SettingsExpanderItemMinHeight}" />
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="Root"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Grid ColumnDefinitions="*,Auto,Auto" RowDefinitions="*,Auto">
<Viewbox
@@ -102,7 +102,7 @@
<ContentPresenter Name="ExpandChevronActionIconContainer" Content="{Binding TemplateSettings.ActionIcon, RelativeSource={RelativeSource TemplatedParent}}" />
</Viewbox>
</Grid>
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
@@ -110,7 +110,7 @@
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:nth-last-child(1) /template/ ui|FABorder#Root">
<Style Selector="^:nth-last-child(1) /template/ Border#Root">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
@@ -152,11 +152,11 @@
</Style>
<Style Selector="^:allowClick">
<Style Selector="^:pointerover /template/ ui|FABorder#Root">
<Style Selector="^:pointerover /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
</Style>
<Style Selector="^:pressed /template/ ui|FABorder#Root">
<Style Selector="^:pressed /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorTertiaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -164,7 +164,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDisabledBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
@@ -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>
@@ -95,7 +95,7 @@
<Setter Property="Template">
<ControlTemplate>
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True">
<ui:FABorder
<Border
Name="ContentRoot"
VerticalAlignment="Top"
BorderBrush="{DynamicResource InfoBarBorderBrush}"
@@ -205,13 +205,13 @@
</Button>
</Grid>
</Panel>
</ui:FABorder>
</Border>
</LayoutTransformControl>
</ControlTemplate>
</Setter>
<Style Selector="^:informational">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<!--<Setter Property="Background" Value="{DynamicResource InfoBarInformationalSeverityBackgroundBrush}" />-->
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
</Style>
@@ -226,7 +226,7 @@
</Style>
<Style Selector="^:error">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="Background" Value="{DynamicResource InfoBarErrorSeverityBackgroundBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#IconBackground">
@@ -241,7 +241,7 @@
</Style>
<Style Selector="^:warning">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="Background" Value="{DynamicResource InfoBarWarningSeverityBackgroundBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#IconBackground">
@@ -255,7 +255,7 @@
</Style>
<Style Selector="^:success">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="Background" Value="{DynamicResource InfoBarSuccessSeverityBackgroundBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#IconBackground">
@@ -294,7 +294,7 @@
</Style>
<!-- Visible -->
<Style Selector="^:hidden /template/ ui|FABorder#ContentRoot">
<Style Selector="^:hidden /template/ Border#ContentRoot">
<Setter Property="IsVisible" Value="False" />
</Style>
@@ -75,7 +75,7 @@
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="Root"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
@@ -83,19 +83,19 @@
BorderThickness="1"
CornerRadius="{TemplateBinding CornerRadius}"
TemplatedControl.IsTemplateFocusTarget="True">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Panel>
<ui:FABorder
<Border
Name="Hover"
Margin="3"
Background="Transparent"
CornerRadius="{TemplateBinding CornerRadius}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<TransformOperationsTransition
@@ -103,8 +103,8 @@
Property="RenderTransform"
Duration="00:00:00.167" />
</Transitions>
</ui:FABorder.Transitions>
</ui:FABorder>
</Border.Transitions>
</Border>
<ContentPresenter
Name="PART_ContentPresenter"
@@ -139,19 +139,19 @@
</Rectangle>
</Panel>
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
<Setter Property="RenderTransform" Value="scale(0.96)" />
</Style>
@@ -161,10 +161,10 @@
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
</Style>
@@ -181,10 +181,10 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDisabledBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlAltFillColorSecondaryBrush}" />
</Style>
@@ -80,7 +80,7 @@
<Setter Property="Template">
<ControlTemplate>
<Panel Name="LayoutRoot" Background="{x:Null}">
<ui:FABorder
<Border
Name="ContentRoot"
MinWidth="{DynamicResource TaskDialogMinWidth}"
MinHeight="{DynamicResource TaskDialogMinHeight}"
@@ -216,7 +216,7 @@
</ItemsControl>
</Border>
</Grid>
</ui:FABorder>
</Border>
</Panel>
</ControlTemplate>
</Setter>
@@ -303,7 +303,7 @@
<Style Selector="^ /template/ Panel#LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TaskDialogSmokeFill}" />
</Style>
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="BorderBrush" Value="{DynamicResource TaskDialogBorderBrush}" />
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
<Setter Property="BorderThickness" Value="{DynamicResource TaskDialogBorderWidth}" />
@@ -332,7 +332,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:hidden /template/ ui|FABorder#ContentRoot">
<Style Selector="^:hidden /template/ Border#ContentRoot">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.167">
<KeyFrame Cue="0%">
@@ -368,7 +368,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:open /template/ ui|FABorder#ContentRoot">
<Style Selector="^:open /template/ Border#ContentRoot">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.250">
<KeyFrame Cue="0%">
@@ -389,7 +389,7 @@
<Setter Property="Template">
<ControlTemplate>
<Panel Name="LayoutRoot" Background="{x:Null}">
<ui:FABorder
<Border
Name="ContentRoot"
MinWidth="{DynamicResource TaskDialogMinWidth}"
MinHeight="{DynamicResource TaskDialogMinHeight}"
@@ -521,7 +521,7 @@
</ItemsControl>
</Border>
</Grid>
</ui:FABorder>
</Border>
</Panel>
</ControlTemplate>
</Setter>
@@ -14,4 +14,5 @@
</Styles.Resources>
<StyleInclude Source="/UI/Styling/Controls.axaml" />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
</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" />
@@ -85,4 +86,10 @@
<StreamGeometry x:Key="Phone">
M15.75 2C16.9926 2 18 3.00736 18 4.25V19.75C18 20.9926 16.9926 22 15.75 22H8.25C7.00736 22 6 20.9926 6 19.75V4.25C6 3.00736 7.00736 2 8.25 2H15.75ZM13.25 18H10.75C10.3358 18 10 18.3358 10 18.75C10 19.1642 10.3358 19.5 10.75 19.5H13.25C13.6642 19.5 14 19.1642 14 18.75C14 18.3358 13.6642 18 13.25 18Z
</StreamGeometry>
<StreamGeometry x:Key="GOGGalaxy">
M304.99296 650.144H185.98496a17.12 17.12 0 0 0-16.992 17.184v85.344c0 8.992 7.68 16.992 16.992 16.992h119.52v56.32H156.16096a43.84 43.84 0 0 1-43.488-44v-144.512c0-23.968 19.68-43.488 44-43.488h148.32z m43.168-157.824c0 24.832-20 44.832-44.832 44.832H112.19296v-57.696h161.312a16.896 16.896 0 0 0 17.024-16.992V272.64a17.024 17.024 0 0 0-17.024-17.184H187.36096a17.152 17.152 0 0 0-17.536 17.184v86.144c0 9.856 7.68 17.024 17.024 17.024H256.00096v57.632H157.02496a44.544 44.544 0 0 1-44.832-44.32V242.304c0-24.352 20.192-44.32 44.832-44.32H303.04096c24.672 0 44.64 19.968 44.64 44.32v249.984z m563.168 333.664h-56.32v-175.84h-39.68a17.12 17.12 0 0 0-16.992 17.184v158.624h-56.832v-175.84h-39.68a17.12 17.12 0 0 0-16.992 17.184v158.624h-56.832v-188.512c0-23.968 19.68-43.488 44-43.488h239.328z m0.512-333.664c0 24.832-20.192 44.832-44.832 44.832h-191.136v-57.696h161.312a16.96 16.96 0 0 0 17.024-16.992V272.64a17.12 17.12 0 0 0-17.024-17.184h-86.656a17.024 17.024 0 0 0-17.024 17.184v86.144c0 9.856 7.68 17.024 17.024 17.024h69.152v57.632h-98.688a44.352 44.352 0 0 1-44.64-44.32V242.304c0-24.352 19.968-44.32 44.64-44.32h146.336c24.832 0 44.832 19.968 44.832 44.32v249.984zM585.34496 198.016h-146.688c-24.832 0-44.512 19.968-44.512 44.32v146.816a44.16 44.16 0 0 0 44.512 44.32h146.688a44.48 44.48 0 0 0 44.512-44.32V242.336c0-24.352-20.192-44.32-44.512-44.32z m-12.672 74.656v86.144c0 9.504-7.68 17.024-17.152 17.024h-86.688a16.96 16.96 0 0 1-16.992-17.024V272.96c0-9.312 7.328-16.992 16.992-16.992h85.856c9.824 0 16.992 7.68 16.992 16.992z m-33.824 321.344h-144.672c-24.16 0-43.808 19.488-43.808 43.488v144.512c0 24.32 19.68 44 43.808 44h144.672c24.32 0 44-19.68 44-44v-144.512c0-23.968-19.68-43.488-44-43.488z m-12.864 73.344v85.344a16.96 16.96 0 0 1-16.992 16.992v-0.352h-84.864a17.12 17.12 0 0 1-17.152-17.152v-84.832a17.28 17.28 0 0 1 17.152-17.184h85.344c9.344 0 17.024 7.84 17.024 17.184z m476.16-620.352A74.24 74.24 0 0 0 949.31296 24.64H74.65696A74.304 74.304 0 0 0 0.00096 99.296v825.344a74.304 74.304 0 0 0 74.656 74.656h874.656a74.304 74.304 0 0 0 74.656-74.656V99.296c0-20.32-8.48-39.168-21.856-52.832z m0 877.664a52.416 52.416 0 0 1-52.832 52.832H74.65696a52.576 52.576 0 0 1-53.344-52.832V99.328a52.672 52.672 0 0 1 52.832-52.832H948.80096c29.344 0 52.992 23.68 52.992 52.832v825.344z
</StreamGeometry>
<StreamGeometry x:Key="Discord">
M1049.062 139.672a3 3 0 0 0-1.528-1.4A970.13 970.13 0 0 0 808.162 64.06a3.632 3.632 0 0 0-3.846 1.82 674.922 674.922 0 0 0-29.8 61.2 895.696 895.696 0 0 0-268.852 0 619.082 619.082 0 0 0-30.27-61.2 3.78 3.78 0 0 0-3.848-1.82 967.378 967.378 0 0 0-239.376 74.214 3.424 3.424 0 0 0-1.576 1.352C78.136 367.302 36.372 589.38 56.86 808.708a4.032 4.032 0 0 0 1.53 2.75 975.332 975.332 0 0 0 293.65 148.378 3.8 3.8 0 0 0 4.126-1.352A696.4 696.4 0 0 0 416.24 860.8a3.72 3.72 0 0 0-2.038-5.176 642.346 642.346 0 0 1-91.736-43.706 3.77 3.77 0 0 1-0.37-6.252 502.094 502.094 0 0 0 18.218-14.274 3.638 3.638 0 0 1 3.8-0.512c192.458 87.834 400.82 87.834 591 0a3.624 3.624 0 0 1 3.848 0.466 469.066 469.066 0 0 0 18.264 14.32 3.768 3.768 0 0 1-0.324 6.252 602.814 602.814 0 0 1-91.78 43.66 3.75 3.75 0 0 0-2 5.222 782.11 782.11 0 0 0 60.028 97.63 3.728 3.728 0 0 0 4.126 1.4A972.096 972.096 0 0 0 1221.4 811.458a3.764 3.764 0 0 0 1.53-2.704c24.528-253.566-41.064-473.824-173.868-669.082zM444.982 675.16c-57.944 0-105.688-53.174-105.688-118.478s46.818-118.482 105.688-118.482c59.33 0 106.612 53.64 105.686 118.478 0 65.308-46.82 118.482-105.686 118.482z m390.76 0c-57.942 0-105.686-53.174-105.686-118.478s46.818-118.482 105.686-118.482c59.334 0 106.614 53.64 105.688 118.478 0 65.308-46.354 118.482-105.688 118.482z
</StreamGeometry>
</ResourceDictionary>
@@ -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>
@@ -210,7 +210,7 @@
<wnd:MinMaxCloseControl
Name="SystemCaptionButtons"
Height="{Binding TemplateSettings.TitleBarHeight, RelativeSource={RelativeSource TemplatedParent}}"
Margin="18,0"
Margin="14,0"
HorizontalAlignment="Right"
VerticalAlignment="Top" />
@@ -1,5 +1,7 @@
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Rendering.Composition;
using FluentAvalonia.UI.Controls.Experimental;
using FluentAvalonia.UI.Navigation;
namespace BD.WTTS.UI.Views.Pages;
@@ -7,6 +9,8 @@ public class PageBase : UserControl
{
public PageBase() : base()
{
_disposables = new();
SizeChanged += PageBaseSizeChanged;
if (HeaderBackground == null)
@@ -14,6 +18,8 @@ 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);
}
@@ -139,6 +145,7 @@ public class PageBase : UserControl
//_previewImageHost = e.NameScope.Find<IconSourceElement>("PreviewImageElement");
//_detailsHost = e.NameScope.Find<StackPanel>("DetailsTextHost");
_optionsHost = e.NameScope.Find<Panel>("OptionsRegion");
_tabsHost = e.NameScope.Find<Border>("TabRegion");
_detailsPanel = e.NameScope.Find<Panel>("PageDetails");
@@ -157,6 +164,7 @@ public class PageBase : UserControl
{
base.OnUnloaded(e);
_hasLoaded = false;
_disposables.Dispose();
}
private void PageBaseSizeChanged(object? sender, SizeChangedEventArgs e)
@@ -266,33 +274,28 @@ public class PageBase : UserControl
ec.ImplicitAnimations = ani;
}
//private void FrameNavigatingFrom(object sender, NavigatingCancelEventArgs e)
//private void FrameNavigatingFrom(object? sender, NavigatingCancelEventArgs e)
//{
// // If TargetType is not set, we know we're currently on a CoreControls page since those
// // are grouped pages - whereas, FA controls only display one control per page and
// // set all the extra properties
// bool isFAControlPage = TargetType != null;
// // Only setup the ConnectedAnimation if it makes sense
// if ((!isFAControlPage && e.SourcePageType == typeof(CoreControlsPageViewModel)) ||
// (isFAControlPage && e.SourcePageType == typeof(FAControlsOverviewPageViewModel)))
// if ((e.SourcePageType == typeof(HomePageViewModel)) ||
// (e.SourcePageType == typeof(PluginStorePageViewModel)))
// {
// // Only setup the Back connected animation if we're going back to the
// // controls list pages
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// svc.PrepareToAnimate("BackAnimation", (Control)_previewImageHost.Parent);
// NavigationService.Instance.PreviousPage = this;
// //NavigationService.Instance.PreviousPage = this;
// }
//}
//private void FrameNavigatedTo(object sender, NavigationEventArgs e)
//private void FrameNavigatedTo(object? sender, NavigationEventArgs e)
//{
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// var animation = svc.GetAnimation("ForwardAnimation");
// if (animation != null)
// {
// var coordinated = new List<Visual>
// var coordinated = new List<Visual?>
// {
// _optionsHost,
// _detailsHost,
@@ -315,6 +318,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 +328,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);
}
@@ -14,6 +14,8 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
public static readonly StyledProperty<bool> IsSaveWindowSizeProperty = AvaloniaProperty
.Register<ReactiveAppWindow<TViewModel>, bool>(nameof(IsSaveWindowSize), true);
private bool _isInitialize;
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveAppWindow{TViewModel}"/> class.
/// </summary>
@@ -127,27 +129,32 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
{
var primaryScreenBounds = screen.Bounds;
if (CanResize && !IsVisible)
if (CanResize && !_isInitialize && !IsVisible)
{
WindowStartupLocation = WindowStartupLocation.Manual;
var scalingOffset = 31 - ((screen.Scaling - 1) / 0.5); // appwindow偏移量
if (SizePosition.Width > 0 &&
primaryScreenBounds.Width >= SizePosition.Width)
Width = SizePosition.Width;
if (SizePosition.Height > 0 &&
primaryScreenBounds.Height >= SizePosition.Height)
Height = SizePosition.Height;
Height = SizePosition.Height - scalingOffset;
if (SizePosition.X > 0 && SizePosition.Y > 0)
{
var leftTopPoint = new PixelPoint(SizePosition.X, SizePosition.Y);
var offsetY = (int)Math.Round(scalingOffset * screen.Scaling);
//var offsetY = scalingOffset;
var leftTopPoint = new PixelPoint(SizePosition.X, SizePosition.Y + offsetY);
var rightBottomPoint = new PixelPoint(SizePosition.X + (int)Width, SizePosition.Y + (int)Height);
if (primaryScreenBounds.Contains(leftTopPoint) &&
primaryScreenBounds.Contains(rightBottomPoint))
{
Position = leftTopPoint;
WindowStartupLocation = WindowStartupLocation.Manual;
}
}
_isInitialize = true;
}
}
}
@@ -126,7 +126,7 @@ public class OptionsDisplayItem : TemplatedControl
{
base.OnApplyTemplate(e);
_layoutRoot = e.NameScope.Find<FABorder>("LayoutRoot");
_layoutRoot = e.NameScope.Find<Border>("LayoutRoot");
if (_layoutRoot != null)
{
_layoutRoot.PointerPressed += OnLayoutRootPointerPressed;
@@ -172,5 +172,5 @@ public class OptionsDisplayItem : TemplatedControl
private bool _isPressed;
private bool _isExpanded;
private FABorder? _layoutRoot;
private Border? _layoutRoot;
}
@@ -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);
}
}
@@ -61,6 +61,10 @@ public class SnackbarManager : TemplatedControl, IManagedNotificationManager
{
Install(host);
}
else
{
Log.Error("SnackbarManager", "Install Faild, host is null");
}
UpdatePseudoClasses(Position);
}
@@ -193,4 +197,39 @@ public class SnackbarManager : TemplatedControl, IManagedNotificationManager
PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
}
/// <inheritdoc/>
public void Close(INotification notification)
{
Dispatcher.UIThread.VerifyAccess();
//if (_items.Remove(notification, out var notificationCard))
//{
// notificationCard.Close();
//}
}
/// <inheritdoc/>
public void Close(object content)
{
Dispatcher.UIThread.VerifyAccess();
//if (_items.Remove(content, out var notificationCard))
//{
// notificationCard.Close();
//}
}
/// <inheritdoc/>
public void CloseAll()
{
Dispatcher.UIThread.VerifyAccess();
//foreach (var kvp in _items)
//{
// kvp.Close();
//}
_items?.Clear();
}
}
@@ -62,14 +62,13 @@
Margin="0,0,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<ItemsRepeater Name="Swiper" IsVisible="False">
<ItemsRepeater.Layout>
<StackLayout
DisableVirtualization="True"
Orientation="Horizontal"
Spacing="8" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Name="Swiper" IsVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
@@ -110,8 +109,8 @@
</i:Interaction.Behaviors>-->
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Panel>
</Panel>
</Border>
@@ -55,7 +55,7 @@ public class CarouselItems : ItemsControl
Carousel? _carouselControl;
Button? _leftButton;
Button? _rightButton;
ItemsRepeater? _swiper;
ItemsControl? _swiper;
Timer? _timer;
public ICommand? CarouselBannerIndexCommand { get; }
@@ -72,7 +72,7 @@ public class CarouselItems : ItemsControl
_carouselControl = e.NameScope.Find<Carousel>("CarouselControl");
_leftButton = e.NameScope.Find<Button>("Left");
_rightButton = e.NameScope.Find<Button>("Right");
_swiper = e.NameScope.Find<ItemsRepeater>("Swiper");
_swiper = e.NameScope.Find<Avalonia.Controls.ItemsControl>("Swiper");
if (_leftButton != null)
{
@@ -85,44 +85,36 @@ public class FixedWrapPanel : Panel, INavigableContainer
/// <inheritdoc/>
protected override Size MeasureOverride(Size constraint)
{
var panelSize = default(MutableSize);
var lineSize = default(MutableSize);
int itemsInCurrentLine = 0;
double itemWidth = (constraint.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
MutableSize currentLineSize = default;
MutableSize panelSize = default;
Size lineConstraint = new(constraint.Width, constraint.Height);
Size childConstraint = new(itemWidth - Spacing, constraint.Height);
for (int i = 0, count = Children.Count; i < count; i++)
foreach (var child in Children.OfType<Control>())
{
Control child = Children[i];
if (child is null)
{
continue;
}
child.Measure(new Size(itemWidth, constraint.Height));
var childSize = child.DesiredSize;
child.Measure(childConstraint);
Size childSize = new(itemWidth, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width + Spacing, lineConstraint.Width))
if (itemsInCurrentLine == ItemsPerLine)
{
// Need to switch to another line
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
panelSize.Height += currentLineSize.Height;
currentLineSize = new(childSize);
// Start a new line
panelSize.Width = Math.Max(panelSize.Width, lineSize.Width);
panelSize.Height += lineSize.Height + Spacing;
lineSize = new MutableSize(childSize.Width, childSize.Height);
itemsInCurrentLine = 1;
}
else
{
// Continue to accumulate a line
currentLineSize.Width += childSize.Width + Spacing;
currentLineSize.Height = Max(childSize.Height, currentLineSize.Height);
// Add to the current line
lineSize.Width += childSize.Width + (itemsInCurrentLine > 0 ? Spacing : 0);
lineSize.Height = Math.Max(lineSize.Height, childSize.Height);
itemsInCurrentLine++;
}
}
// The last line size, if any should be added
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
panelSize.Height = currentLineSize.Height;
if (double.IsInfinity(panelSize.Width))
panelSize.Width = 0;
// Add the last line size
panelSize.Width = Math.Max(panelSize.Width, lineSize.Width);
panelSize.Height += lineSize.Height;
return panelSize.ToSize();
}
@@ -130,41 +122,33 @@ public class FixedWrapPanel : Panel, INavigableContainer
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
double itemWidth = (finalSize.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
int firstInLine = 0;
double accumulatedHeight = 0;
var currentLineSize = default(MutableSize);
for (int i = 0; i < Children.Count; i++)
var itemWidth = (finalSize.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
var position = default(MutablePoint);
var lineHeight = 0.0;
int itemsInCurrentLine = 0;
try
{
Control child = Children[i];
if (child == null)
foreach (var child in Children.OfType<Control>())
{
continue;
}
if (itemsInCurrentLine == ItemsPerLine)
{
// Move to the next line
position.X = 0;
position.Y += lineHeight + Spacing;
lineHeight = 0;
itemsInCurrentLine = 0;
}
MutableSize itemSize = new(itemWidth, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(currentLineSize.Width + itemSize.Width, finalSize.Width))
{
// Need to switch to another line
ArrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, i, itemWidth);
accumulatedHeight += currentLineSize.Height;
currentLineSize = itemSize;
firstInLine = i;
}
else
{
// Continue to accumulate a line
currentLineSize.Width += itemSize.Width;
currentLineSize.Height = Max(itemSize.Height, currentLineSize.Height);
var childSize = new Size(itemWidth, child.DesiredSize.Height);
child.Arrange(new Rect(position.ToPoint(), childSize));
position.X += itemWidth + Spacing;
lineHeight = Math.Max(lineHeight, childSize.Height);
itemsInCurrentLine++;
}
}
if (firstInLine < Children.Count)
{
// Arrange the last line, if any
ArrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, Children.Count, itemWidth);
}
catch
{ }
return finalSize;
}
@@ -180,19 +164,32 @@ public class FixedWrapPanel : Panel, INavigableContainer
continue;
}
if (i < end - 1)
try
{
child.Arrange(new Rect(x, y, width - Spacing, height));
x += width + Spacing;
}
else
{
child.Arrange(new Rect(x, y, width, height));
x += width;
if (i < end - 1 || i % ItemsPerLine == 0)
{
child.Arrange(new Rect(x, y, width - Spacing, height));
x += width + Spacing;
}
else
{
child.Arrange(new Rect(x, y, width, height));
x += width;
}
}
catch { }
}
}
record struct MutablePoint
{
internal double X;
internal double Y;
internal readonly Point ToPoint() => new Point(X, Y);
}
record struct MutableSize
{
internal MutableSize(double width, double height)
@@ -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);
}
}
}
@@ -139,20 +139,24 @@
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding Path=Res.Link, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding Hyperlinks, Mode=OneTime}">
<ItemsRepeater.Layout>
<UniformGridLayout MinItemWidth="166" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Margin="0,0,6,6" ItemsSource="{Binding Hyperlinks, Mode=OneTime}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="spp:HyperlinkModel">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
<Border MinWidth="166">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border
Height="1"
Margin="-50,0"
@@ -162,20 +166,24 @@
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding Path=Res.OpenSourceLibrary, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding OSL, Mode=OneTime}">
<ItemsRepeater.Layout>
<UniformGridLayout MinItemWidth="290" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Margin="0,0,6,6" ItemsSource="{Binding OSL, Mode=OneTime}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="spp:HyperlinkModel">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
<Border MinWidth="290">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
@@ -12,6 +12,7 @@
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="spp:DebugPageViewModel"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
mc:Ignorable="d">
<Design.DataContext>
<spp:DebugPageViewModel />
@@ -134,11 +135,15 @@
<ItemsControl>
<ItemsControl.Items>
<Border Height="30" Background="Red" />
<Border Background="Blue" />
<Border Background="Yellow" />
<Border Height="30" Background="Blue" />
<Border Height="30" Background="Yellow" />
<Border Background="Red" />
<Border Background="Blue" />
<Border Height="30" Background="Yellow" />
<Border Height="30" Background="Red" />
<Border Height="30" Background="Blue" />
<Border Height="30" Background="Blue" />
<Border Height="30" Background="Blue" />
<Border Height="30" Background="Blue" />
<Border Height="30" Background="Red" />
</ItemsControl.Items>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
@@ -149,7 +154,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"
@@ -74,13 +74,18 @@
MaxWidth="1080"
Spacing="20">
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{spp:OnPlatform !GA}"
Message="当前使用的程序版本为测试版本,可能会有各种各样不可言明的 Bug,遇到问题请理性反馈,谢谢。"
Severity="Warning" />
<spp:Snackbar
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{spp:OnPlatform !GA}"
Message="如果您不喜欢当前主页为默认启动页面可以在【设置-界面设置-启动时默认打开页面】处更改" />
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top"
@@ -279,60 +284,61 @@
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}"
MinHeight="250"
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>
@@ -24,10 +24,10 @@
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=False] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 50 0 0" />
<Setter Property="Margin" Value="0 42 0 0" />
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=True] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 10 0 0" />
<Setter Property="Margin" Value="0 2 0 0" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav">
@@ -216,9 +216,23 @@
<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"
HorizontalAlignment="Left"
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 +277,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}}"
@@ -318,7 +332,7 @@
PaneDisplayMode="Left">
<ui:NavigationView.Resources>
<!-- Override this -->
<Thickness x:Key="NavigationViewContentMargin">0,60,0,0</Thickness>
<Thickness x:Key="NavigationViewContentMargin">0,50,0,0</Thickness>
</ui:NavigationView.Resources>
<ui:NavigationView.Styles>
<Style Selector="Button#NavigationViewBackButton">
@@ -329,7 +343,7 @@
<Setter Property="Content" Value="&#xF0D5;" />
</Style>
<Style Selector="Grid#PaneToggleButtonGrid">
<Setter Property="Margin" Value="5 5 0 0" />
<Setter Property="Margin" Value="5 0 0 0" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="ToolTip.Placement" Value="Bottom" />
</Style>
@@ -0,0 +1,88 @@
<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:PluginStorePageViewModel"
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 版本的计划是将所有功能插件化,拆分后按需下载安装,每个人都可以只下载自己需要的插件功能使用,并且支持所有人都可以开发插件,等等还有许多暂时不方便说的内容,因为计划的过于异想天开,且遇到的问题有点多,所以开发难度有点点大,这个功能上线还需要一点时间,上线后 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:PluginStorePage].((spp:PluginStorePageViewModel)DataContext).NavgationToMenuPageCommand}"
CommandParameter="{Binding}"
Theme="{StaticResource NavButtonStyle}">
<Grid RowDefinitions="3*,*">
<Border
Name="IconHost"
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,92 @@
using FluentAvalonia.UI.Controls.Experimental;
using FluentAvalonia.UI.Navigation;
namespace BD.WTTS.UI.Views.Pages;
public partial class PluginStorePage : UserControl
{
public PluginStorePage()
{
InitializeComponent();
this.SetViewModel<PluginStorePageViewModel>(useCache: true);
// Use the frame events here to ensure ConnectedAnimations still work with
// Back/Forward navigation and not just explicit page invokes
//AddHandler(Frame.NavigatingFromEvent, OnNavigatingFrom, RoutingStrategies.Direct);
//AddHandler(Frame.NavigatedToEvent, OnNavigatedTo, RoutingStrategies.Direct);
}
//private void PluginStorePage_Click(object? sender, RoutedEventArgs e)
//{
// if (sender != null && sender is Button b && b.DataContext is TabItemViewModel fci)
// {
// _animationPage = fci;
// //NavigationService.Instance.NavigateFromContext(fci);
// }
//}
//private void OnNavigatedTo(object? sender, NavigationEventArgs e)
//{
// if (_animationPage == null)
// return;
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// var anim = svc.GetAnimation("BackAnimation");
// if (anim == null)
// return;
// var item = this.GetVisualDescendants()
// ?.Where(x => x is Button && x.DataContext == _animationPage)
// ?.FirstOrDefault()
// ?.GetVisualDescendants()
// .Where(x => x is Border && x.Name == "IconHost")
// .FirstOrDefault();
// var presenter = item;// GetAnimationSource();
// // In WinUI, ConnectedAnimation is somehow exempt from all clipping behaviors
// // Here, we are not, so disable ClipToBounds on all elements in the SettingsExpander
// // The rest are taken care of in the xaml.
// // NOTE: The ScrollViewer is not changed here as that's important for scrolling - thus
// // the animation will be cut off, but the back animation is pretty fast and mostly is
// // only visible closer to the element so we're ok, I think
// var x = presenter?.GetVisualParent();
// while (!(x is ScrollContentPresenter) && x != null)
// {
// x.ClipToBounds = false;
// x = x.GetVisualParent();
// }
// anim.Configuration = new DirectConnectedAnimationConfiguration();
// anim.TryStart(presenter);
//}
//private void OnNavigatingFrom(object? sender, NavigatingCancelEventArgs e)
//{
// if (_animationPage == null)
// return;
// // We're not navigating to a control page, don't set up the animation & clear
// // the previous animation source
// if (!e.SourcePageType.Name.Equals(nameof(TabItemViewModel)))
// {
// _animationPage = null;
// _animationPage = null;
// return;
// }
// var item = this.GetVisualDescendants()
// ?.Where(x => x is ListBoxItem && x.DataContext == _animationPage)
// ?.FirstOrDefault()
// ?.GetVisualDescendants()
// .Where(x => x is Viewbox && x.Name == "IconHost")
// .FirstOrDefault();
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// svc.PrepareToAnimate("ForwardAnimation", item);
//}
//private TabItemViewModel? _animationPage;
}
@@ -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}">
@@ -344,6 +341,7 @@
<ComboBox SelectedItem="{Binding Source={x:Static ms:UISettings.WindowBackgroundMaterial}, Path=Value, Mode=TwoWay}">
<ComboBox.Items>
<spp:WindowBackgroundMaterial>None</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>Transparent</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>Blur</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>AcrylicBlur</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>Mica</spp:WindowBackgroundMaterial>
@@ -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;
}
@@ -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",
},
@@ -311,10 +311,29 @@ sealed class IPCSubProcessFileSystem : IOPath.FileSystemBase
config.LoggingRules.Add(new LoggingRule("*", minLevel, target));
}
public static string GetLogDirPath(string? moduleName = null, string? cacheDirectory = null)
{
cacheDirectory ??= IOPath.CacheDirectory;
string logDirPath;
if (moduleName == null)
{
logDirPath = Path.Combine(cacheDirectory, LogDirName);
}
else
{
var dirName = GetDirectoryName(moduleName);
logDirPath = Path.Combine(cacheDirectory, LogDirName, dirName);
}
IOPath.DirCreateByNotExists(logDirPath);
return logDirPath;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitLog(
ref string? logDirPath,
string? mainProcessModuleName = null,
string? moduleName = null,
string? alias = null,
string? cacheDirectory = null
#if STARTUP_WATCH_TRACE || DEBUG
@@ -335,16 +354,8 @@ sealed class IPCSubProcessFileSystem : IOPath.FileSystemBase
cacheDirectory ??= IOPath.CacheDirectory;
if (mainProcessModuleName == null)
{
logDirPath = Path.Combine(cacheDirectory, LogDirName);
}
else
{
var dirName = GetDirectoryName(mainProcessModuleName);
logDirPath = Path.Combine(cacheDirectory, LogDirName, dirName);
}
IOPath.DirCreateByNotExists(logDirPath);
logDirPath = GetLogDirPath(moduleName, cacheDirectory);
#if (STARTUP_WATCH_TRACE || DEBUG) && !LIB_CLIENT_IPC
if (watchTrace) Startup.WatchTrace.Record("InitLog.IOPath");
#endif
@@ -76,6 +76,9 @@
<Compile Include="..\BD.WTTS.Client\Services\Platform\IPlatformService.OS.Linux.Etc.Os_Release.cs">
<LinkBase>Services\Platform</LinkBase>
</Compile>
<Compile Include="..\BD.WTTS.Client\Services\Mvvm\LogConsoleService.Logger.cs">
<LinkBase>Services\Mvvm</LinkBase>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -104,6 +107,7 @@
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Runtime.Caching" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="Utf8StringInterpolation" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
@@ -123,6 +123,7 @@ public static class KestrelServerOptionsExtensions
var httpsPort = IReverseProxyConfig.HttpsPort;
options.Listen(IReverseProxyService.Constants.Instance.ProxyIp, httpsPort, listen =>
{
listen.Protocols = HttpProtocols.Http1AndHttp2;
listen.UseFlowAnalyze();
listen.UseTls();
});
@@ -20,7 +20,7 @@ public partial interface IReverseProxyConfig
/// </summary>
/// <param name="domain"></param>
/// <returns></returns>
bool IsMatch(string domain) => TryGetDomainConfig(domain, out _);
bool IsMatch(string url) => TryGetDomainConfig(url, out _);
/// <summary>
/// 尝试获取域名配置
@@ -28,7 +28,7 @@ public partial interface IReverseProxyConfig
/// <param name="domain"></param>
/// <param name="value"></param>
/// <returns></returns>
bool TryGetDomainConfig(string domain, [MaybeNullWhen(false)] out IDomainConfig value);
bool TryGetDomainConfig(string url, [MaybeNullWhen(false)] out IDomainConfig value);
/// <summary>
/// 尝试获取脚本配置
@@ -36,7 +36,7 @@ public partial interface IReverseProxyConfig
/// <param name="domain"></param>
/// <param name="value"></param>
/// <returns></returns>
bool TryGetScriptConfig(string domain, [MaybeNullWhen(false)] out IEnumerable<IScriptConfig> value);
bool TryGetScriptConfig(string url, [MaybeNullWhen(false)] out IEnumerable<IScriptConfig> value);
/// <summary>
/// 尝试获取脚本内容
@@ -148,26 +148,26 @@ sealed class ReverseProxyConfig : IReverseProxyConfig
// return result;
//}
public bool TryGetDomainConfig(string domain, [MaybeNullWhen(false)] out IDomainConfig value)
public bool TryGetDomainConfig(string url, [MaybeNullWhen(false)] out IDomainConfig value)
{
//value = domainConfigCache.GetOrAdd(domain.Host, GetDomainConfig);
var uri = new UriBuilder(domain).Uri;
var uri = new UriBuilder(url).Uri;
domainConfigCache.TryGetValue(uri.Host, out value);
if (value != null)
return true;
value = GetDomainConfig(domain);
value = GetDomainConfig(url);
if (value == null)
return false;
domainConfigCache.TryAdd(uri.Host, value);
return true;
IDomainConfig? GetDomainConfig(string domain)
IDomainConfig? GetDomainConfig(string url)
{
var key = domainConfigs.Keys.FirstOrDefault(item => item.IsMatch(domain));
var key = domainConfigs.Keys.FirstOrDefault(item => item.IsMatch(url));
return key == null ? null : domainConfigs[key];
}
}
@@ -81,6 +81,7 @@ static void ConfigureServices(IServiceCollection services)
#if DEBUG
l.AddConsole();
#endif
l.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(moduleName));
});
// 设置仓储层数据库文件存放路径
@@ -155,7 +155,21 @@ public static class CertGenerator
}
else
{
builder.AddDnsName(name);
try
{
builder.AddDnsName(name);
}
catch
{
/* name = Environment.MachineName
* System.ArgumentException: Decoded string is not a valid IDN name. (Parameter 'unicode')
* at System.Globalization.IdnMapping.IcuGetAsciiCore(String unicodeString, Char* unicode, Int32 count)
* at System.Globalization.IdnMapping.GetAscii(String unicode, Int32 index, Int32 count)
* at System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder.AddDnsName(String dnsName)
* at BD.WTTS.Services.Implementation.CertGenerator.Add(SubjectAlternativeNameBuilder builder, String name)
* at BD.WTTS.Services.Implementation.CertGenerator.CreateEndCertificate(X509Certificate2 issuerCertificate, X500DistinguishedName subjectName, IEnumerable`1 extraDnsNames, Nullable`1 notBefore, Nullable`1 notAfter, Int32 rsaKeySizeInBits)
*/
}
}
}
@@ -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();
}
}
@@ -12,10 +12,10 @@ sealed class LifetimeHttpHandler : DelegatingHandler
public LifeTimeKey LifeTimeKey { get; }
public LifetimeHttpHandler(IDomainResolver domainResolver, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
public LifetimeHttpHandler(IDomainResolver domainResolver, IWebProxy webProxy, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
{
LifeTimeKey = lifeTimeKey;
InnerHandler = new ReverseProxyHttpClientHandler(lifeTimeKey.DomainConfig, domainResolver);
InnerHandler = new ReverseProxyHttpClientHandler(lifeTimeKey.DomainConfig, domainResolver, webProxy);
timer = new Timer(OnTimerCallback, deactivateAction, lifeTime, Timeout.InfiniteTimeSpan);
}
@@ -15,7 +15,7 @@ public class ReverseProxyHttpClient : HttpMessageInvoker
// = new(new ProductHeaderValue(Constants.HARDCODED_APP_NAME_NEW + "_" + DateTimeOffset.UtcNow.Ticks, ThisAssembly.Version));
public ReverseProxyHttpClient(IDomainConfig domainConfig, IDomainResolver domainResolver)
: this(new ReverseProxyHttpClientHandler(domainConfig, domainResolver), disposeHandler: true)
: this(new ReverseProxyHttpClientHandler(domainConfig, domainResolver, HttpNoProxy.Instance), disposeHandler: true)
{
}
@@ -7,12 +7,14 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
{
readonly IDomainConfig domainConfig;
readonly IDomainResolver domainResolver;
readonly IWebProxy webProxy;
readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
public ReverseProxyHttpClientHandler(IDomainConfig domainConfig, IDomainResolver domainResolver)
public ReverseProxyHttpClientHandler(IDomainConfig domainConfig, IDomainResolver domainResolver, IWebProxy webProxy)
{
this.domainConfig = domainConfig;
this.domainResolver = domainResolver;
this.webProxy = webProxy;
InnerHandler = CreateSocketsHttpHandler();
}
@@ -32,7 +34,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;
@@ -53,12 +55,199 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
{
Proxy = HttpNoProxy.Instance,
UseProxy = false,
PreAuthenticate = false,
UseCookies = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
ConnectCallback = ConnectCallback,
EnableMultipleHttp2Connections = true,
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8,
ResponseHeaderEncodingSelector = (_, _) => Encoding.UTF8,
};
private async ValueTask<Stream> ConnectThroughProxyAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
if (context.InitialRequestMessage.RequestUri == null)
{
throw new InvalidOperationException("Request URI is null");
}
var proxyUri = webProxy.GetProxy(context.InitialRequestMessage.RequestUri);
if (proxyUri == null)
{
throw new InvalidOperationException("Proxy URI is null");
}
var proxyEndPoint = new DnsEndPoint(proxyUri.Host, proxyUri.Port);
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(proxyEndPoint, cancellationToken);
var stream = new NetworkStream(socket, ownsSocket: true);
// 确定代理类型
var proxyType = DetermineProxyType(proxyUri);
switch (proxyType)
{
case ExternalProxyType.Http:
await ConnectHttpProxy(stream, context, cancellationToken);
break;
case ExternalProxyType.Socks4:
await ConnectSocks4Proxy(stream, context, cancellationToken);
break;
case ExternalProxyType.Socks5:
await ConnectSocks5Proxy(stream, context, cancellationToken);
break;
}
if (context.InitialRequestMessage.RequestUri.Scheme == "https" || context.InitialRequestMessage.RequestUri.Port == 443)
{
var requestContext = context.InitialRequestMessage.GetRequestContext();
var tlsSniValue = requestContext.TlsSniValue;
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false);
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
{
TargetHost = tlsSniValue.Value,
RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true,
}, cancellationToken);
return sslStream;
}
return stream;
}
private ExternalProxyType DetermineProxyType(Uri proxyUri)
{
return proxyUri.Scheme.ToLower() switch
{
"socks4" => ExternalProxyType.Socks4,
"socks5" => ExternalProxyType.Socks5,
_ => ExternalProxyType.Http
};
throw new NotImplementedException("Proxy type determination needs to be implemented");
}
private async Task ConnectHttpProxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var credentials = webProxy.Credentials as NetworkCredential;
var authHeader = credentials != null
? $"Proxy-Authorization: Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes($"{credentials.UserName}:{credentials.Password}"))}\r\n"
: string.Empty;
var connectRequest = $"CONNECT {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port} HTTP/1.1\r\n" +
$"Host: {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port}\r\n" +
$"{authHeader}\r\n";
var connectBytes = Encoding.ASCII.GetBytes(connectRequest);
await stream.WriteAsync(connectBytes, cancellationToken);
var buffer = new byte[1024];
var bytesRead = await stream.ReadAsync(buffer, cancellationToken);
var response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
if (!response.StartsWith("HTTP/1.1 200"))
{
throw new Exception($"Proxy connection failed: {response}");
}
}
private async Task ConnectSocks4Proxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var credentials = webProxy.Credentials as NetworkCredential;
var userId = credentials?.UserName ?? string.Empty;
var destinationAddress = await GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken).FirstAsync();
var portBytes = BitConverter.GetBytes((ushort)context.DnsEndPoint.Port);
if (BitConverter.IsLittleEndian) Array.Reverse(portBytes);
var request = new byte[] { 0x04, 0x01 }
.Concat(portBytes)
.Concat(destinationAddress.Address.GetAddressBytes())
.Concat(Encoding.ASCII.GetBytes(userId))
.Concat(new byte[] { 0x00 })
.ToArray();
await stream.WriteAsync(request, cancellationToken);
var response = new byte[8];
await stream.ReadAsync(response, cancellationToken);
if (response[1] != 0x5A)
{
throw new Exception($"SOCKS4 proxy connection failed: {response[1]}");
}
}
private async Task ConnectSocks5Proxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var credentials = webProxy.Credentials as NetworkCredential;
// SOCKS5 握手
byte[] authMethod = credentials != null ? new byte[] { 0x02 } : new byte[] { 0x00 };
await stream.WriteAsync(new byte[] { 0x05, 0x01 }.Concat(authMethod).ToArray(), cancellationToken);
var response = new byte[2];
await stream.ReadAsync(response, cancellationToken);
if (response[0] != 0x05)
{
throw new Exception("SOCKS5 handshake failed");
}
// 如果需要身份验证
if (response[1] == 0x02 && credentials != null)
{
var userBytes = Encoding.ASCII.GetBytes(credentials.UserName);
var passBytes = Encoding.ASCII.GetBytes(credentials.Password);
var authRequest = new byte[] { 0x01, (byte)userBytes.Length }
.Concat(userBytes)
.Concat(new byte[] { (byte)passBytes.Length })
.Concat(passBytes)
.ToArray();
await stream.WriteAsync(authRequest, cancellationToken);
var authResponse = new byte[2];
await stream.ReadAsync(authResponse, cancellationToken);
if (authResponse[1] != 0x00)
{
throw new Exception("SOCKS5 authentication failed");
}
}
// 发送连接请求
byte[] addressBytes;
byte addressType;
if (IPAddress.TryParse(context.DnsEndPoint.Host, out IPAddress? ipAddress))
{
addressBytes = ipAddress.GetAddressBytes();
addressType = (byte)(ipAddress.AddressFamily == AddressFamily.InterNetwork ? 1 : 4);
}
else
{
addressBytes = Encoding.ASCII.GetBytes(context.DnsEndPoint.Host);
addressType = 3; // 域名
}
var request = new byte[] { 0x05, 0x01, 0x00, addressType }
.Concat(addressType == 3 ? new[] { (byte)addressBytes.Length } : Array.Empty<byte>())
.Concat(addressBytes)
.Concat(BitConverter.GetBytes((ushort)context.DnsEndPoint.Port).Reverse())
.ToArray();
await stream.WriteAsync(request, cancellationToken);
var connectResponse = new byte[10];
await stream.ReadAsync(connectResponse, cancellationToken);
if (connectResponse[1] != 0x00)
{
throw new Exception($"SOCKS5 connection failed: {connectResponse[1]}");
}
}
/// <summary>
/// 连接回调
/// </summary>
@@ -67,6 +256,11 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
/// <returns></returns>
async ValueTask<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
if (webProxy != null && !HttpNoProxy.IsNoProxy(webProxy))
{
return await ConnectThroughProxyAsync(context, cancellationToken);
}
var innerExceptions = new List<Exception>();
var ipEndPoints = GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken);
@@ -1,3 +1,4 @@
using System.Net;
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
// ReSharper disable once CheckNamespace
@@ -140,11 +141,6 @@ sealed class HttpLocalRequestMiddleware
return null;
}
StringValues cookie = context.Request.Headers["cookie-steamTool"];
if (StringValues.IsNullOrEmpty(cookie))
cookie = context.Request.Headers["Cookie"];
StringValues referer = context.Request.Headers["Referer-steamTool"];
context.Response.Headers.AccessControlAllowOrigin = context.Request.Headers.Origin.Count == 0 ? "*" : context.Request.Headers.Origin;
context.Response.Headers.AccessControlAllowHeaders = "*";
context.Response.Headers.AccessControlAllowMethods = "*";
@@ -166,29 +162,30 @@ sealed class HttpLocalRequestMiddleware
Method = methodObj,
Content = hasReqContent ? new StreamContent(context.Request.Body) : null,
};
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
if (!StringValues.IsNullOrEmpty(cookie))
foreach (var item in context.Request.Headers)
{
CookieHttpClient.CookieContainer.Add(new Cookie
{
CommentUri = requestUri,
Domain = requestUri.Host,
Value = cookie,
});
// req.Headers.Add("Cookie", cookie);
}
if (!StringValues.IsNullOrEmpty(referer))
{
try
{
var refererUri = new Uri(referer.ToString());
req.Headers.Referrer = refererUri;
}
catch
var headers = item.Key.ToLower();
if (headers.EndsWith("-steamtool"))
{
if (headers.Equals("cookie", StringComparison.OrdinalIgnoreCase))
{
CookieHttpClient.CookieContainer.Add(new Cookie
{
CommentUri = requestUri,
Domain = requestUri.Host,
Value = item.Value
});
}
if (headers.Equals("referer", StringComparison.OrdinalIgnoreCase))
{
var refererUri = new Uri(item.Value.ToString());
req.Headers.Referrer = refererUri;
}
req.Headers.TryAddWithoutValidation(item.Key.TrimEnd("-steamtool"), (IEnumerable<string?>)item.Value);
}
}
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
if (hasReqContent)
{
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType.ThrowIsNull());
@@ -1,5 +1,6 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.HttpServer/HttpReverseProxyMiddleware.cs
using Microsoft.AspNetCore.Http;
using Yarp.ReverseProxy.Forwarder;
// ReSharper disable once CheckNamespace
@@ -38,6 +39,7 @@ sealed partial class HttpReverseProxyMiddleware
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var url = context.Request.GetDisplayUrl();
//var url = context.Request.GetDisplayUrl().Remove(0, context.Request.Scheme.Length + 3);
var isScriptInject = reverseProxyConfig.TryGetScriptConfig(url, out var scriptConfigs);
@@ -50,9 +52,31 @@ sealed partial class HttpReverseProxyMiddleware
context.Response.Body = memoryStream;
}
if (TryGetDomainConfig(url, out var domainConfig) == false)
if (TryGetDomainConfig(url.Remove(0, context.Request.Scheme.Length + 3), out var domainConfig) == false)
{
await next(context);
if (reverseProxyConfig.Service.TwoLevelAgentEnable)
{
var httpClient = httpClientFactory.CreateHttpClient("GlobalProxy", defaultDomainConfig);
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, null);
var forwarderRequestConfig = new ForwarderRequestConfig()
{
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,
},
};
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
if (error != ForwarderError.None)
{
await HandleErrorAsync(context, error);
}
}
else
{
await next(context);
}
return;
}
@@ -60,7 +84,7 @@ sealed partial class HttpReverseProxyMiddleware
!reverseProxyConfig.Service.OnlyEnableProxyScript)
{
// 部分运营商将奇怪的域名解析到 127.0.0.1 再此排除这些不支持的代理域名
var ip = await reverseProxyConfig.DnsAnalysis.AnalysisDomainIpAsync(context.Request.Host.Value, IDnsAnalysisService.DNS_Dnspods).FirstOrDefaultAsync();
var ip = await reverseProxyConfig.DnsAnalysis.AnalysisDomainIpAsync(context.Request.Host.Value!, IDnsAnalysisService.DNS_Dnspods).FirstOrDefaultAsync();
if (ip == null || IPAddress.IsLoopback(ip))
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
@@ -80,7 +104,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 +121,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)
@@ -140,7 +182,7 @@ sealed partial class HttpReverseProxyMiddleware
return true;
}
var host = new Uri(uri).Host;
var host = new UriBuilder(uri).Host;
// 未配置的域名,但仍然被解析到本机 IP 的域名
if (IsDomain(host))
{
@@ -393,6 +435,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;
@@ -23,6 +23,8 @@ sealed class LazyReverseProxyServiceImpl : IReverseProxyService
public byte[]? GetFlowStatistics_Bytes() => impl().GetFlowStatistics_Bytes();
public string? GetLogAllMessage() => impl().GetLogAllMessage();
public async Task<StartProxyResult> StartProxyAsync(byte[] reverseProxySettings)
{
var result = await impl().StartProxyAsync(reverseProxySettings);
@@ -24,7 +24,7 @@ sealed class DnsDohAnalysisService : GeneralHttpClientFactory
{
}
public const string DefaultDohAddres = Dnspod_DohAddres;
public const string DefaultDohAddres = Dnspod_DohAddres2;
static Uri GetDohAddres(string? dohAddres)
{
@@ -53,16 +53,22 @@ sealed class DnsDohAnalysisService : GeneralHttpClientFactory
{
if (dnsClients.TryGetValue(dohAddresUri, out var value))
return value;
var client = CreateClient($"{TAG}_{dohAddresUri}", HttpHandlerCategory.Default);
//var handler = new HttpClientHandler
//{
// UseCookies = false,
// UseProxy = false,
// Proxy = HttpNoProxy.Instance,
//};
//var client = new HttpClient(handler);
//var client = CreateClient($"{TAG}_{dohAddresUri}", HttpHandlerCategory.Default);
var handler = new HttpClientHandler
{
UseCookies = false,
UseProxy = false,
Proxy = HttpNoProxy.Instance,
};
var client = new HttpClient(handler);
client.BaseAddress = dohAddresUri;
var dnsClient = new DnsHttpClient(client);
//暂未考虑释放问题 注意!
//foreach (var dc in dnsClients.Values)
//{
// dc.Dispose();
//}
//dnsClients.Clear();
dnsClients.TryAdd(dohAddresUri, dnsClient);
return dnsClient;
}
@@ -1,11 +1,14 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.Http/HttpClientFactory.cs
// ReSharper disable once CheckNamespace
using System.Net;
namespace BD.WTTS.Services.Implementation;
sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
{
readonly IDomainResolver domainResolver;
readonly IWebProxy webProxy;
/// <summary>
/// 首次生命周期
@@ -27,9 +30,19 @@ sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
/// </summary>
readonly ConcurrentDictionary<LifeTimeKey, Lazy<LifetimeHttpHandler>> httpHandlerLazyCache = new();
public ReverseProxyHttpClientFactory(IDomainResolver domainResolver)
public ReverseProxyHttpClientFactory(IDomainResolver domainResolver, IReverseProxyConfig reverseProxyConfig)
{
this.domainResolver = domainResolver;
if (reverseProxyConfig.Service.TwoLevelAgentEnable)
{
this.webProxy = new WebProxy($"{reverseProxyConfig.Service.TwoLevelAgentProxyType}://{reverseProxyConfig.Service.TwoLevelAgentIp}:{reverseProxyConfig.Service.TwoLevelAgentPortId}", true, null, null);
if (!string.IsNullOrEmpty(reverseProxyConfig.Service.TwoLevelAgentUserName) || !string.IsNullOrEmpty(reverseProxyConfig.Service.TwoLevelAgentPassword))
{
webProxy.Credentials = new NetworkCredential(reverseProxyConfig.Service.TwoLevelAgentUserName, reverseProxyConfig.Service.TwoLevelAgentPassword);
}
}
else
this.webProxy = HttpNoProxy.Instance;
}
public ReverseProxyHttpClient CreateHttpClient(string domain, IDomainConfig domainConfig)
@@ -43,7 +56,7 @@ sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
}
LifetimeHttpHandler CreateLifetimeHttpHandler(LifeTimeKey lifeTimeKey, TimeSpan lifeTime)
=> new(domainResolver, lifeTimeKey, lifeTime, OnLifetimeHttpHandlerDeactivate);
=> new(domainResolver, webProxy, lifeTimeKey, lifeTime, OnLifetimeHttpHandlerDeactivate);
void OnLifetimeHttpHandlerDeactivate(LifetimeHttpHandler lifetimeHttpHandler)
{
@@ -89,6 +89,8 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
public string? CustomDohAddres { get; set; }
public string? ServerSideProxyToken { get; set; }
/// <summary>
/// 获取一个随机的未使用的端口
/// </summary>
@@ -157,15 +159,18 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
return StartProxyResultCode.DeserializeReverseProxySettingsFail;
}
// Linux 如果是443 需要验证 是否允许使用
// Linux 如果是443 需要验证 是否允许使用
if (OperatingSystem.IsLinux())
{
if (string.IsNullOrWhiteSpace(reverseProxySettings.ProxyIp))
return StartProxyResultCode.Exception;
var inUsePort = SocketHelper.IsUsePort(IPAddress.Parse(reverseProxySettings.ProxyIp!), 443);
if (inUsePort)
if (reverseProxySettings.ProxyMode == ProxyMode.Hosts)
{
return StartProxyResultCode.BindPortError;
var inUsePort = SocketHelper.IsUsePort(IPAddress.Parse(reverseProxySettings.ProxyIp!), 443);
if (inUsePort)
{
return StartProxyResultCode.BindPortError;
}
}
}
@@ -178,7 +183,6 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
protected virtual void CheckRootCertificate()
{
}
protected abstract Task<StartProxyResult> StartProxyImpl();
@@ -65,6 +65,8 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
WebRootPath = RootPath,
});
builder.Logging.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(AssemblyInfo.Accelerator));
builder.Services.Configure<HostFilteringOptions>(static o =>
{
o.AllowEmptyHosts = true;
@@ -80,17 +82,19 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
builder.WebHost.UseKestrel(options =>
{
options.AddServerHeader = false;
options.RequestHeaderEncodingSelector = _ => Encoding.UTF8;
options.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
options.NoLimit();
#if WINDOWS
#if !NET7_0_OR_GREATER
if (OperatingSystem2.IsWindows7())
{
//https://github.com/dotnet/aspnetcore/issues/22563
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
});
}
if (OperatingSystem2.IsWindows7())
{
//https://github.com/dotnet/aspnetcore/issues/22563
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
});
}
#endif
//options.ListenSshReverseProxy();
//options.ListenGitReverseProxy();
@@ -165,6 +169,12 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
return bytes;
}
public string? GetLogAllMessage()
{
var result = LogConsoleService.Builder;
return result.ToString();
}
// IDisposable
protected override void DisposeCore()
@@ -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
}
@@ -15,6 +15,12 @@ partial interface IReverseProxyService
/// </summary>
/// <returns></returns>
byte[]? GetFlowStatistics_Bytes();
/// <summary>
/// 获取所有日志信息
/// </summary>
/// <returns></returns>
string? GetLogAllMessage();
}
public static partial class ReverseProxyServiceExtensions
@@ -8,18 +8,18 @@ public partial interface IDnsAnalysisService // 公共常量定义
{
#region DNS 常量
const string DNS_Ali_DohAddres = "https://dns.alidns.com";
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/dns-query";
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/dns-query";
const string DNS_Ali_DohAddres = "https://dns.alidns.com/resolve";
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/resolve";
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/resolve";
const string Dnspod_DohAddres = "https://1.12.12.12";
const string Dnspod_DohAddres2 = "https://doh.pub";
const string Dnspod_DohAddres3 = "https://120.53.53.53/dns-query";
const string Dnspod_DohAddres = "https://1.12.12.12/resolve";
const string Dnspod_DohAddres2 = "https://doh.pub/resolve";
const string Dnspod_DohAddres3 = "https://120.53.53.53/resolve";
const string Google_DohAddres = "https://dns.google";
const string Cloudflare_DohAddres = "https://cloudflare-dns.com";
const string DohAddres_360 = "https://doh.360.cn/dns-query";
const string TUNA_DohAddres = "https://101.6.6.6:8443/dns-query";
const string Google_DohAddres = "https://dns.google/resolve";
const string Cloudflare_DohAddres = "https://cloudflare-dns.com/resolve";
const string DohAddres_360 = "https://doh.360.cn/resolve";
const string TUNA_DohAddres = "https://101.6.6.6:8443/resolve";
const string PrimaryDNS_IPV6_Ali = "2400:3200::1";
@@ -60,5 +60,5 @@ public partial interface IDnsAnalysisService // 公共常量定义
public static readonly IPEndPoint Cloudflare2 = new(IPAddress.Parse("1.0.0.1"), 53);
}
#endregion
#endregion DNS 常量
}
@@ -0,0 +1,22 @@
namespace BD.WTTS.Enums;
/// <summary>
/// 网络环境检查步骤
/// </summary>
public enum NetworkEnvCheckStep
{
/// <summary>
/// Hosts文件检查
/// </summary>
HostsFileCheck = 1,
/// <summary>
/// 网卡检查
/// </summary>
NetworkInterfaceCheck = 2,
/// <summary>
/// LSP检查
/// </summary>
LSPCheck = 3
}
@@ -0,0 +1,7 @@
namespace BD.WTTS.Enums;
public enum TransportProtocol
{
Tcp,
Udp,
}
@@ -42,6 +42,16 @@ static class WinDivertInitHelper
/// </summary>
public static async Task InitializeAsync()
{
(byte[] WinDivert_dll, byte[] WinDivert64_sys) data = default;
void GetData()
{
if (data == default)
{
data = GetWinDivertBinRes();
}
}
if (libraryPath != null && libraryDirPath != null)
{
bool isWrite;
@@ -55,9 +65,10 @@ static class WinDivertInitHelper
}
else
{
GetData();
var libraryBytes = await File.ReadAllBytesAsync(libraryPath);
var libraryHash = SHA256.HashData(libraryBytes);
var libraryHashRes = SHA256.HashData(WinDivertBinRes.WinDivert_dll);
var libraryHashRes = SHA256.HashData(data.WinDivert_dll!);
if (libraryHash.SequenceEqual(libraryHashRes)) // 哈希值相同,不写入
{
isWrite = false;
@@ -76,14 +87,15 @@ static class WinDivertInitHelper
if (isWrite) // 检查文件不存在时写入
{
GetData();
switch (RuntimeInformation.ProcessArchitecture)
{
//case Architecture.X86:
// break;
case Architecture.X64:
IOPath.DirCreateByNotExists(libraryDirPath);
var task1 = File.WriteAllBytesAsync(Path.Combine(libraryDirPath, "WinDivert64.sys"), WinDivertBinRes.WinDivert64_sys);
var task2 = File.WriteAllBytesAsync(libraryPath, WinDivertBinRes.WinDivert_dll);
var task1 = File.WriteAllBytesAsync(Path.Combine(libraryDirPath, "WinDivert64.sys"), data.WinDivert64_sys!);
var task2 = File.WriteAllBytesAsync(libraryPath, data.WinDivert_dll!);
await Task.WhenAll(task1, task2);
break;
}
@@ -128,5 +140,15 @@ static class WinDivertInitHelper
break;
}
}
static (byte[] WinDivert_dll, byte[] WinDivert64_sys) GetWinDivertBinRes()
{
byte[] bytes = WinDivertBinRes.WinDivert_mpo;
bytes.AsSpan().Reverse();
var data = MemoryPackSerializer.Deserialize<byte[][]>(bytes)!;
return (data[0], data[1]);
}
}
#endif
@@ -0,0 +1,48 @@
using FluentAvalonia.Core;
using System.Reactive;
namespace BD.WTTS.Models;
public class ProxyDomainGroupViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; set; } = string.Empty;
public string IconUrl { get; set; } = string.Empty;
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
public ProxyDomainGroupViewModel()
{
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
{
if (EnableProxyDomainVMs == null)
{
Toast.Show(ToastIcon.Warning, "没有可测试的项目");
return;
}
var tasks = EnableProxyDomainVMs.Select(async enableDomain =>
{
enableDomain.DelayMillseconds = "- ms";
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
enableDomain.DelayMillseconds = success switch
{
true when delayMs > 20000 => "Timeout",
true => delayMs.ToString() + " ms",
false => "error",
};
});
await Task.WhenAll(tasks);
if (EnableProxyDomainVMs.Count(x => x.DelayMillseconds == "Timeout") == EnableProxyDomainVMs.Count)
{
Toast.Show(ToastIcon.Error, "当前测试项全部未通过,请检查网络链接状况,以及代理设置里的设置项是否正常。");
}
});
}
}
@@ -0,0 +1,55 @@
using Avalonia.Media;
namespace BD.WTTS.Models;
public class ProxyDomainViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; }
public ProxyType ProxyType { get; }
public string Url { get; }
[Reactive]
public string DelayMillseconds { get; set; } = string.Empty;
[ObservableAsProperty]
public bool RetestButtonVisible { get; }
[ObservableAsProperty]
public IBrush DelayColor { get; } = null!;
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
{
Name = name;
ProxyType = proxyType;
Url = url;
Children = children?.AsReadOnly();
this.WhenAnyValue(x => x.DelayMillseconds)
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d != string.Empty && d != "-")
.ToPropertyEx(this, x => x.RetestButtonVisible);
const int DelayMiddle = 1000;
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d switch
{
"Timeout" or "error" => Brushes.Red,
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
=> ms switch
{
<= DelayMiddle => Brushes.Green,
> DelayMiddle => Brushes.Orange,
},
_ => Brushes.Gray,
})
.ToPropertyEx(this, x => x.DelayColor);
}
}

Some files were not shown because too many files have changed in this diff Show More