Compare commits

...

219 Commits

Author SHA1 Message Date
Aigio Liu deee8d056f 🎨 FileVersion 2024-03-08 17:57:36 +08:00
ff m 3016469c7f 🐛 Fix macOs Icons 2024-03-08 17:44:38 +08:00
Aigio Liu 526fb7a78d 🐛 图片加载缓存 2024-03-08 15:56:13 +08:00
RMBGAME 2790ffc5f1 🐛 Carousel CancelToken 2024-03-08 15:14:14 +08:00
RMBGAME e22897e2ae 🐛 挂卡登录异常处理 2024-03-08 15:03:46 +08:00
RMBGAME 00b8c4dfcf 💄 插件设置的一些样式 2024-03-08 14:54:10 +08:00
Aigio Liu 9a2d2c6c94 🐛 PublishTool 2024-03-08 14:36:19 +08:00
Aigio Liu 29fb43fc38 ⬆️ 3.0.0 rc.5 2024-03-08 11:30:22 +08:00
Aigio Liu 4ca3066644 🐛 https://github.com/BeyondDimension/SteamTools/issues/3213 https://github.com/BeyondDimension/SteamTools/issues/3214 2024-03-08 11:04:23 +08:00
RMBGAME 890f2fab37 🐛 fix最小化启动失效问题 2024-03-07 19:50:38 +08:00
Aigio Liu f561f841e3 🐛 MSStore DNS Mode x3 2024-03-07 18:59:45 +08:00
Aigio Liu a016631d68 🐛 MSStore DNS Mode x2 2024-03-07 18:24:46 +08:00
ff m 367c97b480 Script 文件存在强制替换 Delete 后未移动文件导致丢失。 2024-03-07 18:21:59 +08:00
Aigio L 6367bc5ed1 🐛 MSStore DNS Mode (#3211) 2024-03-07 17:07:59 +08:00
lxy829 2b69b98f3c 🎨 Riot Games and Rockstar switch 2024-03-06 16:28:47 +08:00
Aigio L a6010ba5f4 Merge pull request #3198 from BeyondDimension/develop
v3.0.0-rc.4_win_x64_240306_1204508630992
2024-03-06 13:42:14 +08:00
Aigio Liu 0c67cda96a 🔖 3.0.0 240306_1204508630992 2024-03-06 13:39:59 +08:00
Aigio Liu c23823a5af 🐛 https://github.com/BeyondDimension/SteamTools/issues/3150 & XY_INSTALL 2024-03-05 18:36:00 +08:00
Aigio Liu f8424a2915 🐛 AppxManifestXml 2024-03-05 15:19:03 +08:00
Aigio Liu 152fc74869 🎨 MainTitle 2024-03-05 15:05:49 +08:00
RMBGAME 7c96f7af46 🐛 无游戏列表时UI展示 2024-03-05 14:53:11 +08:00
Aigio Liu 9b5bc82873 🔖 [Release] Steam++_v3.0.0-rc.4_win_x64_240305_1352180388780 2024-03-05 14:54:16 +08:00
RMBGAME 011c08266f ✨ 安装路径选择 2024-03-05 11:53:21 +08:00
RMBGAME bd43f16dd6 💄 GameAcc 2024-03-01 20:37:05 +08:00
RMBGAME 8bd7085d35 🐛 fix代理模式设置被还原问题 2024-03-01 19:49:05 +08:00
Aigio Liu 1bb5bef800 🔖 [Release] Steam++_v3.0.0-rc.4_win_x64_240301_1817017115562 2024-03-01 18:46:10 +08:00
Aigio Liu da73e4cb84 🎨 Publish NSIS 2024-03-01 18:02:59 +08:00
RMBGAME d11c1afc12 🐛 fix 账号切换uniqueId bugs 2024-03-01 18:00:43 +08:00
Aigio Liu f21f069a2b 🎨 PubTool 2024-03-01 15:57:45 +08:00
Aigio Liu 242866fe88 🎨 PublishTool 2024-03-01 14:10:08 +08:00
Aigio Liu 46d87bc2d6 🎨 WinDivertSharp.NativeAssets.Win32 2024-02-29 17:22:37 +08:00
RMBGAME 70ce3237cd 🐛 修复第一次区服选择丢失问题 2024-02-29 16:44:56 +08:00
RMBGAME e57e7db81b 🐛 fix XY_GetAccelStateEx InBackground MainThread Call Exception 2024-02-28 21:07:01 +08:00
Aigio Liu 5bbddf86cd 🐛 xunyou_testspeed_ex 2024-02-28 20:33:59 +08:00
RMBGAME 541399f011 🐛 Misc 2024-02-28 15:48:00 +08:00
Aigio Liu 287ff69011 🐛 游戏有服信息时,无法测速问题,新增接口xunyou_testspeed_ex 2024-02-28 15:31:30 +08:00
Aigio Liu b00fbba75f 🐛 Log XunYouAccelStateToFrontendCallback 2024-02-27 22:13:17 +08:00
RMBGAME f9f0325610 🐛 Fix StateChange not runing MainThread 2024-02-27 21:04:09 +08:00
Aigio Liu 6dae018cb7 ✨ XunYouAccelStateEx 2024-02-27 18:56:40 +08:00
Aigio Liu a675d8ea2f 🎨 XunYouAccelStateCallback 2024-02-27 18:56:40 +08:00
RMBGAME 586f5d6a6c 🐛 The most significant changes include the update of the ItemsPerLine property in CarouselItems.axaml to use a binding to the parent spp:CarouselItems's ItemsPerPage property, the replacement of a large block of code in HomePage.axaml with a new block that sets up a spp:CarouselItems control with a custom ItemTemplate, and the addition of a new line of code in GameAcceleratorService.cs to update the LastAccelerateTime property of the app object.
Here is a list of the changes:

1. In `CarouselItems.axaml`, the `ItemsPerLine` property of `spp:FixedWrapPanel` has been updated to use a binding to the parent `spp:CarouselItems`'s `ItemsPerPage` property. This change allows the number of items per line to dynamically adjust based on the parent's `ItemsPerPage` property.

2. In `HomePage.axaml`, a large block of code related to an `ItemsControl` has been commented out and replaced with a new block of code that sets up a `spp:CarouselItems` control with a custom `ItemTemplate`. This change simplifies the code and makes it easier to manage the items in the carousel.

3. In `GameAcceleratorService.cs`, a new line of code has been added to update the `LastAccelerateTime` property of the `app` object to the current time when the `startCode` is `101`. This change ensures that the `LastAccelerateTime` property is always up-to-date.

4. In `GameInfoPageViewModel.cs`, two new lines of code have been added to set the `SelectedArea` and `SelectedServer` properties of `XunYouGame` to `null` in the `BackSelectArea` method. This change resets the selected area and server when the user goes back to the select area page.

5. In `GameAppItem.axaml`, several changes have been made to the styles and properties of various elements. These changes improve the appearance of the game app item.

6. In `AcceleratorPage2.axaml.cs`, the `SearchGameBox_SelectionChanged` event handler has been replaced with a `SearchGameBox_DropDownClosed` event handler. This change improves the user experience by resetting the search box and scrolling the game list to the top after a game is selected.
2024-02-27 17:47:52 +08:00
Aigio Liu e4058219d0 🐛 热门游戏默认排序 2024-02-27 14:47:12 +08:00
Aigio Liu 9adeb39b4f 🐛 游戏信息根据 Id 排序与加速器上一致 2024-02-27 13:32:24 +08:00
Aigio Liu 2f71afc818 🐛 切换区服数据不变时不重新加速 & 屏蔽安装路径设置项界面 2024-02-27 12:48:14 +08:00
Aigio Liu aa271827b3 🐛 监听加速服切换 2024-02-26 17:11:11 +08:00
Aigio Liu 88ed73d720 🐛 首次启动切换区服 2024-02-26 16:49:11 +08:00
Aigio Liu cde6e0d724 ✨ 刷新时重新加载迅游游戏数据 2024-02-26 16:20:25 +08:00
RMBGAME df5e359d0d 🐛 fix监听加速区服切换BUG 2024-02-26 15:56:50 +08:00
RMBGAME 14e050a942 🐛 The most significant changes involve modifications to the GameAcceleratorService and the layout of the game accelerator page. In GameAcceleratorService, the RestoreGameStatus method now sets PingValue to a new default value, and the logic for adding and updating games has been altered. The condition for early return in the game area selection method has also been changed. The layout of the game accelerator page has been restructured, with changes to the placement of ScrollViewer and GameDetail component.
1. The default value for `PingValue` in `XunYouGameViewModel` has been changed from an empty string to a constant string `DefaultPingValue` set to `"--"`.
2. `RestoreGameStatus` method in `GameAcceleratorService` now sets `PingValue` to `DefaultPingValue` instead of `"0"`.
3. The logic for adding and updating games in `GameAcceleratorService` has been modified. If the game is found in `MyGames`, it is now removed before being added again.
4. The condition for early return in the game area selection method in `GameAcceleratorService` now checks if `SelectedArea` is null on `vm.XunYouGame` instead of `app`.
5. The layout of the game accelerator page in `AcceleratorPage2.axaml` has been changed. The `ScrollViewer` has been moved inside the `ContentLoader` and the `GameDetail` component has been moved outside the `ScrollViewer`.
6. The `ItemsRepeater` in `GameListPage.axaml` now has a `VerticalCacheLength` of `300`.
2024-02-23 20:36:04 +08:00
Aigio Liu 568483c454 🎨 PingValue/PingValueIsInvalid 2024-02-23 19:43:32 +08:00
Aigio Liu 926fd00fc7 🐛 PingValue 2024-02-23 18:56:07 +08:00
Aigio Liu 5c64dd1168 🐛 MyGames Save 2024-02-23 17:26:57 +08:00
Aigio Liu 5dd50aeeca 🎨 延迟大于等于1000,显示-- 2024-02-23 16:05:29 +08:00
RMBGAME c2bb27c537 🐛 The most significant changes involve the GameAcceleratorService class and the GameAppItem.axaml and GameDetail.axaml files. In the GameAcceleratorService class, the variable game2 is now used instead of game in certain cases, and the properties IsAccelerated, IsAccelerating, and LastAccelerateTime are now set directly on game2. The class also now includes SelectedArea and SelectedServer in the GameInfoPageViewModel instance creation and uses an async version of GameAccelerator. In the GameAppItem.axaml and GameDetail.axaml files, new styles and properties have been added and some properties have been moved or removed.
Changes:

1. The `GameAcceleratorService` class now uses `game2` instead of `game` in the switch case for `XunYouState.加速中` and `XunYouState.加速已完成`. The properties `IsAccelerated`, `IsAccelerating`, and `LastAccelerateTime` are now set directly on `game2` (Code Change 1).
2. The `GameAcceleratorService` class now includes `SelectedArea` and `SelectedServer` in the `GameInfoPageViewModel` instance creation (Code Change 2).
3. The `GameAcceleratorService` class now uses an async version of `GameAccelerator` (Code Change 3).
4. The `GameInfoPageViewModel` class has been updated to change the second parameter of `WhenPropertyChanged` from `false` to `true` for `XunYouGame.SelectedArea` (Code Change 4).
5. The `GameAppItem.axaml` file has been updated with a new style for `Grid Panel.AppName TextBlock.LeftMargin` and new properties for `spp:Image2`. The `Margin` property of `TextBlock` has been moved to a new class `Classes.LeftMargin` (Code Change 5).
6. The `GameDetail.axaml` file has been updated to move the `spp:Image2` inside a `StackPanel` and add `IsVisible` and `Margin` properties to it. The `Margin` property of `TextBlock` has been removed (Code Change 6).
7. The `AcceleratorPage2.axaml.cs` file has been updated to add a new dictionary `dictPinYinArray` and a new method `GameContains` that checks if a game contains a given text using case-insensitive comparison and Pinyin comparison. The `TextSelector` and `TextFilter` properties of `SearchGameBox` have been set to `null` and `GameContains` respectively (Code Change 7).
2024-02-23 15:10:30 +08:00
Aigio Liu 9cf88df056 ✨ 确定性编译 2024-02-23 14:56:56 +08:00
RMBGAME 995aaee884 🐛 The most significant changes are in the AuthenticatorHomePageViewModel.cs and AuthenticatorHomePage.axaml files. In the ViewModel, a new else block has been added to set IsVerificationPass to true when the condition for the if statement is not met. In the axaml file, the visibility of the StackPanel is now directly bound to the IsVerificationPass property, simplifying the visibility condition. The MultiBinding converter code has been commented out, indicating it is no longer in use.
List of changes:

1. A new else block has been added to the `AuthenticatorHomePageViewModel.cs` file. This block sets `IsVerificationPass` to true when the if statement's condition is not met.

2. In the `AuthenticatorHomePage.axaml` file, the visibility of the `StackPanel` is now directly bound to the `IsVerificationPass` property. This change simplifies the visibility condition of the `StackPanel`.

3. The `MultiBinding` converter code, which was previously used to bind `IsVerificationPass` and `Auths.Count` to the visibility of the `StackPanel`, has been commented out. This indicates that it is no longer in use but is kept for possible future reference.

4. No changes have been made to the `TextBlock` element. It is included for context.
2024-02-23 01:23:23 +08:00
RMBGAME 99e915adbe 🐛 The most significant changes involve the GameAccelerator method in the GameAcceleratorService class and the GameAcceleratorCommand ReactiveCommand. The GameAccelerator method has been changed from an asynchronous void method to an asynchronous Task method, allowing for improved error handling and control flow. The GameAcceleratorCommand has been updated to reflect this change. Additionally, a new property IsAccelerating has been added to the XunYouGameViewModel class to better track the state of the game acceleration process.
Changes:

1. The `GameAccelerator` method in the `GameAcceleratorService` class has been changed from an asynchronous void method to an asynchronous Task method. This change allows for better error handling and control flow.

2. The `GameAcceleratorCommand` ReactiveCommand has been updated to use `CreateFromTask` instead of `Create`, reflecting the change in the `GameAccelerator` method.

3. A new property `IsAccelerating` has been added to the `XunYouGameViewModel` class. This property is set to `true` at the start of the `GameAccelerator` method and is set to `false` in various places where the method may return early due to an error or condition not being met. This change allows for better tracking of the state of the game acceleration process.

4. The line setting `app.IsAccelerating` to `true` has been removed from the end of the `GameAccelerator` method, as it is now set at the start of the method.

5. In the case where the game acceleration is stopped successfully, the line setting `app.IsAccelerating` to `true` has been removed. In the case where stopping the game acceleration fails, `app.IsAccelerating` is now set to `false`.
2024-02-23 01:23:23 +08:00
ff m b8801d6b82 🐛 调整更新文本大小按钮位置. 2024-02-22 18:17:58 +08:00
Aigio Liu 73639376aa 🐛 Application Crash - Watt Toolkit
---------------------------
Stopped program because of exception, name: AppDomain, isTerminating: True
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at System.Log.get_Factory()
   at System.Log.CreateLogger(String tag)
   at System.Log.Error(String tag, String msg, Object[] args)
   at BD.WTTS.Services.Implementation.BackendAcceleratorServiceImpl.XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m) in C:\Code\2023\SteamTools\src\BD.WTTS.Client.Plugins.Accelerator\Services.Implementation\BackendAcceleratorServiceImpl.XunYou.cs:line 49
   at BD.WTTS.Services.Implementation.BackendAcceleratorServiceImpl.XunYouAccelStateCallback(XunYouState status, IntPtr thisptr) in C:\Code\2023\SteamTools\src\BD.WTTS.Client.Plugins.Accelerator\Services.Implementation\BackendAcceleratorServiceImpl.XunYou.cs:line 30
---------------------------
确定
---------------------------
2024-02-22 18:16:24 +08:00
RMBGAME 5852423652 🐛 The most significant changes involve the GameAcceleratorService.cs file, where new cases have been added to the switch statement to handle different states of the game accelerator. The SetGameStatus method has also been updated to check and update the CurrentAcceleratorGame properties. In addition, changes have been made to the GameDetail.axaml, AcceleratorPage2.axaml, AcceleratorPage2.axaml.cs, and MainFramePage.axaml files to modify UI elements and functionality.
1. `GameAcceleratorService.cs` has been updated to include a new case for `XunYouState.加速中` in the switch statement. This case checks if the `GameId` is not 0 and `CurrentAcceleratorGame` is null, then it creates a new `XunYouGameViewModel` instance and assigns it to `CurrentAcceleratorGame`.
2. A new case for `XunYouState.加速已断开` has been added to the switch statement in `GameAcceleratorService.cs`. This case stops the speed test and updates the game status.
3. The `SetGameStatus` method in `GameAcceleratorService.cs` has been updated to check if `CurrentAcceleratorGame` is not null and its `Id` is equal to `game.Id`. If true, it updates the `CurrentAcceleratorGame` properties. If `SelectedArea` is null, it fetches the game info and updates `SelectedArea` and `SelectedServer`.
4. The `GameDetail.axaml` file has been updated to change the `Foreground` color of two `TextBlock` elements.
5. The `AcceleratorPage2.axaml` file has been updated to remove some UI elements related to the Watt Accelerator installation path and add them back later in the file. The order of some elements has been changed.
6. The `AcceleratorPage2.axaml.cs` file has been updated to clear the selected item in `SearchGameBox` after adding a game to `GameAcceleratorService`.
7. The `MainFramePage.axaml` file has been updated to comment out a `TabStripItem` related to the community fix.
2024-02-22 17:27:27 +08:00
RMBGAME 18012f775e 🐛 GameAcc 2024-02-22 15:58:40 +08:00
Aigio Liu 9054d4d095 🐛 globalXunYouDownLoadCallback_xunyou_install 2024-02-22 15:54:58 +08:00
Aigio Liu e19011a785 🐛 本机库路径 & 切换加速 2024-02-22 15:02:52 +08:00
Aigio Liu a63dfc0846 🐛 PublishTool 2024-02-22 04:57:44 +08:00
RMBGAME 40539d99ed ⬆️ Avalonia 11.0.9 2024-02-21 19:25:43 +08:00
RMBGAME 9ae1584506 🚧 ScriptUpdate 2024-02-21 19:18:12 +08:00
Mossimo 38f35d36e0 🐛 遗漏初始化后检测脚本更新 2024-02-21 19:03:59 +08:00
Aigio Liu b3c18e2042 ⬆️ Common 2024-02-21 18:47:02 +08:00
RMBGAME 5ea5fe1e83 🚧 GameAcc 2024-02-21 18:44:23 +08:00
RMBGAME 9c71e89645 🚧 GameAcc 2024-02-21 18:08:40 +08:00
Aigio Liu 8a745839fe 🐛 GameAcceleratorSettings 2024-02-21 17:42:45 +08:00
Aigio Liu 1a237a10bc 🐛 InitStateSubscribe 2024-02-21 16:49:56 +08:00
Aigio Liu 655f943f5d 🐛 单例服务 2024-02-21 16:25:50 +08:00
Mossimo 77f8178557 🐛 添加新表 旧数据全部失效 旧数据 IsBuild 全部异常 2024-02-21 16:19:33 +08:00
Aigio Liu c33dfc8e70 🔧 Ipc XYSDK 2024-02-21 16:05:25 +08:00
Aigio Liu 803b626e8a 🐛 Settings 2024-02-21 15:57:05 +08:00
Aigio Liu f9228b93a3 🐛 Login2 2024-02-21 14:55:12 +08:00
Aigio Liu 60ec614d46 🐛 Login 2024-02-21 14:42:06 +08:00
Aigio Liu 237190384f 🐛 Urls 2024-02-21 14:13:46 +08:00
Aigio Liu 7dd576a6c5 ⬆️ ClientSDK 2024-02-21 14:10:53 +08:00
Aigio Liu 3fb0057883 🐛 GameAcc 2024-02-21 13:53:10 +08:00
Aigio Liu aeccf68061 🐛 GameAcc 2024-02-21 00:06:44 +08:00
RMBGAME 0806f61448 🚧 Fix 2024-02-20 22:22:52 +08:00
RMBGAME b67adfd67c 🚧 GameAcc 2024-02-20 21:45:58 +08:00
RMBGAME 97fb2ed673 🚧 GameAcc 2024-02-20 21:45:50 +08:00
Aigio Liu 0beadc8733 🚑 GameAcc 2024-02-20 21:05:02 +08:00
Aigio Liu d0bc99d5a5 🐛 ApiRsp Message 2024-02-20 20:34:00 +08:00
Aigio Liu 0852c57ed8 ✨ Resx GameAccelerator 2024-02-20 17:17:06 +08:00
Aigio L 84653abace Merge branch 'BeyondDimension:develop' into develop 2024-02-20 16:11:27 +08:00
Aigio Liu 2a738a36a1 🐛 XYSDK 2024-02-20 16:12:08 +08:00
Aigio Liu b18925a1a0 ✨ XYSDK 2024-02-20 16:08:41 +08:00
ff m 29fd1f0b19 🐛 撤回错误修复 3.0 该地方需要补完 旧版本不需要 2024-02-20 16:03:43 +08:00
RMBGAME d414267c9f 🐛 Misc 2024-02-19 18:05:46 +08:00
lxy829 af8739bb59 🎨 移除 Origin 平台账号切换支持 2024-02-19 15:57:27 +08:00
lxy829 e140902de6 🎨 BattleNetSwitcher 2024-02-19 14:53:39 +08:00
john.jiang 4a5e3447ff 🎨 ArchiSteamFarmPlus fixs 2024-02-19 14:51:33 +08:00
Aigio Liu a97357099d 🐛 fix https://github.com/BeyondDimension/SteamTools/issues/3163 2024-02-19 14:00:47 +08:00
john.jiang 7131deb860 🎨 ArchiSteamFarmPlus 2024-02-06 15:50:36 +08:00
lxy829 268b803ce7 🎨 Platformsjson 2024-02-06 15:45:13 +08:00
lxy829 6a9af1d95f 🎨 PlatformSwitcher 2024-02-06 10:37:51 +08:00
RMBGAME 1bf13ffb4b 🚧 Misc 2024-02-02 21:08:45 +08:00
RMBGAME 1b90cd4bce 🚧 Misc 2024-02-02 19:42:25 +08:00
Mossimo 62efead062 🐛 Add OpenId 调用 v1 版本接口 2024-02-02 17:51:59 +08:00
ff m 2754fb5dac 🐛 fix script NotFound 2024-01-29 22:54:49 +08:00
RMBGAME 8fff1d1828 💄 FixedWrapPanel 2024-01-29 18:00:20 +08:00
RMBGAME 9805ad4114 🐛 修复某些情况导入SDA令牌出错 2024-01-04 11:32:26 +08:00
Mossimo 83ae3102dd ↑ update SteamClient
🐛 Fix LC_ALL = en_US.UTF-8 保持兼容性
2023-12-22 17:07:18 +08:00
RMBGAME c3911fd5bf 🚧 FlexPanel 2023-12-21 11:58:29 +08:00
RMBGAME ef27310e4f 💄✨ CarouselItems Impl 2023-12-20 14:44:27 +08:00
RMBGAME 9ea1c83342 🐛 修复成就 云存档窗口在部分设置下打不开的问题 2023-12-18 14:49:35 +08:00
RMBGAME bbcee67fe0 💄✨ CarouselItems Control 2023-12-15 18:23:34 +08:00
RMBGAME 4706561d42 🐛 修复挂卡登录账号不便于查看令牌问题 2023-12-12 14:36:08 +08:00
RMBGAME c9d5fe37bb 🐛 Misc 2023-12-08 18:12:02 +08:00
Mossimo 6bca078614 🐛 添加 离线初始化安装 优化卸载脚本 (现在会验证父级目录 存在已知文件夹 且需要用户输入 y 执行卸载) 2023-12-06 11:24:42 +08:00
Mossimo a4481011eb 🐛 Fix 迁移 MoveDirByScripts 父级目录未创建直接 Move 异常 2023-12-05 18:38:02 +08:00
Mossimo 81d39b5436 🐛 Fix Script 2023-12-05 18:38:02 +08:00
Mossimo 4bff8602d1 ✨ 一键登录商城 2023-12-05 18:38:02 +08:00
john.jiang 4e434d25fe 🎨 submodule update 2023-12-04 11:52:00 +08:00
john.jiang 4e2cfc23ae 🎨 fixs DefaultIdleRule 2023-11-30 18:03:46 +08:00
RMBGAME e61c240db7 🐛 Misc 2023-11-30 16:51:29 +08:00
john.jiang b1a0f178e5 🎨 fixs 2023-11-30 16:39:18 +08:00
RMBGAME 5d7d00c623 🐛 Misc 2023-11-30 16:18:31 +08:00
john.jiang c9b3191f2b 🎨 遗漏提交 2023-11-30 16:01:47 +08:00
john.jiang e8ac8a52d6 🎨 DefaultSwitchTime fixs 2023-11-30 15:53:41 +08:00
john.jiang 34b4dc9ef7 🎨 Idle Add FastMode 2023-11-30 15:42:34 +08:00
Mossimo 47e16e3fc4 🐛 Linux 排除 游戏工具插件 2023-11-30 15:18:35 +08:00
RMBGAME bbd9623311 🐛 Misc 2023-11-30 14:13:10 +08:00
RMBGAME 30d5a7d14f 🐛 Misc 2023-11-30 14:05:28 +08:00
RMBGAME f8d02c64b0 🐛 修复部分文本显示错误 2023-11-30 13:40:58 +08:00
RMBGAME 80c28caed9 fix #3052 2023-11-30 10:42:41 +08:00
RMBGAME 4d91ce0690 🐛💄 Misc 2023-11-30 10:15:12 +08:00
RMBGAME d92968caf4 🐛 Fix 2023-11-29 21:08:39 +08:00
RMBGAME 57459be38c 🐛 Fix Mac \ Linux GetSteamId Bug 2023-11-29 20:28:29 +08:00
RMBGAME 276f150866 💄 Fix 2023-11-29 19:04:04 +08:00
RMBGAME 209634c73e 🐛 改进挂卡登录账号检测行为 2023-11-29 18:46:30 +08:00
RMBGAME d22a83228f 💄 UI Misc 2023-11-29 14:41:57 +08:00
RMBGAME 8d1e8b2795 🚧 Misc 2023-11-29 12:49:56 +08:00
Mossimo ef728120e4 ↑ Submodule 2023-11-29 11:18:06 +08:00
RMBGAME df158c340e 🚧 Misc 2023-11-28 18:19:47 +08:00
Mossimo bf450815ae 🐛 快速登录官网 2023-11-28 16:17:30 +08:00
Mossimo ee1187482c ✨ 快速登录 Web 2023-11-28 13:55:02 +08:00
RMBGAME 755fda1cd3 🐛 Fix Idle Bugs 2023-11-28 13:40:34 +08:00
john.jiang 72fad6c877 🎨 Strings fixs 2023-11-27 14:34:02 +08:00
john.jiang c6873c255a 🎨 DefaultSwitchTime fixs 2023-11-24 19:15:15 +08:00
RMBGAME ffa89cef34 🐛 Fix 2023-11-24 19:00:44 +08:00
RMBGAME db058c79f6 🐛 Fix 2023-11-24 18:49:16 +08:00
RMBGAME 5be7f5984d 🚧 Misc 2023-11-24 18:35:59 +08:00
john.jiang 2dbe0df4aa 🎨 Idle fixs 2023-11-24 18:12:04 +08:00
Mossimo beef9611f7 🐛 修复 Linux 证书安装 dotnet8 修复 Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) 获取路径错误 (程序以前依赖此BUG运行) 2023-11-24 18:11:11 +08:00
RMBGAME b340c365ea 🚧 binding 遗留问题改进 2023-11-24 17:26:01 +08:00
john.jiang 924779d29f 🎨 fixs 2023-11-24 15:54:26 +08:00
john.jiang 5bd73c86e8 🎨 fixs 2023-11-24 15:51:38 +08:00
john.jiang 95052a6af1 🎨 idle fixs 2023-11-24 15:43:32 +08:00
RMBGAME 0aedef2bb6 🐛 Misc 2023-11-24 14:58:13 +08:00
Mossimo 3038f72e49 ✨ 商城用户登录
↑ macos net8.0 build
2023-11-24 14:48:33 +08:00
RMBGAME 710ee5f350 🐛 Fix 2023-11-24 14:16:06 +08:00
RMBGAME 74eb79b7f5 🚧 Tips 2023-11-24 13:44:04 +08:00
RMBGAME f626cbe96c 🐛 Fix 2023-11-24 13:27:24 +08:00
john.jiang bfbb1e36d4 🎨 挂卡完的游戏列表中删除,挂卡顺序切换事件 2023-11-24 12:10:46 +08:00
RMBGAME dc2826af1c 💄 Misc 2023-11-24 00:24:17 +08:00
github-actions[bot] d3dcb913a8 fix Publish .NET 8.x 2023-11-23 23:18:16 +08:00
john.jiang 5a6f18109d 🎨 Idle fixs priorityIdle 2023-11-23 19:37:26 +08:00
john.jiang eb25310530 🎨 fixs 2023-11-23 19:24:53 +08:00
john.jiang db5ff210f9 🎨 idle fixs 2023-11-23 18:52:34 +08:00
RMBGAME b1d7faee8a 🚧 Misc 2023-11-23 17:52:48 +08:00
RMBGAME 0ff633ec8f 🚧 Misc 2023-11-23 17:03:14 +08:00
RMBGAME d0ae475c50 🚧 Fixed 2023-11-23 17:00:13 +08:00
RMBGAME 47cb686ed1 ✨ Idle Complete 2023-11-23 16:11:40 +08:00
john.jiang 053bd2097b 🎨 Idle fixs 2023-11-23 11:48:37 +08:00
RMBGAME c945161805 🚧 Misc 2023-11-22 23:15:57 +08:00
john.jiang 4c848ef373 🎨 Idle fixs 2023-11-22 18:13:51 +08:00
RMBGAME 12d49ef32a 🚧 Misc 2023-11-22 12:15:06 +08:00
john.jiang 3afb735fb9 🎨 ASF Idle settings fixs 2023-11-21 19:02:51 +08:00
RMBGAME f24a828cca 🚧 Misc 2023-11-21 16:06:22 +08:00
RMBGAME d56312246e 🚧 Misc 2023-11-21 15:51:02 +08:00
RMBGAME dc3dac0bdd 🚧 Misc 2023-11-21 14:05:19 +08:00
RMBGAME 16758c9295 🚧 Idle 2023-11-20 23:08:08 +08:00
RMBGAME cda93da954 🚧 Misc 2023-11-20 19:32:24 +08:00
Mossimo 93e35508f3 ↑ Submodules 2023-11-20 16:11:18 +08:00
Mossimo efc234e3fa ↑ dotnet 8.0 2023-11-20 15:18:58 +08:00
RMBGAME c31848929f 🚧 Misc 2023-11-20 15:05:00 +08:00
RMBGAME 0eff585092 🚧 Misc 2023-11-19 22:41:09 +08:00
RMBGAME eaeb90d853 💄 主页 UI 改进 2023-11-18 01:26:43 +08:00
RMBGAME 09dc6a5e81 🚧 Idle 2023-11-17 19:18:35 +08:00
Mossimo 8a56c52940 ↑ Update SteamClient 2023-11-17 13:55:36 +08:00
Mossimo 2f14d083ee 🐛 修复卸载脚本 rfq -> rf 2023-11-17 13:43:47 +08:00
Mossimo 07d5c3de27 🐛 Certutil 工具可移除 sql: 字符 减少兼容问题 2023-11-17 11:17:38 +08:00
RMBGAME 9e4c4836c0 🚧 Misc 2023-11-16 20:47:38 +08:00
RMBGAME d34a5d3347 🚧 改进主页数据加载逻辑 2023-11-16 20:47:38 +08:00
RMBGAME fd119ae0e1 ✨ 账号切换数据改为启动时加载避免等待 2023-11-16 20:47:37 +08:00
Mossimo 3d59c24dec 🐛 Misc 移除调试代码。添加注释。 2023-11-16 17:16:34 +08:00
Mossimo ea315a9b44 🐛 修复版本迁移导致的脚本失效。 2023-11-16 17:06:45 +08:00
RMBGAME d5592b4bc2 🐛 fix 2023-11-15 19:48:57 +08:00
RMBGAME 2df32c67d0 🐛 fix 2023-11-15 19:28:16 +08:00
RMBGAME fdaf795202 🐛 fix 2023-11-15 14:49:09 +08:00
john.jiang a7572ecd50 🎨 asf 路径修改启动问题修复 2023-11-15 14:43:05 +08:00
RMBGAME d433d60d29 ⬆️ Submodules 2023-11-15 14:42:54 +08:00
RMBGAME 3fe8f931a2 ✨ 令牌搜索功能 2023-11-15 14:42:21 +08:00
john.jiang a69d97fcdb 🎨 ASF fixs 2023-11-14 19:09:47 +08:00
RMBGAME aa63e3ab9f 🐛 fix UserIdleInfo 2023-11-13 19:15:20 +08:00
RMBGAME a56fc36380 fix Steam令牌某些情况下闪退问题 2023-11-13 16:08:43 +08:00
john.jiang 4c5428be5f 🎨 fixs 2023-11-11 19:54:59 +08:00
john.jiang e6953e50b8 🎨 idle fixs 2023-11-11 19:53:53 +08:00
ff m a961f68242 🐛 TrimStart(Constants.UrlSchemes.Login) -> Substring(Constants.UrlSchemes.Login.Length, loginUrl.Length - Constants.UrlSchemes.Login.Length) 2023-11-11 16:43:46 +08:00
ff m 72f301d526 🐛 MacOS Substring内存溢出 改用 TrimStart 已测试通过 2023-11-11 16:07:31 +08:00
john.jiang 2b18e03098 🎨 fixs 2023-11-10 18:36:35 +08:00
john.jiang 9fffe8458a 🎨 ASF Support 2023-11-10 18:32:20 +08:00
RMBGAME ea7282f290 🚧 Misc 2023-11-10 18:13:04 +08:00
RMBGAME 3714710dd8 fix 加速列表加载缓存数据时丢失选中状态问题 2023-11-04 21:27:25 +08:00
RMBGAME 00c228abd8 🐛 fix CI 2023-11-02 17:40:25 +08:00
Mossimo 54fc1e3593 ✨ Mac 支持 UrlSchemes 协议登录 2023-10-28 19:15:46 +08:00
RMBGAME d2d04a37cf ⬆️ Bump 2023-10-24 18:59:41 +08:00
RMBGAME 0600a5ca9f 🚧 Misc 2023-10-24 18:59:41 +08:00
Mossimo 01dc528e21 🐛 修改 install.sh -> online_install.sh 避免用户理解错误 添加 init_desktop.sh 添加桌面 desktop 文件 2023-10-19 17:48:27 +08:00
ff m 9014198aeb 🐛 修复 VDF Json 读取问题 2023-10-19 12:55:44 +08:00
RMBGAME bfd6b82a95 🚧 🐛 修复账号切换某些情况下切换后仍需要登录问题 2023-10-18 19:48:06 +08:00
RMBGAME a6a45e5324 🚧 Misc 2023-10-13 19:46:08 +08:00
RMBGAME 0ed0416f69 🐛 Fix 421 Misdirected Request 2023-10-06 21:12:10 +08:00
RMBGAME ce6fd9a4d6 ✨ 账号切换 Steam 离线模式启动 2023-10-06 18:33:40 +08:00
320 changed files with 15491 additions and 1612 deletions
+23
View File
@@ -216,6 +216,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam++", "src\BD.WTTS.Clie
EndProject
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BD.WTTS.Client.AppHost.Bridge.Package", "src\BD.WTTS.Client.AppHost.Bridge.Package\BD.WTTS.Client.AppHost.Bridge.Package.wapproj", "{27795C79-2994-4514-8656-BA34FCEE8901}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ref\ArchiSteamFarm\ArchiSteamFarm\ArchiSteamFarm.csproj", "{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1260,6 +1262,26 @@ Global
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.ActiveCfg = Release|x86
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Build.0 = Release|x86
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Deploy.0 = Release|x86
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.ActiveCfg = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.Build.0 = Debug|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.Build.0 = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.ActiveCfg = Release|Any CPU
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1326,6 +1348,7 @@ Global
{35676E23-8E54-4785-A75B-98EB0650596B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
{26D8AA24-2ACE-4167-BC2B-117B9BB76AA4} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
{27795C79-2994-4514-8656-BA34FCEE8901} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {567F588F-E95C-4286-8B30-DB3B2200221C}
+6 -6
View File
@@ -40,14 +40,14 @@ else
fi
}
certutil_Init() {
certutil -d sql:$HOME/.pki/nssdb -L
certutil -d $HOME/.pki/nssdb -L
ret=$?
if [ $ret -ne 0 ]; then
echo "即将初始化 certutil sql:\$HOME/.pki/nssdb"
mkdir -p sql:$HOME/.pki
mkdir -p sql:$HOME/.pki/nssdb
chmod 700 sql:$HOME/.pki/nssdb
certutil -d sql:$HOME/.pki/nssdb -N --empty-password
echo "即将初始化 certutil \$HOME/.pki/nssdb"
mkdir -p $HOME/.pki
mkdir -p $HOME/.pki/nssdb
chmod 700 $HOME/.pki/nssdb
certutil -d $HOME/.pki/nssdb -N --empty-password
else
echo "certutil nssdb 正常"
fi
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
#添加桌面文件
run_path=$(dirname "$0")
base_path="$(dirname "$run_path")"
exec_name="Steam++"
rm -rf "$HOME/Desktop/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"
exit 0
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Name=°²×° Watt Toolkit
Exec=curl https://steampp.net/pkg/latest/steamdeck/install.sh | bash -s --
Icon=steamdeck-gaming-return
Terminal=true
Type=Application
StartupNotify=false
+189
View File
@@ -0,0 +1,189 @@
#!/bin/bash
run_path=$(dirname "$0")
base_path="$(dirname "$run_path")"
mkdir -p "$base_path"
cd "$base_path" || exit 1
appVer_path="$base_path/WattToolkit.AppVer"
exec_name="Steam++"
tar_name="WattToolkit.tgz"
tar_path="$base_path/$tar_name"
app_name="Watt Toolkit"
PROCESS_NAMES=("$exec_name" "$app_name")
export LC_ALL=en_US.UTF-8
Install_certutil() {
# 判断发行版类型
if command -v certutil &>/dev/null; then
echo "certutil 工具已安装。"
else
echo "证书导入以及验证需要使用 certutil 工具。"
# 判断包管理器
if command -v apt &>/dev/null; then
# 使用 apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y libnss3-tools
elif command -v dnf &>/dev/null; then
# 使用 dnf (Fedora)
sudo dnf install -y nss-tools
elif command -v yum &>/dev/null; then
# 使用 yum (CentOS/Red Hat)
sudo yum install -y nss-tools
elif command -v pacman &>/dev/null; then
# 使用 pacman (Arch Linux)
# sudo pacman -S nss
echo "请手动安装 certutil 工具。"
exit 1
else
echo "请手动安装 certutil 工具。"
exit 1
fi
echo "certutil 工具已安装。"
fi
}
certutil_Init() {
certutil -d $HOME/.pki/nssdb -L
ret=$?
if [ $ret -ne 0 ]; then
echo "即将初始化 certutil \$HOME/.pki/nssdb"
mkdir -p $HOME/.pki
mkdir -p $HOME/.pki/nssdb
chmod 700 $HOME/.pki/nssdb
certutil -d $HOME/.pki/nssdb -N --empty-password
else
echo "certutil nssdb 正常"
fi
}
Install_jq() {
# Check if jq is already installed
if command -v jq &>/dev/null; then
echo "jq 工具已安装。"
else
echo "jq 用来解析版本更新。"
# Check the package manager
if command -v apt &>/dev/null; then
# Using apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y jq
elif command -v dnf &>/dev/null; then
# Using dnf (Fedora)
sudo dnf install -y jq
elif command -v yum &>/dev/null; then
# Using yum (CentOS/Red Hat)
sudo yum install -y jq
elif command -v pacman &>/dev/null; then
# Using pacman (Arch Linux)
sudo pacman -S jq
else
echo "请手动安装 jq 工具。"
exit 1
fi
echo "请手动安装 jq 工具。"
fi
}
Install_zenity() {
# 判断发行版类型
if command -v zenity &>/dev/null; then
echo "zenity 工具已安装。"
else
echo "安装过程需要 zenity 工具。"
# 判断包管理器
if command -v apt &>/dev/null; then
# 使用 apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y zenity
elif command -v dnf &>/dev/null; then
# 使用 dnf (Fedora)
sudo dnf install -y zenity
elif command -v yum &>/dev/null; then
# 使用 yum (CentOS/Red Hat)
sudo yum install -y zenity
elif command -v pacman &>/dev/null; then
# 使用 pacman (Arch Linux)
# sudo pacman -S zenity
echo "请手动安装 zenity 工具。"
exit 1
else
echo "请手动安装 zenity 工具。"
exit 1
fi
echo "zenity 工具已安装。"
fi
}
Show_Run() {
local param1=$1
# 显示提示框,询问是否运行程序
zenity --question --text="$1" --width=400
# 获取上一个命令的退出码
response=$?
if [ $response -eq 0 ]; then
echo "程序已启动。"
exit 0 &
# 用户点击了 "运行" 按钮,启动程序
/bin/sh -c "$base_path/$exec_name.sh"
else
# 用户点击了 "关闭" 按钮,退出脚本
exit 0
fi
}
Kill_Process() {
# 尝试的次数
Kill_MAX_RETRIES=3
# 循环尝试终止进程
for process_name in "${PROCESS_NAMES[@]}"; do
kill_retry=1
while [ $kill_retry -le $Kill_MAX_RETRIES ]; do
pid=$(pgrep "$process_name")
if [ -n "$pid" ]; then
echo "尝试 $kill_retry: 进程 $process_name 正在运行中。正在终止..."
kill $pid
sleep 2
else
break
fi
kill_retry=$((kill_retry + 1))
done
done
for process_name in "${PROCESS_NAMES[@]}"; do
# 检查是否成功终止进程
if pgrep -x "$process_name" >/dev/null; then
echo "无法终止程序 $process_name。尝试次数已达上限。"
exit 1
else
echo "程序 $process_name 已成功终止。"
fi
done
}
Install_certutil
Install_zenity
Install_jq
certutil_Init
Kill_Process
dotnet_exec="$dotnet_path/dotnet/dotnet"
if [ -x "$dotnet_exec" ]; then
echo "文件具有执行权限。"
else
chmod +x "$dotnet_exec"
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
[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"
# update-desktop-database ~/.local/share/applications
#运行程序
Show_Run "下载安装完成,是否启动程序?"
exit 0
@@ -41,14 +41,14 @@ Install_certutil() {
fi
}
certutil_Init() {
certutil -d sql:$HOME/.pki/nssdb -L
certutil -d $HOME/.pki/nssdb -L
ret=$?
if [ $ret -ne 0 ]; then
echo "即将初始化 certutil sql:\$HOME/.pki/nssdb"
mkdir -p sql:$HOME/.pki
mkdir -p sql:$HOME/.pki/nssdb
chmod 700 sql:$HOME/.pki/nssdb
certutil -d sql:$HOME/.pki/nssdb -N --empty-password
echo "即将初始化 certutil \$HOME/.pki/nssdb"
mkdir -p $HOME/.pki
mkdir -p $HOME/.pki/nssdb
chmod 700 $HOME/.pki/nssdb
certutil -d $HOME/.pki/nssdb -N --empty-password
else
echo "certutil nssdb 正常"
fi
+50 -11
View File
@@ -1,6 +1,6 @@
#!/bin/bash
base_path="$HOME/WattToolkit"
export LC_ALL=en_US.UTF-8
run_path=$(dirname "$0")
base_path="$(dirname "$run_path")"
exec_name="Steam++"
app_name="Watt Toolkit"
AppData="$XDG_DATA_HOME/Steam++"
@@ -8,7 +8,37 @@ AppData_t="$HOME/.local/share/Steam++"
Cache="$XDG_CACHE_HOME/Steam++"
Cache_t="$HOME/.cache/Steam+"
PROCESS_NAMES=("$exec_name" "$app_name")
export LC_ALL=en_US.UTF-8
#检查是否存在 Steam++.sh 避免卸载错误
file_to_check="$base_path/Steam++.sh"
folders_to_check=("dotnet" "modules" "native" "script" "assemblies")
if [ -e "$file_to_check" ]; then
echo "文件 $file_to_check 存在"
else
echo "文件 $file_to_check 不存在"
exit 1
fi
for folder in "${folders_to_check[@]}"; do
folder_path="$base_path/$folder"
if [ -d "$folder_path" ]; then
echo "文件夹 $folder_path 存在"
else
echo "文件夹 $folder_path 不存在"
exit 1
fi
done
echo "验证该文件夹是 $app_name 安装目录,请确认是否卸载。"
read -p "所有文件和文件夹都存在,确认卸载吗? (输入 'y' 确认): " confirmation
if [ "${confirmation,,}" == "y" ]; then
echo "开始执行卸载操作"
# 在这里添加您的卸载操作
else
echo "用户取消卸载操作"
exit 0
fi
Kill_Process() {
# 尝试的次数
Kill_MAX_RETRIES=3
@@ -42,19 +72,28 @@ Kill_Process() {
}
Kill_Process
zenity --question --text="是否删除用户数据" --width=400
zenity --question --text="是否删除证书?" --width=400
# 获取上一个命令的退出码
response=$?
delCer=$?
if [ $response -eq 0 ]; then
rm -rfq "$AppData" 2>/dev/null
rm -rfq "$AppData_t" 2>/dev/null
if [ $delCer -eq 0 ]; then
certutil -D -d $HOME/.pki/nssdb -n "SteamTools"
fi
zenity --question --text="是否删除用户数据?" --width=400
# 获取上一个命令的退出码
delUserData=$?
if [ $delUserData -eq 0 ]; then
rm -rf $AppData 2>/dev/null
rm -rf $AppData_t 2>/dev/null
certutil -D -d $HOME/.pki/nssdb -n "SteamTools"
else
echo "保留用户数据方便下次安装。"
echo "如需删除可手动删除该目录:$AppData 或者 $AppData_t"
fi
rm -rfq "$Cache" 2>/dev/null
rm -rfq "$Cache_t" 2>/dev/null
rm -rfq "$base_path" 2>/dev/null
rm -rfq "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
rm -rf $Cache 2>/dev/null
rm -rf $Cache_t 2>/dev/null
rm -rf $base_path 2>/dev/null
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
zenity --info --text="卸载完成!" --width=300
+19 -19
View File
@@ -14,7 +14,7 @@ CodesignKey_Name="mossimo code"
App_Modules_Path="$App_Path/$Publish_Path/modules"
echo "编译主程序"
cd "$App_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
rm -r "$App_Modules_Path"
mkdir -p "$App_Modules_Path"
@@ -30,59 +30,59 @@ Copy_Plugins() {
echo "编译加速插件"
Plugins_Accelerator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Accelerator"
cd "$Plugins_Accelerator_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Accelerator" "BD.WTTS.Client.Plugins.Accelerator.dll"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "Accelerator" "BD.WTTS.Client.Plugins.Accelerator.dll"
Plugins_Accelerator_ReverseProxy_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy"
cd "$Plugins_Accelerator_ReverseProxy_Path"
# 根据 Build_Mode 执行不同逻辑
if [ "$Build_Mode" = "any" ]; then
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net8.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net8.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
lipo -create "$Publish_Path/arm64/Steam++.Accelerator" "$Publish_Path/x64/Steam++.Accelerator" -output "$Publish_Path/Steam++.Accelerator"
Copy_Plugins "$Publish_Path" "Accelerator" "Steam++.Accelerator"
elif [ "$Build_Mode" = "arm64" ]; then
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net8.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Publish_Path/arm64" "Accelerator" "Steam++.Accelerator"
elif [ "$Build_Mode" = "x64" ]; then
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net8.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Publish_Path/x64" "Accelerator" "Steam++.Accelerator"
fi
echo "编译令牌插件"
Plugins_Authenticator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Authenticator"
cd "$Plugins_Authenticator_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
echo "编译账户插件"
Plugins_GameAccount_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameAccount"
cd "$Plugins_GameAccount_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
echo "编译游戏库插件"
Plugins_GameList_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameList"
cd "$Plugins_GameList_Path"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
echo "复制插件到程序目录"
cd "$App_Path"
# 根据 Build_Mode 执行不同逻辑
if [ "$Build_Mode" = "any" ]; then
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/MonoBundle"
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/MonoBundle"
elif [ "$Build_Mode" = "arm64" ]; then
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-arm64/Steam++.app/Contents/MonoBundle"
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/osx-arm64/Steam++.app/Contents/MonoBundle"
cp -r "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents"
elif [ "$Build_Mode" = "x64" ]; then
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
cp -r "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents"
fi
echo "代码签名重新手动签名"
App_Publish_Path="$App_Path/bin/Release/net7.0-macos/Steam++.app"
App_Publish_Path="$App_Path/bin/Release/net8.0-macos/Steam++.app"
# #代码签名重新手动签名
codesign --remove-signature "$App_Publish_Path"
codesign --remove-signature "$App_Publish_Path/Contents/MacOS/Steam++"
+203
View File
@@ -964,6 +964,209 @@
}
],
"Usings": ""
},
"SteamIdleSettings": {
"ClassFilePath": "..\\src\\BD.WTTS.Client.Plugins.SteamIdleCard\\Settings",
"InterfaceFilePath": "..\\src\\BD.WTTS.Client.Plugins.SteamIdleCard\\Settings\\Abstractions",
"Properties": [
{
"TypeName": "TimeSpan",
"PropertyName": "IdleTime",
"DefaultValue": "TimeSpan.FromMinutes(6)",
"DefaultValueIsConst": false,
"Summary": "挂卡状态更新时间",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "IdleRule",
"PropertyName": "IdleRule",
"DefaultValue": "IdleRule.FastMode",
"DefaultValueIsConst": false,
"Summary": "运行规则",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "IdleSequentital",
"PropertyName": "IdleSequentital",
"DefaultValue": "IdleSequentital.Default",
"DefaultValueIsConst": false,
"Summary": "运行顺序",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "MaxIdleCount",
"DefaultValue": "30",
"DefaultValueIsConst": false,
"Summary": "最大并行运行游戏数量",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "double",
"PropertyName": "MinRunTime",
"DefaultValue": "2",
"DefaultValueIsConst": false,
"Summary": "最少游戏时间 hours",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "double",
"PropertyName": "SwitchTime",
"DefaultValue": "5000",
"DefaultValueIsConst": false,
"Summary": "自动切换游戏时间间隔 ms",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "double",
"PropertyName": "RefreshBadgesTime",
"DefaultValue": "6",
"DefaultValueIsConst": false,
"Summary": "自动刷新徽章数据时间间隔 min",
"IsRegionOrEndregion": null,
"IsValueType": true
}
],
"Usings": ""
},
"ASFSettings": {
"ClassFilePath": "..\\src\\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\\Settings",
"InterfaceFilePath": "..\\src\\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\\Settings\\Abstractions",
"Properties": [
{
"TypeName": "string",
"PropertyName": "ArchiSteamFarmExePath",
"DefaultValue": "string.Empty",
"DefaultValueIsConst": false,
"Summary": "ASF 路径",
"IsRegionOrEndregion": null,
"IsValueType": false
},
{
"TypeName": "bool",
"PropertyName": "AutoRunArchiSteamFarm",
"DefaultValue": "false",
"DefaultValueIsConst": false,
"Summary": "程序启动时自动运行 ASF",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "bool",
"PropertyName": "CheckArchiSteamFarmExe",
"DefaultValue": "false",
"DefaultValueIsConst": false,
"Summary": "检查文件安全性",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "ConsoleMaxLineDefault",
"DefaultValue": "200",
"DefaultValueIsConst": true,
"Summary": "控制台默认最大行数",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "MinRangeConsoleMaxLine",
"DefaultValue": "DefaultConsoleMaxLineDefault",
"DefaultValueIsConst": true,
"Summary": "控制台默认最大行数范围最小值",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "MaxRangeConsoleMaxLine",
"DefaultValue": "5000",
"DefaultValueIsConst": true,
"Summary": "控制台默认最大行数范围最大值",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "ConsoleMaxLine",
"DefaultValue": "DefaultConsoleMaxLineDefault",
"DefaultValueIsConst": false,
"Summary": "控制台最大行数",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "ConsoleFontSizeDefault",
"DefaultValue": "14",
"DefaultValueIsConst": true,
"Summary": "",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "MinRangeConsoleFontSize",
"DefaultValue": "8",
"DefaultValueIsConst": true,
"Summary": "",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "MaxRangeConsoleFontSize",
"DefaultValue": "24",
"DefaultValueIsConst": true,
"Summary": "",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "ConsoleFontSize",
"DefaultValue": "DefaultConsoleFontSizeDefault",
"DefaultValueIsConst": false,
"Summary": "控制台字体大小",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "int",
"PropertyName": "IPCPortIdValue",
"DefaultValue": "6242",
"DefaultValueIsConst": true,
"Summary": "",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "int",
"PropertyName": "IPCPortId",
"DefaultValue": "DefaultIPCPortIdValue",
"DefaultValueIsConst": false,
"Summary": "IPC 端口号,默认值为 <see cref=\"DefaultIPCPortIdValue\" />",
"IsRegionOrEndregion": null,
"IsValueType": true
},
{
"TypeName": "bool",
"PropertyName": "IPCPortOccupiedRandom",
"DefaultValue": "true",
"DefaultValueIsConst": false,
"Summary": "IPC 端口号被占用时是否随机一个未使用的端口号,默认值 <see langword=\"true\" />",
"IsRegionOrEndregion": null,
"IsValueType": true
}
],
"Usings": ""
}
}
}
BIN
View File
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -21,7 +21,7 @@ public static partial class AssemblyInfo
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "3";
const string ver_for_rc = "5";
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
+3
View File
@@ -22,6 +22,9 @@ global using Avalonia.Metadata;
[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.Client.Resources")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "Mobius.Models")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "Mobius")]
[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")]
+1 -1
View File
@@ -10,6 +10,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="7.0.11"/>
<add key="d" value="8.0.0"/>
</appSettings>
</configuration>
+2 -2
View File
@@ -16,9 +16,9 @@ static unsafe partial class Program
{
static Program()
{
dotnet_version_major = "7";
dotnet_version_major = "8";
dotnet_version_minor = "0";
dotnet_version_build = "11";
dotnet_version_build = "0";
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
}
@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.AppHost"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="asInvoker" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.AppHost"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
@@ -98,14 +98,14 @@
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll')">
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll')">
<!-- Windows Only -->
<!-- 修复:存在 Microsoft.WindowsDesktop.App 依赖时不会将 System.Drawing.Common.dll 复制到输出目录-->
<None Include="$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll">
<None Include="$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Reference Include="System.Drawing.Common">
<HintPath>$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll</HintPath>
<HintPath>$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll</HintPath>
</Reference>
</ItemGroup>
@@ -22,6 +22,8 @@
<string>NSApplication</string>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>
<key>CFBundleIdentifier</key>
<string>net.steampp.app</string>
<key>CFBundleURLTypes</key>
@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.App"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="asInvoker" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.App"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
@@ -126,6 +126,12 @@
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Views\Controls\Widgets\CarouselItems.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
</Project>
@@ -18,6 +18,8 @@ public sealed class EnumLocalizationNameConverter : IValueConverter
else
{
text = Strings.ResourceManager.GetString(desc, Strings.Culture);
if (string.IsNullOrEmpty(text))
return desc;
}
if (!string.IsNullOrEmpty(text))
@@ -1,19 +1,23 @@
namespace BD.WTTS.Converters;
public sealed class VisbleStringConverter : IMultiValueConverter
public sealed class VisibleStringConverter : IMultiValueConverter
{
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
if (values.Count >= 3 && values.FirstOrDefault() is bool isVisble)
if (values.Count >= 2 && values.FirstOrDefault() is bool isVisble)
{
if (isVisble)
{
return values[1];
}
else
else if (values.Count >= 3)
{
return values[2];
}
else
{
return null;
}
}
return values;
}
@@ -2,18 +2,11 @@ namespace BD.WTTS.UI;
public static class ControlExtensions
{
public static void SetViewModel<T>(this Control c, bool useCache = true) where T : ViewModelBase
public static void SetViewModel<T>(this Control c) where T : ViewModelBase, new()
{
if (c.DataContext == null || c.DataContext.GetType() != typeof(T))
{
T cx;
if (useCache)
cx = IViewModelManager.Instance.Get<T>();
else
cx = Activator.CreateInstance<T>();
c.DataContext = cx;
c.DataContext = new T();
}
}
}
@@ -0,0 +1,31 @@
namespace BD.WTTS;
/// <summary>
/// <see cref="IEnumerable"/> extensions methods
/// </summary>
public static class IEnumerableExtensions
{
public static IEnumerable<IEnumerable> Batch(this IEnumerable source, int size)
{
object[]? bucket = null;
var count = 0;
foreach (var item in source)
{
bucket ??= new object[size];
bucket[count++] = item;
if (count == size)
{
yield return bucket;
bucket = null;
count = 0;
}
}
if (bucket != null && count > 0)
yield return bucket.Take(count).ToArray();
}
}
@@ -4,7 +4,7 @@ using System.Collections.Immutable;
namespace BD.WTTS.Services.Implementation;
/// <inheritdoc cref="IWindowManager"/>
sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
{
Type? IWindowManagerImpl.WindowType => typeof(Window);
@@ -116,7 +116,7 @@ sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
if (viewModel is IWindowViewModel window)
{
window.Close = () => td?.Hide(TaskDialogStandardResult.Close);
window.Close = b => td?.Hide(b ? TaskDialogStandardResult.OK : TaskDialogStandardResult.Close);
}
td.DataContext = viewModel;
@@ -88,7 +88,7 @@ partial class App
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
},
Converter = (VisbleStringConverter)App.Instance.FindResource("VisbleStringConverter")!,
Converter = (VisibleStringConverter)App.Instance.FindResource(nameof(VisibleStringConverter))!,
},
Command = ReactiveCommand.Create(async () =>
{
+1 -1
View File
@@ -17,7 +17,7 @@
<spp:EnumEqualValueConverter x:Key="EnumEqualValueConverter" />
<spp:StringEqualsConverter x:Key="StringEqualsConverter" />
<spp:EnumLocalizationNameConverter x:Key="EnumLocalizationNameConverter" />
<spp:VisbleStringConverter x:Key="VisbleStringConverter" />
<spp:VisibleStringConverter x:Key="VisibleStringConverter" />
</Application.Resources>
<Application.Styles>
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="True" />
+4 -2
View File
@@ -46,6 +46,7 @@ public sealed partial class App : Application
#if WINDOWS || LINUX || MACOS
if (GeneralSettings.MinimizeOnStartup.Value)
Startup.Instance.IsMinimize = true;
if (Startup.Instance.IsSteamRun)
{
try
@@ -93,15 +94,16 @@ public sealed partial class App : Application
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
MainWindow = InitializeMainWindow?.Invoke(this) ?? new MainWindow();
desktop.MainWindow =
#if !UI_DEMO
Startup.Instance.IsMinimize ? null :
Startup.Instance.IsMinimize && MainWindow is MainWindow ? null :
#endif
MainWindow;
if (Startup.Instance.IsMinimize && MainWindow is AppWindow appWindow)
{
Task2.InBackground(() => appWindow.SplashScreen.RunTasks(CancellationToken.None));
Task2.InBackground(() => appWindow.SplashScreen?.RunTasks(CancellationToken.None));
}
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -13,8 +13,9 @@
<Thickness x:Key="PageMargin">10</Thickness>
<sys:Double x:Key="SettingsSpacing">3</sys:Double>
<!--<sys:Char x:Key="PasswordChar">&#x25CF;</sys:Char>-->
<sys:Char x:Key="PasswordChar">•</sys:Char>
<sys:Char x:Key="PasswordChar">&#x25CF;</sys:Char>
<!--<sys:Char x:Key="PasswordChar">•</sys:Char>
<sys:Char x:Key="PasswordChar">·</sys:Char>-->
<FontFamily x:Key="ContentControlThemeFontFamily">Null</FontFamily>
<BoxShadows x:Key="InfoBoxShadow">4 4 32 0 #66000000</BoxShadows>
@@ -44,6 +45,7 @@
<StyleInclude Source="/UI/Styling/InfoBox.axaml" />
<StyleInclude Source="/UI/Styling/Controls/PageBase.axaml" />
<StyleInclude Source="/UI/Styling/Controls/CarouselItems.axaml" />
<StyleInclude Source="/UI/Styling/Controls/OptionsDisplayItem.axaml" />
<StyleInclude Source="/UI/Styling/Controls/RippleEffect.axaml" />
<StyleInclude Source="/UI/Styling/Controls/Draggable.axaml" />
@@ -180,6 +180,7 @@
<TextBlock
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="0,5"
VerticalAlignment="Top"
Classes="CaptionTextBlockStyle"
@@ -16,7 +16,7 @@
<!-- AutoCompleteBox -->
<Style Selector="AutoCompleteBox.Search /template/ TextBox#PART_TextBox">
<Setter Property="Watermark" Value="{ReflectionBinding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="Watermark" Value="{Binding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="InnerRightContent">
<Template>
<StackPanel Orientation="Horizontal">
@@ -0,0 +1,112 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:generic="using:System.Collections.Generic"
xmlns:spp="https://steampp.net/ui"
xmlns:sys="using:System"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<spp:CarouselItems>
<spp:CarouselItems.Items>
<Border Background="Red" />
<Border Background="Blue" />
<Border Background="Black" />
<Border Background="Yellow" />
</spp:CarouselItems.Items>
</spp:CarouselItems>
</Border>
</Design.PreviewWith>
<Style Selector="spp|CarouselItems">
<Setter Property="Template">
<ControlTemplate>
<Border
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0"
ClipToBounds="True"
CornerRadius="8">
<Grid RowDefinitions="*,5,Auto">
<Carousel Name="CarouselControl" Classes="AnimeSlide">
<Carousel.ItemTemplate>
<DataTemplate DataType="sys:Object">
<ItemsControl ItemTemplate="{Binding $parent[spp:CarouselItems].ItemTemplate}" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<spp:FixedWrapPanel ItemsPerLine="{Binding $parent[spp:CarouselItems].ItemsPerPage}" Spacing="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Carousel.ItemTemplate>
</Carousel>
<Button
Name="Left"
HorizontalAlignment="Left"
Background="#80000000"
Classes="action"
IsVisible="False">
<ui:SymbolIcon FontSize="18" Symbol="ChevronLeft" />
</Button>
<Button
Name="Right"
HorizontalAlignment="Right"
Background="#80000000"
Classes="action"
IsVisible="False">
<ui:SymbolIcon FontSize="18" Symbol="ChevronRight" />
</Button>
<Panel
Grid.Row="2"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<ItemsRepeater Name="Swiper" IsVisible="False">
<ItemsRepeater.Layout>
<StackLayout
DisableVirtualization="True"
Orientation="Horizontal"
Spacing="8" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
</DataTemplate.DataType>
<Button
Padding="0"
Classes="Plain"
Command="{Binding $parent[spp:CarouselItems].CarouselBannerIndexCommand}"
CommandParameter="{Binding Key}">
<Ellipse
Name="IndexEllipse"
Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{Binding Value}"
Opacity="0.6"
StrokeThickness="0" />
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Panel>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="spp|CarouselItems /template/ Button.action">
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="64" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
</Style>
</Styles>
@@ -75,19 +75,29 @@
</ContentPresenter.IsVisible>-->
</ContentPresenter>
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding NoResultMessage}"
Foreground="{TemplateBinding Foreground}"
Theme="{StaticResource SubtitleTextBlockStyle}">
<ContentPresenter.IsVisible>
<Panel HorizontalAlignment="Center" VerticalAlignment="Center">
<spp:Image2
Width="100"
Height="100"
Margin="0,0,0,120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/empty.png"
Stretch="UniformToFill" />
<Panel.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="IsShowNoResultText" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="!IsLoading" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</ContentPresenter.IsVisible>
</ContentPresenter>
</Panel.IsVisible>
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding NoResultMessage}"
Foreground="{TemplateBinding Foreground}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
</Panel>
</Panel>
</ControlTemplate>
</Setter>
@@ -39,8 +39,8 @@
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Panel
Name="FilePickerTipBorder"
MaxWidth="{ReflectionBinding $parent.Bounds.Width}"
MaxHeight="{ReflectionBinding $parent.Bounds.Height}">
MaxWidth="{Binding $parent.Bounds.Width}"
MaxHeight="{Binding $parent.Bounds.Height}">
<Border Background="{DynamicResource ControlOnImageFillColorSecondaryBrush}" Opacity="0.8">
<StackPanel
Margin="10,5"
@@ -49,9 +49,7 @@
<ui:SymbolIcon FontSize="30" Symbol="Download" />
<TextBlock
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{ReflectionBinding Path=Res.CustomFilePicker_Tip,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.CustomFilePicker_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
TextAlignment="Center"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
</StackPanel>
@@ -59,12 +59,10 @@
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
<Grid
MinHeight="{TemplateBinding MinHeight}"
ColumnDefinitions="Auto,*,Auto,Auto"
RowDefinitions="*,Auto">
<Grid MinHeight="{TemplateBinding MinHeight}" ColumnDefinitions="Auto,*,Auto,Auto">
<Viewbox
Name="IconHost"
Grid.RowSpan="2"
Width="{DynamicResource SettingsExpanderItemIconSize}"
Height="{DynamicResource SettingsExpanderItemIconSize}"
HorizontalAlignment="Left"
@@ -76,28 +74,31 @@
Margin="{DynamicResource SettingsExpanderItemContentMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<TextBlock
<ContentPresenter
Name="HeaderText"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="BaseTextBlockStyle"
Text="{TemplateBinding Header}"
Content="{TemplateBinding Header}"
TextTrimming="CharacterEllipsis" />
<TextBlock
<ContentPresenter
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
HorizontalContentAlignment="Stretch"
Classes="CaptionTextBlockStyle"
Content="{TemplateBinding Description}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
IsVisible="{TemplateBinding Description,
Converter={StaticResource IsNullConverter},
ConverterParameter=invert}"
Text="{TemplateBinding Description}"
TextWrapping="Wrap" />
</StackPanel>
<ContentPresenter
Name="ActionButton"
Grid.RowSpan="2"
Grid.Column="2"
Margin="{DynamicResource SettingsExpanderItemFooterMargin}"
HorizontalAlignment="Right"
@@ -106,6 +107,7 @@
<ui:SymbolIcon
Name="Chevron"
Grid.RowSpan="2"
Grid.Column="3"
Margin="24,4,0,4"
VerticalAlignment="Center"
@@ -17,10 +17,7 @@
Classes="Circle"
Value="60" />
<ProgressBar Width="150" IsIndeterminate="True" />
<ProgressBar
Width="150"
Classes="notransitions"
Value="50" />
<ProgressBar Width="150" Value="50" />
<TextBlock Text="Material Progress" />
<ProgressBar
@@ -31,7 +28,6 @@
<ProgressBar
Width="150"
Height="5"
Classes="Material notransitions"
Value="50" />
</StackPanel>
</Border>
@@ -58,7 +54,7 @@
<Setter Property="Width" Value="{DynamicResource ProgressBarMinHeight}" />
</Style>
<Style Selector="ProgressBar:not(.notransitions) /template/ Border#PART_Indicator">
<Style Selector="ProgressBar[IsIndeterminate=False] /template/ Border#PART_Indicator">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition
@@ -296,8 +296,8 @@
</Styles.Resources>
<Style Selector="ToggleSwitch">
<Setter Property="OffContent" Value="{ReflectionBinding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="OnContent" Value="{ReflectionBinding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="OffContent" Value="{Binding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="OnContent" Value="{Binding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="MinWidth" Value="0" />
</Style>
@@ -52,7 +52,7 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
//SystemDecorations = SystemDecorations.BorderOnly;
Background = null;
TransparencyBackgroundFallback = Brushes.Transparent;
TransparencyBackgroundFallback = OperatingSystem2.IsMacOS() ? Brushes.Black : Brushes.Transparent;
TransparencyLevelHint = new WindowTransparencyLevel[] { UISettings.WindowBackgroundMaterial.Value.ToWindowTransparencyLevel() };
if (IsSaveWindowSize)
@@ -8,9 +8,7 @@
xmlns:spp="https://steampp.net/ui"
d:DesignHeight="450"
d:DesignWidth="800"
IsVisible="{ReflectionBinding IsShowAdvertise,
Mode=OneWay,
Source={x:Static s:AdvertiseService.Current}}"
IsVisible="{Binding IsShowAdvertise, Mode=OneWay, Source={x:Static s:AdvertiseService.Current}}"
mc:Ignorable="d">
<Border ClipToBounds="True" CornerRadius="{DynamicResource ControlCornerRadius}">
<Panel>
@@ -20,15 +18,14 @@
<Button
Padding="0"
Classes="Plain"
Command="{ReflectionBinding ClickAdvertisementCommand,
Mode=OneWay,
Source={x:Static s:AdvertiseService.Current}}"
Command="{Binding ClickAdvertisementCommand, Mode=OneWay, Source={x:Static s:AdvertiseService.Current}}"
CommandParameter="{Binding}"
Cursor="Hand"
ToolTip.Tip="{Binding Describe}">
<spp:Image2
Width="{Binding $parent[UserControl].Bounds.Width}"
Height="{Binding $parent[UserControl].Bounds.Height}"
EnableCancelToken="False"
Source="{Binding ImageUrl}"
Stretch="UniformToFill" />
</Button>
@@ -39,17 +36,13 @@
Margin="10"
FontSize="10"
Foreground="White"
Text="{ReflectionBinding Path=Res.Advertisement,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
Text="{Binding Path=Res.Advertisement, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Button
Name="CloseAdBtn"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="Transparent"
Content="{ReflectionBinding Path=Res.Advertisement_Close,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Content="{Binding Path=Res.Advertisement_Close, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Cursor="Hand"
Foreground="White" />
</Panel>
@@ -0,0 +1,13 @@
namespace BD.WTTS.UI.Views.Controls;
public enum AlignContent
{
FlexStart,
FlexEnd,
Center,
Stretch,
SpaceBetween,
SpaceAround,
SpaceEvenly
}
@@ -0,0 +1,10 @@
namespace BD.WTTS.UI.Views.Controls;
[SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names")]
public enum AlignItems
{
FlexStart,
FlexEnd,
Center,
Stretch
}
@@ -0,0 +1,183 @@
namespace BD.WTTS.UI.Views.Controls;
public static class Flex
{
/// <summary>
/// Defines an attached property to control the alignment of a specific child in a flex layout.
/// </summary>
public static readonly AttachedProperty<AlignItems?> AlignSelfProperty =
AvaloniaProperty.RegisterAttached<Layoutable, AlignItems?>("AlignSelf", typeof(Flex));
/// <summary>
/// Defines an attached property to control the order of a specific child in a flex layout.
/// </summary>
public static readonly AttachedProperty<int> OrderProperty =
AvaloniaProperty.RegisterAttached<Layoutable, int>("Order", typeof(Flex));
public static readonly AttachedProperty<FlexBasis> BasisProperty =
AvaloniaProperty.RegisterAttached<Layoutable, FlexBasis>("Basis", typeof(Flex), FlexBasis.Auto);
public static readonly AttachedProperty<double> ShrinkProperty =
AvaloniaProperty.RegisterAttached<Layoutable, double>("Shrink", typeof(Flex), 1.0, validate: v => v >= 0.0);
public static readonly AttachedProperty<double> GrowProperty =
AvaloniaProperty.RegisterAttached<Layoutable, double>("Grow", typeof(Flex), 0.0, validate: v => v >= 0.0);
internal static readonly AttachedProperty<double> BaseLengthProperty =
AvaloniaProperty.RegisterAttached<Layoutable, double>("BaseLength", typeof(Flex), 0.0);
internal static readonly AttachedProperty<double> CurrentLengthProperty =
AvaloniaProperty.RegisterAttached<Layoutable, double>("CurrentLength", typeof(Flex), 0.0);
/// <summary>
/// Gets the child alignment in a flex layout
/// </summary>
public static AlignItems? GetAlignSelf(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(AlignSelfProperty);
}
/// <summary>
/// Sets the child alignment in a flex layout
/// </summary>
public static void SetAlignSelf(Layoutable layoutable, AlignItems? value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(AlignSelfProperty, value);
}
/// <summary>
/// Gets the child order in a flex layout
/// </summary>
public static int GetOrder(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(OrderProperty);
}
/// <summary>
/// Sets the child order in a flex layout
/// </summary>
public static void SetOrder(Layoutable layoutable, int value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(OrderProperty, value);
}
public static FlexBasis GetBasis(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(BasisProperty);
}
public static void SetBasis(Layoutable layoutable, FlexBasis value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(BasisProperty, value);
}
public static double GetShrink(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(ShrinkProperty);
}
public static void SetShrink(Layoutable layoutable, double value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(ShrinkProperty, value);
}
public static double GetGrow(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(GrowProperty);
}
public static void SetGrow(Layoutable layoutable, double value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(GrowProperty, value);
}
internal static double GetBaseLength(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(BaseLengthProperty);
}
internal static void SetBaseLength(Layoutable layoutable, double value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(BaseLengthProperty, value);
}
internal static double GetCurrentLength(Layoutable layoutable)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
return layoutable.GetValue(CurrentLengthProperty);
}
internal static void SetCurrentLength(Layoutable layoutable, double value)
{
if (layoutable is null)
{
throw new ArgumentNullException(nameof(layoutable));
}
layoutable.SetValue(CurrentLengthProperty, value);
}
}
@@ -0,0 +1,66 @@
namespace BD.WTTS.UI.Views.Controls;
public readonly struct FlexBasis : IEquatable<FlexBasis>
{
public double Value { get; }
public FlexBasisKind Kind { get; }
public FlexBasis(double value, FlexBasisKind kind)
{
if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
throw new ArgumentException($"Invalid basis value: {value}", nameof(value));
if (kind < FlexBasisKind.Auto || kind > FlexBasisKind.Relative)
throw new ArgumentException($"Invalid basis kind: {kind}", nameof(kind));
Value = value;
Kind = kind;
}
public FlexBasis(double value) : this(value, FlexBasisKind.Absolute) { }
public static FlexBasis Auto => new(0.0, FlexBasisKind.Auto);
public bool IsAuto => Kind == FlexBasisKind.Auto;
public bool IsAbsolute => Kind == FlexBasisKind.Absolute;
public bool IsRelative => Kind == FlexBasisKind.Relative;
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
public bool Equals(FlexBasis other) =>
(IsAuto && other.IsAuto) || (Value == other.Value && Kind == other.Kind);
public override bool Equals(object? obj) =>
obj is FlexBasis other && Equals(other);
public override int GetHashCode() =>
HashCode.Combine(Value, (int)Kind);
public static bool operator ==(FlexBasis left, FlexBasis right) =>
left.Equals(right);
public static bool operator !=(FlexBasis left, FlexBasis right) =>
!left.Equals(right);
public override string ToString()
{
return Kind switch
{
FlexBasisKind.Auto => "Auto",
FlexBasisKind.Absolute => FormattableString.Invariant($"{Value:G17}"),
FlexBasisKind.Relative => FormattableString.Invariant($"{Value * 100:G17}%"),
_ => throw new InvalidOperationException(),
};
}
public static FlexBasis Parse(string str)
{
return str.ToUpperInvariant() switch
{
"AUTO" => Auto,
var s when s.EndsWith("%") => new FlexBasis(ParseDouble(s[..^1].Trim()) / 100, FlexBasisKind.Relative),
_ => new FlexBasis(ParseDouble(str), FlexBasisKind.Absolute),
};
double ParseDouble(string s) => double.Parse(s, CultureInfo.InvariantCulture);
}
}
@@ -0,0 +1,8 @@
namespace BD.WTTS.UI.Views.Controls;
public enum FlexBasisKind
{
Auto,
Absolute,
Relative,
}
@@ -0,0 +1,9 @@
namespace BD.WTTS.UI.Views.Controls;
public enum FlexDirection
{
Row,
RowReverse,
Column,
ColumnReverse
}
@@ -0,0 +1,468 @@
namespace BD.WTTS.UI.Views.Controls;
public sealed class FlexPanel : Panel
{
private static readonly Func<Layoutable, int> s_getOrder = x => x is { } y ? Flex.GetOrder(y) : 0;
private static readonly Func<Layoutable, bool> s_isVisible = x => x.IsVisible;
/// <summary>
/// Defines the <see cref="Direction"/> property.
/// </summary>
public static readonly StyledProperty<FlexDirection> DirectionProperty =
AvaloniaProperty.Register<FlexPanel, FlexDirection>(nameof(Direction));
/// <summary>
/// Defines the <see cref="JustifyContent"/> property.
/// </summary>
public static readonly StyledProperty<JustifyContent> JustifyContentProperty =
AvaloniaProperty.Register<FlexPanel, JustifyContent>(nameof(JustifyContent));
/// <summary>
/// Defines the <see cref="AlignItems"/> property.
/// </summary>
public static readonly StyledProperty<AlignItems> AlignItemsProperty =
AvaloniaProperty.Register<FlexPanel, AlignItems>(nameof(AlignItems));
/// <summary>
/// Defines the <see cref="AlignContent"/> property.
/// </summary>
public static readonly StyledProperty<AlignContent> AlignContentProperty =
AvaloniaProperty.Register<FlexPanel, AlignContent>(nameof(AlignContent));
/// <summary>
/// Defines the <see cref="Wrap"/> property.
/// </summary>
public static readonly StyledProperty<FlexWrap> WrapProperty =
AvaloniaProperty.Register<FlexPanel, FlexWrap>(nameof(Wrap), FlexWrap.Wrap);
/// <summary>
/// Defines the <see cref="ColumnSpacing"/> property.
/// </summary>
public static readonly StyledProperty<double> ColumnSpacingProperty =
AvaloniaProperty.Register<FlexPanel, double>(nameof(ColumnSpacing));
/// <summary>
/// Defines the <see cref="RowSpacing"/> property.
/// </summary>
public static readonly StyledProperty<double> RowSpacingProperty =
AvaloniaProperty.Register<FlexPanel, double>(nameof(RowSpacing));
private FlexLayoutState? _state;
static FlexPanel()
{
AffectsMeasure<FlexPanel>(
DirectionProperty,
JustifyContentProperty,
WrapProperty,
ColumnSpacingProperty,
RowSpacingProperty);
AffectsArrange<FlexPanel>(
AlignItemsProperty,
AlignContentProperty);
AffectsParentMeasure<FlexPanel>(
HorizontalAlignmentProperty,
VerticalAlignmentProperty,
Flex.OrderProperty,
Flex.BasisProperty,
Flex.ShrinkProperty,
Flex.GrowProperty);
AffectsParentArrange<FlexPanel>(
Flex.AlignSelfProperty);
}
/// <summary>
/// Gets or sets the flex direction
/// </summary>
public FlexDirection Direction
{
get => GetValue(DirectionProperty);
set => SetValue(DirectionProperty, value);
}
/// <summary>
/// Gets or sets the flex justify content mode
/// </summary>
public JustifyContent JustifyContent
{
get => GetValue(JustifyContentProperty);
set => SetValue(JustifyContentProperty, value);
}
/// <summary>
/// Gets or sets the flex align items mode
/// </summary>
public AlignItems AlignItems
{
get => GetValue(AlignItemsProperty);
set => SetValue(AlignItemsProperty, value);
}
/// <summary>
/// Gets or sets the flex align content mode
/// </summary>
public AlignContent AlignContent
{
get => GetValue(AlignContentProperty);
set => SetValue(AlignContentProperty, value);
}
/// <summary>
/// Gets or sets the flex wrap mode
/// </summary>
public FlexWrap Wrap
{
get => GetValue(WrapProperty);
set => SetValue(WrapProperty, value);
}
/// <summary>
/// Gets or sets the column spacing
/// </summary>
public double ColumnSpacing
{
get => GetValue(ColumnSpacingProperty);
set => SetValue(ColumnSpacingProperty, value);
}
/// <summary>
/// Gets or sets the row spacing
/// </summary>
public double RowSpacing
{
get => GetValue(RowSpacingProperty);
set => SetValue(RowSpacingProperty, value);
}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
var children = (IReadOnlyList<Layoutable>)Children;
var isColumn = Direction is FlexDirection.Column or FlexDirection.ColumnReverse;
var max = Uv.FromSize(availableSize, isColumn);
var spacing = Uv.FromSize(ColumnSpacing, RowSpacing, isColumn);
var (lineU, lineV, lineShrink, lineGrow, lineAutoMargins) = (0.0, 0.0, 0.0, 0.0, 0);
var (childIndex, firstChildIndex, itemIndex, lineIndex) = (0, 0, 0, 0);
var lines = new List<FlexLine>();
children = children.Where(s_isVisible).OrderBy(s_getOrder).ToArray();
foreach (var element in children)
{
var basis = Flex.GetBasis(element);
var flexConstraint = basis.Kind switch
{
FlexBasisKind.Auto => max.U,
FlexBasisKind.Absolute => basis.Value,
FlexBasisKind.Relative => max.U * basis.Value / 100,
_ => throw new InvalidOperationException()
};
element.Measure(Uv.ToSize(max.WithU(flexConstraint), isColumn));
var size = Uv.FromSize(element.DesiredSize, isColumn);
var flexLength = basis.Kind switch
{
FlexBasisKind.Auto => size.U,
FlexBasisKind.Absolute or FlexBasisKind.Relative => Math.Max(size.U, flexConstraint),
_ => throw new InvalidOperationException()
};
size = size.WithU(flexLength);
Flex.SetBaseLength(element, flexLength);
Flex.SetCurrentLength(element, flexLength);
if (Wrap != FlexWrap.NoWrap && lineU + size.U + itemIndex * spacing.U > max.U)
{
lines.Add(new FlexLine(firstChildIndex, childIndex - 1,
lineU, lineV, lineShrink, lineGrow, lineAutoMargins));
(lineU, lineV, lineShrink, lineGrow, lineAutoMargins) = (0.0, 0.0, 0.0, 0.0, 0);
firstChildIndex = childIndex;
itemIndex = 0;
lineIndex++;
}
lineU += size.U;
lineV = Math.Max(lineV, size.V);
lineShrink += Flex.GetShrink(element);
lineGrow += Flex.GetGrow(element);
lineAutoMargins += GetItemAutoMargins(element, isColumn);
itemIndex++;
childIndex++;
}
if (itemIndex != 0)
{
lines.Add(new FlexLine(firstChildIndex, firstChildIndex + itemIndex - 1,
lineU, lineV, lineShrink, lineGrow, lineAutoMargins));
}
var state = new FlexLayoutState(children, lines, Wrap);
var totalSpacingV = (lines.Count - 1) * spacing.V;
var panelSizeU = lines.Count > 0 ? lines.Max(line => line.U + (line.Count - 1) * spacing.U) : 0.0;
// Reizing along main axis using grow and shrink factors can affect cross axis, so remeasure affected items and lines.
foreach (var line in lines)
{
var (itemsCount, totalSpacingU, totalU, freeU) = GetLineMeasureU(line, max.U, spacing.U);
var (lineMult, autoMargins, remainingFreeU) = GetLineMultInfo(line, freeU);
if (lineMult != 0.0 && remainingFreeU != 0.0)
{
foreach (var element in state.GetLineItems(line))
{
var baseLength = Flex.GetBaseLength(element);
var mult = GetItemMult(element, freeU);
if (mult != 0.0)
{
var length = Math.Max(0.0, baseLength + remainingFreeU * mult / lineMult);
element.Measure(Uv.ToSize(max.WithU(length), isColumn));
}
}
line.V = state.GetLineItems(line).Max(i => Uv.FromSize(i.DesiredSize, isColumn).V);
}
}
_state = state;
var totalLineV = lines.Sum(l => l.V);
var panelSize = lines.Count == 0 ? default : new Uv(panelSizeU, totalLineV + totalSpacingV);
return Uv.ToSize(panelSize, isColumn);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var state = _state ?? throw new InvalidOperationException();
var isColumn = Direction is FlexDirection.Column or FlexDirection.ColumnReverse;
var isReverse = Direction is FlexDirection.RowReverse or FlexDirection.ColumnReverse;
var panelSize = Uv.FromSize(finalSize, isColumn);
var spacing = Uv.FromSize(ColumnSpacing, RowSpacing, isColumn);
var linesCount = state.Lines.Count;
var totalLineV = state.Lines.Sum(s => s.V);
var totalSpacingV = (linesCount - 1) * spacing.V;
var totalV = totalLineV + totalSpacingV;
var freeV = panelSize.V - totalV;
var alignContent = freeV >= 0.0 ? AlignContent : AlignContent switch
{
AlignContent.FlexStart or AlignContent.Stretch or AlignContent.SpaceBetween => AlignContent.FlexStart,
AlignContent.Center or AlignContent.SpaceAround or AlignContent.SpaceEvenly => AlignContent.Center,
AlignContent.FlexEnd => AlignContent.FlexEnd,
_ => throw new InvalidOperationException()
};
var (spacingV, v) = alignContent switch
{
AlignContent.FlexStart => (spacing.V, 0.0),
AlignContent.FlexEnd => (spacing.V, freeV),
AlignContent.Center => (spacing.V, freeV / 2),
AlignContent.Stretch => (spacing.V, 0.0),
AlignContent.SpaceBetween => (spacing.V + freeV / (linesCount - 1), 0.0),
AlignContent.SpaceAround => (spacing.V + freeV / linesCount, freeV / linesCount / 2),
AlignContent.SpaceEvenly => (spacing.V + freeV / (linesCount + 1), freeV / (linesCount + 1)),
_ => throw new InvalidOperationException()
};
var scaleV = alignContent == AlignContent.Stretch ? (panelSize.V - totalSpacingV) / totalLineV : 1.0;
foreach (var line in state.Lines)
{
var lineV = scaleV * line.V;
var (itemsCount, totalSpacingU, totalU, freeU) = GetLineMeasureU(line, panelSize.U, spacing.U);
var (lineMult, lineAutoMargins, remainingFreeU) = GetLineMultInfo(line, freeU);
var currentFreeU = remainingFreeU;
if (lineMult != 0.0 && remainingFreeU != 0.0)
{
foreach (var element in state.GetLineItems(line))
{
var baseLength = Flex.GetBaseLength(element);
var mult = GetItemMult(element, freeU);
if (mult != 0.0)
{
var length = Math.Max(0.0, baseLength + remainingFreeU * mult / lineMult);
Flex.SetCurrentLength(element, length);
currentFreeU -= length - baseLength;
}
}
}
remainingFreeU = currentFreeU;
if (lineAutoMargins != 0 && remainingFreeU != 0.0)
{
foreach (var element in state.GetLineItems(line))
{
var baseLength = Flex.GetCurrentLength(element);
var autoMargins = GetItemAutoMargins(element, isColumn);
if (autoMargins != 0)
{
var length = Math.Max(0.0, baseLength + remainingFreeU * autoMargins / lineAutoMargins);
Flex.SetCurrentLength(element, length);
currentFreeU -= length - baseLength;
}
}
}
remainingFreeU = currentFreeU;
var (spacingU, u) = line.Grow > 0 ? (spacing.U, 0.0) : JustifyContent switch
{
JustifyContent.FlexStart => (spacing.U, 0.0),
JustifyContent.FlexEnd => (spacing.U, remainingFreeU),
JustifyContent.Center => (spacing.U, remainingFreeU / 2),
JustifyContent.SpaceBetween => (spacing.U + remainingFreeU / (itemsCount - 1), 0.0),
JustifyContent.SpaceAround => (spacing.U + remainingFreeU / itemsCount, remainingFreeU / itemsCount / 2),
JustifyContent.SpaceEvenly => (spacing.U + remainingFreeU / (itemsCount + 1), remainingFreeU / (itemsCount + 1)),
_ => throw new InvalidOperationException()
};
foreach (var element in state.GetLineItems(line))
{
var size = Uv.FromSize(element.DesiredSize, isColumn).WithU(Flex.GetCurrentLength(element));
var align = Flex.GetAlignSelf(element) ?? AlignItems;
var positionV = align switch
{
AlignItems.FlexStart => v,
AlignItems.FlexEnd => v + lineV - size.V,
AlignItems.Center => v + (lineV - size.V) / 2,
AlignItems.Stretch => v,
_ => throw new InvalidOperationException()
};
size = size.WithV(align == AlignItems.Stretch ? lineV : size.V);
var position = new Uv(isReverse ? panelSize.U - size.U - u : u, positionV);
element.Arrange(new Rect(Uv.ToPoint(position, isColumn), Uv.ToSize(size, isColumn)));
u += size.U + spacingU;
}
v += lineV + spacingV;
}
return finalSize;
}
private static (int ItemsCount, double TotalSpacingU, double TotalU, double FreeU) GetLineMeasureU(
FlexLine line, double panelSizeU, double spacingU)
{
var itemsCount = line.Count;
var totalSpacingU = (itemsCount - 1) * spacingU;
var totalU = line.U + totalSpacingU;
var freeU = panelSizeU - totalU;
return (itemsCount, totalSpacingU, totalU, freeU);
}
private static (double LineMult, double LineAutoMargins, double RemainingFreeU) GetLineMultInfo(FlexLine line, double freeU)
{
var lineMult = freeU switch
{
< 0 => line.Shrink,
> 0 => line.Grow,
_ => 0.0,
};
// https://www.w3.org/TR/css-flexbox-1/#remaining-free-space
// Sum of flex factors less than 1 reduces remaining free space to be distributed.
return lineMult is > 0 and < 1
? (lineMult, line.AutoMargins, freeU * lineMult)
: (lineMult, line.AutoMargins, freeU);
}
private static double GetItemMult(Layoutable element, double freeU)
{
var mult = freeU switch
{
< 0 => Flex.GetShrink(element),
> 0 => Flex.GetGrow(element),
_ => 0.0,
};
return mult;
}
private static int GetItemAutoMargins(Layoutable element, bool isColumn)
{
return isColumn
? element.VerticalAlignment switch
{
VerticalAlignment.Stretch => 0,
VerticalAlignment.Top or VerticalAlignment.Bottom => 1,
VerticalAlignment.Center => 2,
_ => throw new InvalidOperationException()
}
: element.HorizontalAlignment switch
{
HorizontalAlignment.Stretch => 0,
HorizontalAlignment.Left or HorizontalAlignment.Right => 1,
HorizontalAlignment.Center => 2,
_ => throw new InvalidOperationException()
};
}
private readonly struct FlexLayoutState
{
private readonly IReadOnlyList<Layoutable> _children;
public IReadOnlyList<FlexLine> Lines { get; }
public FlexLayoutState(IReadOnlyList<Layoutable> children, List<FlexLine> lines, FlexWrap wrap)
{
if (wrap == FlexWrap.WrapReverse)
{
lines.Reverse();
}
_children = children;
Lines = lines;
}
public IEnumerable<Layoutable> GetLineItems(FlexLine line)
{
for (var i = line.First; i <= line.Last; i++)
yield return _children[i];
}
}
private class FlexLine
{
public FlexLine(int first, int last, double u, double v, double shrink, double grow, int autoMargins)
{
First = first;
Last = last;
U = u;
V = v;
Shrink = shrink;
Grow = grow;
AutoMargins = autoMargins;
}
/// <summary>First item index.</summary>
public int First { get; }
/// <summary>Last item index.</summary>
public int Last { get; }
/// <summary>Sum of main sizes of items.</summary>
public double U { get; }
/// <summary>Max of cross sizes of items.</summary>
public double V { get; set; }
/// <summary>Sum of shrink factors of flexible items.</summary>
public double Shrink { get; }
/// <summary>Sum of grow factors of flexible items.</summary>
public double Grow { get; }
/// <summary>Number of "auto margins" along main axis.</summary>
public int AutoMargins { get; }
/// <summary>Number of items.</summary>
public int Count => Last - First + 1;
}
}
@@ -0,0 +1,8 @@
namespace BD.WTTS.UI.Views.Controls;
public enum FlexWrap
{
NoWrap,
Wrap,
WrapReverse
}
@@ -0,0 +1,11 @@
namespace BD.WTTS.UI.Views.Controls;
public enum JustifyContent
{
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly
}
@@ -0,0 +1,29 @@
namespace BD.WTTS.UI.Views.Controls;
internal readonly struct Uv(double u, double v)
{
public double U { get; } = u;
public double V { get; } = v;
public static Uv FromSize(double width, double height, bool swap) =>
new Uv(swap ? height : width, swap ? width : height);
public static Uv FromSize(Size size, bool swap) =>
FromSize(size.Width, size.Height, swap);
public static Point ToPoint(Uv uv, bool swap) =>
new Point(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
public static Size ToSize(Uv uv, bool swap) =>
new Size(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
public Uv WithU(double u) =>
new Uv(u, V);
public Uv WithV(double v) =>
new Uv(U, v);
public override string ToString() =>
$"U: {U}, V: {V}";
}
@@ -1,15 +1,17 @@
using Avalonia.Controls;
namespace BD.WTTS.UI.Views.Controls;
public class OptionsDisplayItem : TemplatedControl
{
public static readonly StyledProperty<string> HeaderProperty =
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Header));
public static readonly StyledProperty<object?> HeaderProperty =
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Header));
public static readonly StyledProperty<string> DescriptionProperty =
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Description));
public static readonly StyledProperty<object?> DescriptionProperty =
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Description));
public static readonly StyledProperty<FAIconElement> IconProperty =
AvaloniaProperty.Register<OptionsDisplayItem, FAIconElement>(nameof(Icon));
public static readonly StyledProperty<object?> IconProperty =
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Icon));
public static readonly StyledProperty<bool> NavigatesProperty =
AvaloniaProperty.Register<OptionsDisplayItem, bool>(nameof(Navigates));
@@ -29,19 +31,19 @@ public class OptionsDisplayItem : TemplatedControl
public static readonly StyledProperty<ICommand> NavigationCommandProperty =
AvaloniaProperty.Register<OptionsDisplayItem, ICommand>(nameof(NavigationCommand));
public string Header
public object? Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public string Description
public object? Description
{
get => GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
public FAIconElement Icon
public object? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
@@ -112,7 +114,7 @@ public class OptionsDisplayItem : TemplatedControl
}
else if (change.Property == DescriptionProperty)
{
PseudoClasses.Set(":desc", !string.IsNullOrEmpty(change.GetNewValue<string>()));
PseudoClasses.Set(":desc", change.GetNewValue<object?>() != null);
}
else if (change.Property == IsExpandedProperty)
PseudoClasses.Set(":expanded", change.GetNewValue<bool>());
@@ -31,7 +31,7 @@
</UserControl.Styles>
<Border
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
BorderThickness="0"
ClipToBounds="True"
CornerRadius="8">
<Panel>
@@ -15,7 +15,7 @@ namespace BD.WTTS.UI.Views.Controls
/// Defines the <see cref="AutoScrollInterval"/> property.
/// </summary>
public static readonly StyledProperty<int> AutoScrollIntervalProperty =
AvaloniaProperty.Register<CarouselBanner, int>(nameof(AutoScrollInterval), 8000);
AvaloniaProperty.Register<CarouselBanner, int>(nameof(AutoScrollInterval), 6000);
/// <summary>
/// Defines the Avalonia.Controls.ItemsControl.Items property.
@@ -29,6 +29,12 @@ namespace BD.WTTS.UI.Views.Controls
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
Carousel.ItemTemplateProperty.AddOwner<CarouselBanner>();
///// <summary>
///// Defines the <see cref="GroupView"/> property.
///// </summary>
//public static readonly StyledProperty<bool> GroupViewProperty =
// AvaloniaProperty.Register<CarouselBanner, bool>(nameof(GroupView), true);
/// <summary>
/// Gets or sets the items to display.
/// </summary>
@@ -65,23 +71,32 @@ namespace BD.WTTS.UI.Views.Controls
set => SetValue(AutoScrollIntervalProperty, value);
}
///// <summary>
///// GroupView
///// </summary>
//public bool GroupView
//{
// get => GetValue(GroupViewProperty);
// set => SetValue(GroupViewProperty, value);
//}
private Timer? _timer;
public CarouselBanner()
{
InitializeComponent();
Left.Click += (s, e) => SwiperPrevious();
Right.Click += (s, e) => SwiperNext();
Left.Command = ReactiveCommand.Create(SwiperPrevious);
Right.Command = ReactiveCommand.Create(SwiperNext);
//CarouselControl[!Carousel.Items] = this[!Items];
CarouselControl[!Carousel.ItemsSourceProperty] = this[!ItemsSourceProperty];
CarouselControl[!Carousel.ItemTemplateProperty] = this[!ItemTemplateProperty];
this.GetObservable(ItemsSourceProperty)
CarouselControl.GetObservable(Carousel.ItemCountProperty)
.Subscribe(_ => SwipersLoad());
CarouselControl.GetObservable(Carousel.SelectedIndexProperty)
.Subscribe(x => SwipersLoad());
.Subscribe(_ => SwipersLoad());
this.GetObservable(AutoScrollProperty)
.Subscribe(x =>
@@ -107,6 +122,40 @@ namespace BD.WTTS.UI.Views.Controls
});
}
//protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
//{
// if (change.Property == ItemsSourceProperty)
// {
// SwipersLoad();
// }
// else if (change.Property == AutoScrollProperty)
// {
// if (change.NewValue is bool x)
// {
// if (x && _timer == null)
// {
// _timer = new Timer(_ =>
// {
// if (!this.IsPointerOver)
// {
// Dispatcher.UIThread.Post(SwiperNext, DispatcherPriority.Background);
// }
// }, nameof(AutoScroll), AutoScrollInterval, AutoScrollInterval);
// }
// else
// {
// if (_timer != null)
// {
// _timer.Dispose();
// _timer = null;
// }
// }
// }
// }
// base.OnPropertyChanged(change);
//}
private void CarouselBannerIndexButton_Click(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is int index)
@@ -0,0 +1,234 @@
using Avalonia.Controls;
using Avalonia.Layout;
using FluentAvalonia.Core;
using System.Reactive;
namespace BD.WTTS.UI.Views.Controls;
public class CarouselItems : ItemsControl
{
/// <summary>
/// Defines the <see cref="AutoScroll"/> property.
/// </summary>
public static readonly StyledProperty<bool> AutoScrollProperty =
AvaloniaProperty.Register<CarouselItems, bool>(nameof(AutoScroll), true);
/// <summary>
/// Defines the <see cref="AutoScrollInterval"/> property.
/// </summary>
public static readonly StyledProperty<int> AutoScrollIntervalProperty =
AvaloniaProperty.Register<CarouselItems, int>(nameof(AutoScrollInterval), 6000);
/// <summary>
/// Defines the <see cref="ItemsPerPage"/> property.
/// </summary>
public static readonly StyledProperty<int> ItemsPerPageProperty =
AvaloniaProperty.Register<CarouselItems, int>(nameof(ItemsPerPage), 4);
/// <summary>
/// AutoScroll
/// </summary>
public bool AutoScroll
{
get => GetValue(AutoScrollProperty);
set => SetValue(AutoScrollProperty, value);
}
/// <summary>
/// 自动播放间隔时间(单位毫秒)
/// </summary>
public int AutoScrollInterval
{
get => GetValue(AutoScrollIntervalProperty);
set => SetValue(AutoScrollIntervalProperty, value);
}
/// <summary>
/// ItemsPerPage
/// </summary>
public int ItemsPerPage
{
get => GetValue(ItemsPerPageProperty);
set => SetValue(ItemsPerPageProperty, value);
}
Carousel? _carouselControl;
Button? _leftButton;
Button? _rightButton;
ItemsRepeater? _swiper;
Timer? _timer;
public ICommand? CarouselBannerIndexCommand { get; }
public CarouselItems()
{
CarouselBannerIndexCommand = ReactiveCommand.Create<int>(CarouselBannerIndex);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_carouselControl = e.NameScope.Find<Carousel>("CarouselControl");
_leftButton = e.NameScope.Find<Button>("Left");
_rightButton = e.NameScope.Find<Button>("Right");
_swiper = e.NameScope.Find<ItemsRepeater>("Swiper");
if (_leftButton != null)
{
_leftButton.Command = ReactiveCommand.Create(SwiperPrevious);
}
if (_rightButton != null)
{
_rightButton.Command = ReactiveCommand.Create(SwiperNext);
}
if (_carouselControl != null)
{
_carouselControl.GetObservable(Carousel.ItemCountProperty)
.Subscribe(_ => SwipersLoad());
_carouselControl.GetObservable(Carousel.SelectedIndexProperty)
.Subscribe(_ => SwipersLoad());
RefreshItemsSource();
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ItemCountProperty)
{
RefreshItemsSource();
}
else if (change.Property == ItemsPerPageProperty)
{
RefreshItemsSource();
}
else if (change.Property == AutoScrollProperty)
{
var x = change.GetNewValue<bool>();
if (x && _timer == null)
{
_timer = new Timer(_ =>
{
if (!this.IsPointerOver)
{
Dispatcher.UIThread.Post(SwiperNext, DispatcherPriority.Background);
}
}, nameof(AutoScroll), AutoScrollInterval, AutoScrollInterval);
}
else
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
}
}
protected override void OnPointerEntered(PointerEventArgs e)
{
base.OnPointerEntered(e);
if (_swiper == null || _leftButton == null || _rightButton == null)
return;
_leftButton.IsVisible = _rightButton.IsVisible = _swiper.IsVisible;
}
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
if (_swiper == null || _leftButton == null || _rightButton == null)
return;
_leftButton.IsVisible = _rightButton.IsVisible = false;
}
void RefreshItemsSource()
{
if (_carouselControl != null)
{
var items = ItemsSource?.Batch(ItemsPerPage);
_carouselControl.ItemsSource = items;
}
}
void CarouselBannerIndex(int index)
{
if (_carouselControl != null)
_carouselControl.SelectedIndex = index;
}
void SwipersLoad()
{
if (_carouselControl == null || _swiper == null || _leftButton == null || _rightButton == null)
return;
if (_carouselControl.ItemCount <= 0)
{
_swiper.ItemsSource = null;
return;
}
if (_carouselControl.ItemCount == 1)
{
_swiper.IsVisible = false;
return;
}
else
{
_swiper.IsVisible = true;
var arr = new Dictionary<int, string>();
for (var i = 0; i < _carouselControl.ItemCount; i++)
{
arr.Add(i, "#ADADAD");
}
var index = _carouselControl.SelectedIndex < 0 ? 0 : _carouselControl.SelectedIndex;
arr[index] = "#FFFFFF";
_swiper.ItemsSource = arr;
}
}
/// <summary>
/// 滑动到下一个
/// </summary>
public void SwiperNext()
{
if (_carouselControl == null || _carouselControl.ItemCount < 1)
return;
if (_carouselControl.SelectedIndex < _carouselControl.ItemCount - 1)
{
_carouselControl.Next();
}
else
{
_carouselControl.SelectedIndex = 0;
}
}
/// <summary>
/// 滑动到上一个
/// </summary>
public void SwiperPrevious()
{
if (_carouselControl == null || _carouselControl.ItemCount < 1)
return;
if (_carouselControl.SelectedIndex > 0)
{
_carouselControl.Previous();
}
else
{
_carouselControl.SelectedIndex = _carouselControl.ItemCount - 1;
}
}
}
@@ -2,25 +2,50 @@ using static System.Math;
namespace BD.WTTS.UI.Views.Controls;
/// <summary>
/// 固定的 WrapPanel
/// </summary>
public class FixedWrapPanel : Panel, INavigableContainer
{
/// <summary>
/// Defines the <see cref="ItemsPerLine"/> property.
/// </summary>
public static readonly StyledProperty<int> ItemsPerLineProperty =
AvaloniaProperty.Register<FixedWrapPanel, int>(nameof(ItemsPerLine), 3);
/// <summary>
/// Defines the <see cref="Spacing"/> property.
/// </summary>
public static readonly StyledProperty<double> SpacingProperty =
AvaloniaProperty.Register<FixedWrapPanel, double>(nameof(Spacing), 0);
static FixedWrapPanel()
{
AffectsMeasure<FixedWrapPanel>(ItemsPerLineProperty);
}
public double ItemsPerLine
/// <summary>
/// ItemsPerLine
/// </summary>
public int ItemsPerLine
{
get => GetValue(ItemsPerLineProperty);
set => SetValue(ItemsPerLineProperty, value);
}
/// <summary>
/// Spacing
/// </summary>
public double Spacing
{
get => GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
/// <inheritdoc/>
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
{
int index = from is not null ? Children.IndexOf((Control)from) : -1;
int index = from is Control control ? Children.IndexOf(control) : -1;
switch (direction)
{
case NavigationDirection.First:
@@ -57,13 +82,14 @@ public class FixedWrapPanel : Panel, INavigableContainer
return this;
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size constraint)
{
double itemWidth = constraint.Width / ItemsPerLine;
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, constraint.Height);
Size childConstraint = new(itemWidth - Spacing, constraint.Height);
for (int i = 0, count = Children.Count; i < count; i++)
{
@@ -76,7 +102,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
child.Measure(childConstraint);
Size childSize = new(itemWidth, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width, lineConstraint.Width))
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width + Spacing, lineConstraint.Width))
{
// Need to switch to another line
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
@@ -86,21 +112,25 @@ public class FixedWrapPanel : Panel, INavigableContainer
else
{
// Continue to accumulate a line
currentLineSize.Width += childSize.Width;
currentLineSize.Width += childSize.Width + Spacing;
currentLineSize.Height = Max(childSize.Height, currentLineSize.Height);
}
}
// The last line size, if any should be added
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
panelSize.Height += currentLineSize.Height;
panelSize.Height = currentLineSize.Height;
if (double.IsInfinity(panelSize.Width))
panelSize.Width = 0;
return panelSize.ToSize();
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
double itemWidth = finalSize.Width / ItemsPerLine;
double itemWidth = (finalSize.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
int firstInLine = 0;
double accumulatedHeight = 0;
var currentLineSize = default(MutableSize);
@@ -139,7 +169,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
return finalSize;
}
private void ArrangeLine(double y, double height, int start, int end, double width)
void ArrangeLine(double y, double height, int start, int end, double width)
{
double x = 0;
for (int i = start; i < end; i++)
@@ -150,12 +180,20 @@ public class FixedWrapPanel : Panel, INavigableContainer
continue;
}
child.Arrange(new Rect(x, y, width, height));
x += width;
if (i < end - 1)
{
child.Arrange(new Rect(x, y, width - Spacing, height));
x += width + Spacing;
}
else
{
child.Arrange(new Rect(x, y, width, height));
x += width;
}
}
}
private struct MutableSize
record struct MutableSize
{
internal MutableSize(double width, double height)
{
@@ -172,7 +210,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
internal double Width;
internal double Height;
internal Size ToSize()
internal readonly Size ToSize()
{
return new Size(Width, Height);
}
@@ -15,9 +15,7 @@
<StackPanel Spacing="{StaticResource SettingsSpacing}">
<TextBlock
Margin="0,0,0,5"
Text="{ReflectionBinding Path=Res.About,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Text="{Binding Path=Res.About, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<spp:OptionsDisplayItem
@@ -140,9 +138,7 @@
Margin="12,4,0,0"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{ReflectionBinding Path=Res.Link,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
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" />
@@ -165,9 +161,7 @@
Margin="12,4,0,0"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{ReflectionBinding Path=Res.OpenSourceLibrary,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />
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" />
@@ -8,7 +8,8 @@ public partial class AboutPage : ReactiveUserControl<AboutPageViewModel>
public AboutPage()
{
InitializeComponent();
this.SetViewModel<AboutPageViewModel>(false);
DataContext = new AboutPageViewModel();
this.SetViewModel<AboutPageViewModel>();
}
/// <summary>
@@ -21,6 +21,7 @@
</spp:PageBase.PreviewImage>
<spp:PageBase.TabContent>
<TabStrip
Name="TabSelect"
Margin="4"
HorizontalAlignment="Right"
VerticalAlignment="Top"
@@ -110,19 +111,45 @@
</DropDownButton>
</StackPanel>
</spp:PageBase.ActionContent>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid RowDefinitions="*,Auto">
<TextBox
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Classes="readonlybox"
Text="{Binding DebugString}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBox
Name="CommandTextBox"
Grid.Row="1"
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Grid>
</Border>
<Carousel SelectedIndex="{Binding #TabSelect.SelectedIndex}">
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid RowDefinitions="*,Auto">
<TextBox
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Classes="readonlybox"
Text="{Binding DebugString}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBox
Name="CommandTextBox"
Grid.Row="1"
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Grid>
</Border>
<Panel>
<TextBlock Text="Test Control" />
<ItemsControl>
<ItemsControl.Items>
<Border Height="30" Background="Red" />
<Border Background="Blue" />
<Border Background="Yellow" />
<Border Height="30" Background="Yellow" />
<Border Background="Red" />
<Border Background="Blue" />
</ItemsControl.Items>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<spp:FixedWrapPanel ItemsPerLine="3" Spacing="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Panel>
<Panel>
<TextBlock Text="Test Control" />
</Panel>
</Carousel>
</spp:PageBase>
@@ -7,7 +7,7 @@ public partial class DebugPage : PageBase<DebugPageViewModel>
public DebugPage()
{
InitializeComponent();
this.SetViewModel<DebugPageViewModel>(false);
this.SetViewModel<DebugPageViewModel>();
CommandTextBox.KeyUp += (s, e) =>
{
@@ -2,6 +2,7 @@
x:Class="BD.WTTS.UI.Views.Pages.HomePage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="using:System.Collections.Generic"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://steampp.net/services"
@@ -125,6 +126,7 @@
<Grid Margin="0,-10,0,0" RowDefinitions="Auto,*">
<spp:CarouselBanner
MinHeight="350"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ItemsSource="{Binding NavigationBanners}">
@@ -148,7 +150,7 @@
</Border>
</spp:AppItem.Status>
<spp:AppItem.Image>
<spp:Image2 Source="{Binding ImageUrl}" />
<spp:Image2 EnableCancelToken="False" Source="{Binding ImageUrl}" />
</spp:AppItem.Image>
</spp:AppItem>
</DataTemplate>
@@ -227,31 +229,34 @@
DataContext="{Binding Articles[2]}" />
</Grid>
<!--<ItemsControl ItemsSource="{Binding Articles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ColumnDefinitions="*,*,*" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="spp:ArticleItemDTO">
<spp:AppItem
Title="{Binding Title}"
Classes="Vertical Scale"
Description="{Binding Introduction}"
Tags="{Binding AuthorName}">
<spp:AppItem.Image>
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
</spp:AppItem.Image>
<spp:AppItem.Status>
<Border Classes="Status">
<TextBlock Text="{Binding CreationTime, StringFormat={}{yyyy-MM-dd}}" />
</Border>
</spp:AppItem.Status>
</spp:AppItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>-->
<!--<spp:CarouselItems ItemsPerPage="3" ItemsSource="{Binding Articles}">
<spp:CarouselItems.ItemTemplate>
<DataTemplate x:DataType="spp:ArticleItemDTO">
<spp:AppItem
Title="{Binding Title}"
Classes="Vertical Scale"
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
Cursor="Hand"
Description="{Binding Introduction}"
Tags="{Binding AuthorName}">
<spp:AppItem.ClickCommandParameter>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.OfficialWebsite_Article_Detail_}" />
<CompiledBinding Path="Id" />
</MultiBinding>
</spp:AppItem.ClickCommandParameter>
<spp:AppItem.Image>
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
</spp:AppItem.Image>
<spp:AppItem.Status>
<Border Classes="Status">
<TextBlock Text="{Binding CreationTime, StringFormat={}{0:yyyy-MM-dd}}" />
</Border>
</spp:AppItem.Status>
</spp:AppItem>
</DataTemplate>
</spp:CarouselItems.ItemTemplate>
</spp:CarouselItems>-->
<DockPanel>
<TextBlock
@@ -268,7 +273,172 @@
Theme="{StaticResource HyperlinkButton}" />
</DockPanel>
<ScrollViewer
<spp:CarouselItems
MinHeight="250"
ItemsPerPage="4"
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}">
<TextBlock
VerticalAlignment="Center"
Foreground="#beee11"
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
</Border>
</spp:AppItem.ActionButton>
</spp:AppItem>
</Border>
</DataTemplate>
</spp:CarouselItems.ItemTemplate>
</spp:CarouselItems>
<!--<spp:CarouselBanner
MinHeight="250"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ItemsSource="{Binding Shops}">
<spp:CarouselBanner.Resources>
<DataTemplate x:Key="ShopItem" 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 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}">
<TextBlock
VerticalAlignment="Center"
Foreground="#beee11"
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
</Border>
</spp:AppItem.ActionButton>
</spp:AppItem>
</Border>
</DataTemplate>
</spp:CarouselBanner.Resources>
<spp:CarouselBanner.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:List" />
</DataTemplate.DataType>
<Grid ColumnDefinitions="*,10,*,10,*,10,*">
<ContentPresenter
Grid.Column="0"
Content="{Binding [0]}"
ContentTemplate="{StaticResource ShopItem}" />
<ContentPresenter
Grid.Column="2"
Content="{Binding [1]}"
ContentTemplate="{StaticResource ShopItem}" />
<ContentPresenter
Grid.Column="4"
Content="{Binding [2]}"
ContentTemplate="{StaticResource ShopItem}" />
<ContentPresenter
Grid.Column="6"
Content="{Binding [3]}"
ContentTemplate="{StaticResource ShopItem}" />
</Grid>
</DataTemplate>
-->
<!--<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:IEnumerable" />
</DataTemplate.DataType>
<ItemsControl ItemTemplate="{StaticResource ShopItem}" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ColumnDefinitions="*,*,*,*" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>-->
<!--
</spp:CarouselBanner.ItemTemplate>
</spp:CarouselBanner>-->
<!--<ScrollViewer
Name="ShopsScrollViewer"
Margin="0,0,0,20"
Padding="0,0,0,15"
@@ -336,7 +506,7 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ScrollViewer>-->
<TextBlock
Margin="-3,0,0,0"
@@ -7,7 +7,7 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
public HomePage()
{
InitializeComponent();
this.SetViewModel<HomePageViewModel>(false);
this.SetViewModel<HomePageViewModel>();
//ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
}
@@ -263,19 +263,19 @@
</MenuItem.Header>
</MenuItem>
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
<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
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
<!--<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
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
<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}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
@@ -7,7 +7,7 @@ public partial class SettingsPage : PageBase<SettingsPageViewModel>
public SettingsPage()
{
InitializeComponent();
this.SetViewModel<SettingsPageViewModel>(false);
this.SetViewModel<SettingsPageViewModel>();
//SettingsScrollTab.SelectionChanged += SettingsScrollTab_SelectionChanged;
@@ -102,6 +102,7 @@
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).DeletePlugin_Click}"
CommandParameter="{Binding Data}"
Content="{Binding Path=Res.Plugin_Delete, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
IsClickEnabled="True" />
</ui:SettingsExpander>
</DataTemplate>
@@ -66,7 +66,7 @@
Margin="0,5"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Command="{ReflectionBinding $parent[UserControl].DataContext.ChooseChannel}"
Command="{Binding $parent[UserControl].((spp:LoginOrRegisterWindowViewModel)DataContext).ChooseChannel}"
CommandParameter="{Binding Id, Mode=OneWay}">
<DockPanel>
<Panel Margin="5,0,0,0" DockPanel.Dock="Left">
@@ -34,7 +34,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
this.SetViewModel<LoginOrRegisterWindowViewModel>(false);
this.SetViewModel<LoginOrRegisterWindowViewModel>();
if (this.ViewModel != null)
{
@@ -42,9 +42,21 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
if (UserService.Current.IsAuthenticated)
{
Toast.Show(ToastIcon.Info, "当前已是登录状态");
this.ViewModel.Close();
this.ViewModel.Close(false);
}
}
Application.Current!.UrlsOpened += Current_UrlsOpened;
}
private async void Current_UrlsOpened(object? sender, UrlOpenedEventArgs e)
{
var loginUrl = e.Urls.Where(x => x.StartsWith(Constants.UrlSchemes.Login)).FirstOrDefault();
if (loginUrl != null)
{
//var token = loginUrl.TrimStart(Constants.UrlSchemes.Login);
var token = loginUrl[Constants.UrlSchemes.Login.Length..];
await ThirdPartyLoginHelper.LoginForStr(token);
}
}
protected override void OnDataContextChanged(EventArgs e)
@@ -59,6 +71,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
Application.Current!.UrlsOpened -= Current_UrlsOpened;
base.OnDetachedFromVisualTree(e);
if (DataContext is LoginOrRegisterWindowViewModel vm)
{
@@ -52,6 +52,7 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
}
Task2.InBackground(async () =>
{
IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
await AdvertiseService.Current.RefrshAdvertiseAsync();
NoticeService.Current.GetNewsAsync();
});
@@ -169,13 +170,14 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
#pragma warning disable SA1114 // Parameter list should follow declaration
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
{
new MenuTabItemViewModel("Welcome")
{
PageType = typeof(HomePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
},
}, ImmutableArray.Create<TabItemViewModel>(
new MenuTabItemViewModel("Welcome")
{
PageType = typeof(HomePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
},
},
ImmutableArray.Create<TabItemViewModel>(
#if DEBUG
new MenuTabItemViewModel("Debug")
{
@@ -93,6 +93,9 @@ public interface IPCSubProcessService : IDisposable
var nativeLibraryPath = Environment.GetEnvironmentVariable(EnvKey_NativeLibraryPath);
if (!string.IsNullOrWhiteSpace(nativeLibraryPath))
{
var nativeLibraryPaths = nativeLibraryPath.Split(';',
StringSplitOptions.RemoveEmptyEntries);
// 监听当前应用程序域的程序集加载
AppDomain.CurrentDomain.AssemblyLoad += (_, args)
=> CurrentDomain_AssemblyLoad(args.LoadedAssembly);
@@ -119,23 +122,34 @@ public interface IPCSubProcessService : IDisposable
return libraryName;
}
var libraryFileName = GetLibraryFileName(libraryName);
var libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
if (File.Exists(libraryPath) &&
NativeLibrary.TryLoad(libraryPath, out var handle))
string libraryPath = libraryName;
try
{
return handle;
}
else if (!OperatingSystem.IsWindows() && !libraryFileName.StartsWith("lib", StringComparison.OrdinalIgnoreCase))
{
libraryFileName = $"lib{libraryFileName}";
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
if (File.Exists(libraryPath) &&
NativeLibrary.TryLoad(libraryPath, out handle))
foreach (var nativeLibraryPath in nativeLibraryPaths)
{
return handle;
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
if (File.Exists(libraryPath) &&
NativeLibrary.TryLoad(libraryPath, out var handle))
{
return handle;
}
else if (!OperatingSystem.IsWindows() && !libraryFileName.StartsWith("lib", StringComparison.OrdinalIgnoreCase))
{
libraryFileName = $"lib{libraryFileName}";
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
if (File.Exists(libraryPath) &&
NativeLibrary.TryLoad(libraryPath, out handle))
{
return handle;
}
}
}
return NativeLibrary.Load(libraryName, assembly, searchPath);
}
catch (Exception ex)
{
throw new FileNotFoundException(null, libraryPath, ex);
}
return NativeLibrary.Load(libraryName, assembly, searchPath);
}
NativeLibrary.SetDllImportResolver(loadedAssembly, Delegate);
}
@@ -32,9 +32,9 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
var tlsSniValue = domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom();
request.SetRequestContext(new RequestContext(isHttps, tlsSniValue));
//// 设置请求头 host,修改协议为 http
//request.Headers.Host = uri.Host;
//request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
// 设置请求头 host,修改协议为 http
request.Headers.Host = uri.Host;
request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
if (domainConfig.Timeout != null)
{
@@ -191,7 +191,7 @@ sealed class HttpLocalRequestMiddleware
}
if (hasReqContent)
{
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType.ThrowIsNull());
req.Content.Headers.ContentLength = context.Request.ContentLength;
}
var rsp = await HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted).ConfigureAwait(false);
@@ -89,7 +89,7 @@ sealed partial class HttpReverseProxyMiddleware
var forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = context.Request.Protocol.StartsWith("HTTP/2.0") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
Version = context.Request.Protocol.StartsWith("HTTP/2") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
};
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
@@ -10,14 +10,16 @@
<ItemGroup>
<None Remove="UI\Assets\accelerator.ico" />
<None Remove="UI\Assets\defaultBackground.png" />
<None Remove="UI\Assets\script.ico" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="UI\Assets\accelerator.ico" />
<AvaloniaResource Include="UI\Assets\defaultBackground.png" />
<AvaloniaResource Include="UI\Assets\script.ico" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\AssemblyInfo.Xaml.cs">
<LinkBase>Properties</LinkBase>
@@ -108,20 +110,57 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
<Compile Update="Properties\WinDivertResource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>WinDivertResource.resx</DependentUpon>
</Compile>
<EmbeddedResource Update="Properties\WinDivertResource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>WinDivertResource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<Compile Remove="Properties\WinDivertResource.Designer.cs" />
<EmbeddedResource Remove="Properties\WinDivertResource.resx" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectDirectory)\..\XunYouSDK\**\*.cs">
<LinkBase>XunYouSDK</LinkBase>
</Compile>
</ItemGroup>
<ItemGroup Condition=" Exists('$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\XunYouSDK\XunYouSDK.AppId.cs') ">
<Compile Remove="$(MSBuildProjectDirectory)\..\XunYouSDK\XunYouSDK.AppId.cs" />
<Compile Include="$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\XunYouSDK\XunYouSDK.AppId.cs">
<LinkBase>XunYouSDK</LinkBase>
</Compile>
<!--<None Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\xunyoucall.dll') " Include="$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\xunyoucall.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>-->
</ItemGroup>
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
<ItemGroup>
<UpToDateCheckInput Remove="UI\Views\Pages\AcceleratorPage2.axaml" />
</ItemGroup>
</Project>
@@ -8,7 +8,8 @@ namespace BD.WTTS.Entities;
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public sealed class Script : IEntity<int>
{
public const string TableName = "1B2D820C";
//TableName = ScriptV2
public const string TableName = "CAEC0F80";
public const string ColumnName_Id = "E386BC36";
public const string ColumnName_Enable = "6DB11594";
public const string ColumnName_CachePath = "997C6B59";
@@ -0,0 +1,132 @@
#if WINDOWS
using WinDivertBinRes = BD.WTTS.Properties.WinDivertResource;
namespace Mobius.Helpers;
/// <summary>
/// 初始化 WinDivert 助手类
/// </summary>
static class WinDivertInitHelper
{
const string libraryName = "WinDivert.dll";
static readonly string? libraryPath;
static readonly string? libraryDirPath;
//static nint libraryIntPtr;
///// <summary>
///// 配置本机库路径解析
///// </summary>
///// <param name="libraryName"></param>
///// <param name="assembly"></param>
///// <param name="searchPath"></param>
///// <returns></returns>
//public static nint DllImportResolver(string libraryName,
// Assembly assembly,
// DllImportSearchPath? searchPath)
//{
// switch (libraryName)
// {
// case WinDivertInitHelper.libraryName:
// if (libraryIntPtr != default)
// return libraryIntPtr;
// break;
// }
// return default;
//}
static bool isInitialize = false;
/// <summary>
/// 初始化 WinDivert
/// </summary>
public static async Task InitializeAsync()
{
if (libraryPath != null && libraryDirPath != null)
{
bool isWrite;
var libraryFileInfo = new FileInfo(libraryPath);
if (libraryFileInfo.Exists)
{
if (libraryFileInfo.Length > 20000000L) // 文件不可能大于 20 MB
{
libraryFileInfo.Delete();
isWrite = true;
}
else
{
var libraryBytes = await File.ReadAllBytesAsync(libraryPath);
var libraryHash = SHA256.HashData(libraryBytes);
var libraryHashRes = SHA256.HashData(WinDivertBinRes.WinDivert_dll);
if (libraryHash.SequenceEqual(libraryHashRes)) // 哈希值相同,不写入
{
isWrite = false;
}
else
{
libraryFileInfo.Delete();
isWrite = true;
}
}
}
else
{
isWrite = true;
}
if (isWrite) // 检查文件不存在时写入
{
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);
await Task.WhenAll(task1, task2);
break;
}
}
}
if (isInitialize)
return;
isInitialize = true;
//if (IsSupported && libraryPath != null)
//{
// try
// {
// libraryIntPtr = NativeLibrary.Load(libraryPath);
// }
// catch
// {
// }
// if (libraryIntPtr != default)
// {
// NativeLibrary.SetDllImportResolver(typeof(WinDivertSharp.WinDivert).Assembly, DllImportResolver);
// return;
// }
//}
IsSupported = false;
}
/// <summary>
/// 当前运行环境是否支持使用 WinDivert
/// </summary>
public static bool IsSupported { get; private set; }
static WinDivertInitHelper()
{
switch (RuntimeInformation.OSArchitecture)
{
//case Architecture.X86:
case Architecture.X64:
IsSupported = true;
libraryPath = GlobalDllImportResolver.GetLibraryPath("WinDivert.dll");
libraryDirPath = Path.GetDirectoryName(libraryPath);
break;
}
}
}
#endif
@@ -11,6 +11,7 @@ sealed class AcceleratorAutoMapperProfile : Profile
.ForMember(x => x.Icon, d => d.MapFrom(x => x.IconUrl))
.ForMember(x => x.Description, d => d.MapFrom(x => x.Describe))
.ForMember(x => x.Enable, d => d.MapFrom(x => !x.Disable))
.ForMember(x => x.IsBuild, d => d.MapFrom(x => x.IsCompile))
.ForMember(x => x.Id, d => d.MapFrom(x => x.LocalId));
CreateMap<Script, ScriptDTO>()
.ForMember(x => x.LocalId, d => d.MapFrom(x => x.Id))
@@ -18,6 +19,7 @@ sealed class AcceleratorAutoMapperProfile : Profile
.ForMember(x => x.Describe, d => d.MapFrom(x => x.Description))
.ForMember(x => x.IconUrl, d => d.MapFrom(x => x.Icon))
.ForMember(x => x.Disable, d => d.MapFrom(x => !x.Enable))
.ForMember(x => x.IsCompile, d => d.MapFrom(x => x.IsBuild))
.ForMember(x => x.Id, d => d.MapFrom(x => x.Pid));
}
}
@@ -0,0 +1,34 @@
namespace Mobius.Models;
/// <summary>
/// 迅游加速状态模型类
/// </summary>
[MP2Obj(MP2SerializeLayout.Explicit)]
public partial class XunYouAccelStateModel
{
/// <inheritdoc cref="XunYouState"/>
[MP2Key(0)]
public XunYouState State { get; set; }
/// <summary>
/// 当前加速的游戏 id。
/// </summary>
[MP2Key(1)]
public int GameId { get; set; }
/// <summary>
/// 当前加速的游戏区 id。
/// </summary>
[MP2Key(2)]
public int AreaId { get; set; }
/// <summary>
/// 当前加速游戏的服 id,没有则为 0。
/// </summary>
[MP2Key(3)]
public int ServerId { get; set; }
/// <inheritdoc cref="XunYouAccelStateEx"/>
[MP2Key(4)]
public XunYouAccelStateEx? AccelState { get; set; }
}
@@ -0,0 +1,216 @@
namespace BD.WTTS.Models;
[MP2Obj(MP2SerializeLayout.Explicit)]
public partial class XunYouGameViewModel : ReactiveSerializationObject, IReactiveObject
{
/// <inheritdoc cref="XunYouGame"/>
[MP2Key(0), JsonPropertyOrder(0)]
public XunYouGame Model { get; set; } = model;
/// <summary>
/// 加速延迟
/// </summary>
[Reactive]
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public uint PingValue { get; set; }
/// <summary>
/// 加速延迟是否无效
/// </summary>
[Reactive]
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public bool PingValueIsInvalid { get; set; }
/// <summary>
/// 设置加速延迟
/// </summary>
/// <param name="value"></param>
public void SetPingValue(uint value)
{
PingValueIsInvalid = value >= 1000;
if (PingValueIsInvalid) // 延迟大于等于1000,就显示--
PingValue = default;
else
PingValue = value;
}
/// <summary>
/// 加速丢包率
/// </summary>
[Reactive]
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public float PingSpeedLoss { get; set; }
/// <summary>
/// 游戏信息
/// </summary>
[Reactive]
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public XunYouGameInfo? GameInfo { get; set; }
/// <summary>
/// 是否正在加速等待
/// </summary>
[Reactive]
[MP2Ignore, SystemTextJsonIgnore]
public bool IsAccelerating { get; set; }
/// <summary>
/// 加速进度
/// </summary>
[Reactive]
[MP2Ignore, SystemTextJsonIgnore]
public int AcceleratingProgress { get; set; }
/// <summary>
/// 是否已加速
/// </summary>
[Reactive]
[MP2Ignore, SystemTextJsonIgnore]
public bool IsAccelerated { get; set; }
/// <summary>
/// 是否正在停止加速等待
/// </summary>
[Reactive]
[MP2Ignore, SystemTextJsonIgnore]
public bool IsStopAccelerating { get; set; }
/// <summary>
/// 最后加速时间
/// </summary>
[Reactive]
[MP2Key(1), JsonPropertyOrder(1)]
public DateTimeOffset? LastAccelerateTime { get; set; }
/// <summary>
/// 加速的区服
/// </summary>
[Reactive]
[MP2Key(2), JsonPropertyOrder(2)]
public XunYouGameArea? SelectedArea { get; set; }
/// <summary>
/// 加速的区服服务器
/// </summary>
[Reactive]
[MP2Key(3), JsonPropertyOrder(3)]
public XunYouGameServer? SelectedServer { get; set; }
}
/// <summary>
/// <see cref="XunYouGame"/> 的视图模型
/// </summary>
sealed partial class XunYouGameViewModel(XunYouGame model) : ReactiveSerializationObject
{
/// <summary>
/// Initializes a new instance of the <see cref="XunYouGameViewModel"/> class.
/// </summary>
[MP2Constructor, SystemTextJsonConstructor]
public XunYouGameViewModel() : this(new())
{
}
/// <summary>
/// ViewModel => Model
/// </summary>
/// <param name="vm"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator XunYouGame(XunYouGameViewModel vm) => vm.Model;
/// <summary>
/// Model => ViewModel
/// </summary>
/// <param name="model"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator XunYouGameViewModel(XunYouGame model) => new(model);
/// <inheritdoc cref="XunYouGame.Id"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public int Id { get => Model.Id; set => Model.Id = value; }
/// <inheritdoc cref="XunYouGame.Name"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public string? Name { get => Model.Name; set => Model.Name = value; }
/// <inheritdoc cref="XunYouGame.IconUrl"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public string? IconUrl { get => Model.IconUrl; set => Model.IconUrl = value; }
/// <inheritdoc cref="XunYouGame.PicUrl"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public string? PicUrl { get => Model.PicUrl; set => Model.PicUrl = value; }
/// <inheritdoc cref="XunYouGame.PicMD5"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public string? PicMD5 { get => Model.PicMD5; set => Model.PicMD5 = value; }
/// <inheritdoc cref="XunYouGame.LogoUrl"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public string? LogoUrl { get => Model.LogoUrl; set => Model.LogoUrl = value; }
/// <inheritdoc cref="XunYouGame.LogoMD5"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public string? LogoMD5 { get => Model.LogoMD5; set => Model.LogoMD5 = value; }
/// <inheritdoc />
public override string ToString() => Model.ToString()!;
}
sealed class LastAccelerateTimeComparer : IComparer<XunYouGameViewModel>
{
/// <inheritdoc/>
public int Compare(XunYouGameViewModel? x, XunYouGameViewModel? y)
{
if (x == null || y == null)
return 0;
if (x.IsAccelerating)
{
return -1;
}
else if (y.IsAccelerating)
{
return 1;
}
if (x.IsAccelerated)
{
return -1;
}
else if (y.IsAccelerated)
{
return 1;
}
else if (x.LastAccelerateTime.HasValue && y.LastAccelerateTime.HasValue)
{
return y.LastAccelerateTime.Value.CompareTo(x.LastAccelerateTime.Value);
}
else if (x.LastAccelerateTime.HasValue)
{
return -1;
}
else if (y.LastAccelerateTime.HasValue)
{
return 1;
}
else
{
var mygamekeys = GameAcceleratorSettings.MyGames.Value?.Keys;
if (mygamekeys != null)
{
var x2 = mygamekeys.IndexOf(x.Id);
var y2 = mygamekeys.IndexOf(y.Id);
if (x2 < y2)
{
return -1;
}
else if (x2 > y2)
{
return 1;
}
}
return 0;
}
}
}
@@ -1,5 +1,7 @@
using BD.WTTS.Properties;
using BD.WTTS.UI.Views.Pages;
using dotnetCampus.Ipc.CompilerServices.GeneratedProxies;
using dotnetCampus.Ipc.Pipes;
namespace BD.WTTS.Plugins;
@@ -8,6 +10,13 @@ namespace BD.WTTS.Plugins;
#endif
public sealed class Plugin : PluginBase<Plugin>, IPlugin
{
static Plugin()
{
#if WINDOWS
XunYouSDK.Initialize();
#endif
}
const string moduleName = AssemblyInfo.Accelerator;
public override Guid Id => Guid.Parse(AssemblyInfo.AcceleratorId);
@@ -43,6 +52,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
readonly TaskCompletionSource<IReverseProxyService> reverseProxyService = new();
readonly TaskCompletionSource<ICertificateManager> certificateManager = new();
//readonly TaskCompletionSource<IAcceleratorService> acceleratorService = new();
public override void ConfigureDemandServices(IServiceCollection services, Startup startup)
{
@@ -56,6 +66,16 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
services.AddSingleton(_ => certificateManager.Task.GetAwaiter().GetResult());
#endif
}
//if (startup.HasIPCRoot)
//{
// services.AddSingleton<IAcceleratorService, BackendAcceleratorServiceImpl>();
//}
//else if (startup.IsMainProcess)
{
//services.AddSingleton(_ => acceleratorService.Task.GetAwaiter().GetResult());
services.AddSingleton<IAcceleratorService, BackendAcceleratorServiceImpl>();
services.AddSingleton<IXunYouAccelStateToFrontendCallback, XunYouAccelStateToFrontendCallbackImpl>();
}
if (startup.HasServerApiClient)
{
@@ -64,6 +84,14 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
}
}
sealed class XunYouAccelStateToFrontendCallbackImpl : IXunYouAccelStateToFrontendCallback
{
public void XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m)
{
GameAcceleratorService.Current.XYAccelState = m;
}
}
public override void ConfigureRequiredServices(IServiceCollection services, Startup startup)
{
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
@@ -71,6 +99,18 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
#endif
}
public override void ConfigureServices(IpcProvider ipcProvider, Startup startup)
{
//if (startup.HasIPCRoot)
//{
// ipcProvider.CreateIpcJoint(Ioc.Get<IAcceleratorService>());
//}
//else if (startup.IsMainProcess)
//{
// ipcProvider.CreateIpcJoint(Ioc.Get<IXunYouAccelStateToFrontendCallback>());
//}
}
public override async ValueTask OnInitializeAsync()
{
var ipc = IPCMainProcessService.Instance;
@@ -94,6 +134,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
// 从子进程中获取 IPC 远程服务
await GetIpcRemoteServiceAsync(moduleName, ipc, reverseProxyService);
await GetIpcRemoteServiceAsync(moduleName, ipc, certificateManager);
//await GetIpcRemoteServiceAsync(IPlatformService.IPCRoot.moduleName, ipc, acceleratorService);
#if DEBUG
//try
//{
@@ -125,6 +166,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
try
{
await ProxyService.Current.ExitAsync();
GameAcceleratorSettings.MyGames.Save();
}
catch
{
@@ -196,5 +238,6 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
public override IEnumerable<(Action<IServiceCollection>? @delegate, bool isInvalid, string name)>? GetConfiguration(bool directoryExists)
{
yield return GetConfiguration<ProxySettings_>(directoryExists);
yield return GetConfiguration<GameAcceleratorSettings_>(directoryExists);
}
}
@@ -0,0 +1,10 @@
// C# 10 定义全局 using
#pragma warning disable IDE0079 // 请删除不必要的忽略
#pragma warning disable IDE0005
#pragma warning disable SA1209 // Using alias directives should be placed after other using directives
#pragma warning disable SA1211 // Using alias directives should be ordered alphabetically by alias name
global using Mobius;
global using Mobius.Enums;
global using Mobius.Models;
@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace BD.WTTS.Properties {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class WinDivertResource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal WinDivertResource() {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BD.WTTS.Properties.WinDivertResource", typeof(WinDivertResource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// </summary>
internal static byte[] WinDivert_dll {
get {
object obj = ResourceManager.GetObject("WinDivert_dll", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// </summary>
internal static byte[] WinDivert64_sys {
get {
object obj = ResourceManager.GetObject("WinDivert64_sys", resourceCulture);
return ((byte[])(obj));
}
}
}
}
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="WinDivert64_sys" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\..\ref\WinDivert64.sys;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="WinDivert_dll" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\..\ref\WinDivert.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>
@@ -47,17 +47,8 @@ internal sealed class ScriptRepository : Repository<Script, int>, IScriptReposit
const string sql_ =
$"{SQLStrings.Update}[{Script.TableName}] " +
$"set [{Script.ColumnName_CachePath}] = '{{0}}' " +
$"where [{Script.ColumnName_CachePath}] = {{1}}";
$"where [{Script.ColumnName_Id}] = {{1}}";
var sql = string.Format(sql_, item.CachePath, item.LocalId);
//var sql =
// SQLStrings.Update +
// "[" + Script.TableName + "]" +
// " set " +
// "[" + Script.ColumnName_Enable + "]" +
// $" = {item.Enable}" +
// " where " +
// "[" + Script.ColumnName_Id + "]" +
// $" = {item.LocalId}";
var r = await dbConnection.ExecuteAsync(sql);
return r;
}, cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -0,0 +1,192 @@
using System.Threading.Channels;
namespace BD.WTTS.Services.Implementation;
partial class BackendAcceleratorServiceImpl
{
XunYouState lastXunYouState = default;
int XunYouAccelStateCallback(XunYouState status, nint thisptr)
{
lastXunYouState = status;
XunYouAccelStateModel m;
switch (status)
{
case XunYouState.加速中:
case XunYouState.加速已完成:
case XunYouState.停止加速中:
m = GetXunYouAccelStateModel(status);
break;
default:
m = new XunYouAccelStateModel
{
State = status,
};
break;
}
#if DEBUG
static string? EnumToStringDebug<T>(T? value) where T : struct, Enum
{
if (value is null)
return null;
if (Enum.IsDefined(typeof(T), value))
{
return $"{value}({ConvertibleHelper.Convert<int, T>(value.Value)})";
}
return value.ToString();
}
Console.WriteLine($"迅游加速状态变更:{EnumToStringDebug((XunYouState?)m.State)}, {EnumToStringDebug(m.AccelState)}, GameId: {m.GameId}, AreaId: {m.AreaId}, ServerId: {m.ServerId}");
#endif
XunYouAccelStateToFrontendCallback(m);
return 0;
}
/*async*/
void XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m)
{
if (disposedValue)
return;
try
{
// 通知前端
//var ipc = Ioc.Get<IPCSubProcessService>();
//var accelStateToFrontendCallback = ipc.GetService<IXunYouAccelStateToFrontendCallback>();
var accelStateToFrontendCallback = Ioc.Get<IXunYouAccelStateToFrontendCallback>();
accelStateToFrontendCallback.ThrowIsNull(nameof(accelStateToFrontendCallback)).XunYouAccelStateToFrontendCallback(m);
//await Plugin.IpcServer.HubContext.Clients.All.SendAsync(nameof(XunYouAccelStateModel), m);
}
catch (Exception ex)
{
Log.Error(nameof(BackendAcceleratorServiceImpl), ex, "XunYouAccelStateToFrontendCallback fail.");
}
}
static XunYouAccelStateModel GetXunYouAccelStateModel(XunYouState status)
{
var accState = XunYouSDK.GetAccelStateEx(out var gameid, out var areaid, out var serverid);
return new XunYouAccelStateModel
{
State = status,
AccelState = accState,
GameId = gameid,
AreaId = areaid,
ServerId = serverid,
};
}
/// <inheritdoc/>
public async Task<ApiRsp<bool>> XY_IsInstall(CancellationToken cancellationToken = default)
{
var result = XunYouSDK.IsInstall();
await Task.CompletedTask;
return result;
}
/// <inheritdoc/>
public async Task<ApiRsp<XunYouUninstallCode>> XY_Uninstall(CancellationToken cancellationToken = default)
{
var result = XunYouSDK.Uninstall();
await Task.CompletedTask;
return result;
}
/// <inheritdoc/>
public async Task<ApiRsp<int>> XY_StartEx2(
string openid,
string nickname,
int gameid,
int area,
int server,
string? areaPath = default,
string? svrPath = default,
CancellationToken cancellationToken = default)
{
var result = XunYouSDK.StartEx2(
openid,
nickname,
gameid,
area,
server,
null,
default,
GameAcceleratorSettings.WattAcceleratorDirPath.Value!,
areaPath,
svrPath);
await Task.CompletedTask;
return result;
}
/// <inheritdoc/>
public async Task<ApiRsp<int>> XY_StartAccel(
int gameid,
int area,
int serverid = default,
string? areaName = default,
string? svrName = default,
CancellationToken cancellationToken = default)
{
var result = XunYouSDK.StartAccel(
gameid,
area,
areaName,
serverid,
svrName);
await Task.CompletedTask;
return result == XunYouSendResultCode.发送失败 ? 300 : 101;
}
/// <inheritdoc/>
public async Task<ApiRsp<XunYouAccelStateModel?>> XY_GetAccelStateEx(CancellationToken cancellationToken = default)
{
var result = GetXunYouAccelStateModel(lastXunYouState);
await Task.CompletedTask;
return result;
}
/// <inheritdoc/>
public async IAsyncEnumerable<ApiRsp<int>> XY_Install(string installPath, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var channel = Channel.CreateUnbounded<int>();
int XunYouDownLoadCallback(int par, nint thisptr)
{
channel.Writer.TryWrite(par);
if (par > 100)
{
channel.Writer.Complete();
}
return 0;
}
var installPackPath = Plugin.Instance.CacheDirectory;
var result = XunYouSDK.Install(XunYouDownLoadCallback, default, installPath, installPackPath);
if (result == 101) // 成功
{
await foreach (var item in channel.Reader.ReadAllAsync(cancellationToken))
{
yield return item;
}
}
else
{
yield return result;
}
}
/// <inheritdoc/>
public async Task<ApiRsp<XunYouSendResultCode>> XY_StopAccel(CancellationToken cancellationToken = default)
{
var result = XunYouSDK.Stop();
await Task.CompletedTask;
return result;
}
/// <inheritdoc/>
public async Task<ApiRsp<XunYouIsRunningCode>> XY_IsRunning(CancellationToken cancellationToken = default)
{
var result = XunYouSDK.IsRunning();
await Task.CompletedTask;
return result;
}
}
@@ -0,0 +1,45 @@
namespace BD.WTTS.Services.Implementation;
sealed partial class BackendAcceleratorServiceImpl : IAcceleratorService, IDisposable
{
bool disposedValue;
public BackendAcceleratorServiceImpl()
{
}
public void InitStateSubscribe()
{
try
{
if (XunYouSDK.IsSupported)
XunYouSDK.GetState(XunYouAccelStateCallback);
}
catch (Exception ex)
{
Log.Error(nameof(BackendAcceleratorServiceImpl), ex, "GetAccelStateEx2 fail.");
}
}
void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// 释放托管状态(托管对象)
}
// 释放未托管的资源(未托管的对象)并重写终结器
// 将大型字段设置为 null
disposedValue = true;
}
}
public void Dispose()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
@@ -134,7 +134,10 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
if (jsSaveInfo.Exists)
{
if (isNoRepeat)
{
jsSaveInfo.Delete();
fileInfo.CopyTo(jsSavePath);
}
}
else
{
@@ -366,17 +369,16 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
static IApiRsp OK_Script_DeleteSuccess() => ApiRspHelper.Ok(AppResources.Script_DeleteSuccess);
}
/// <summary>
/// 版本升级 检查数据库 数据 是否正确 不正确删除重新打包缓存文件
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
public async Task<IEnumerable<ScriptDTO>> CheckFiles(IEnumerable<ScriptDTO> list)
{
var scripts = list.ToList();
foreach (var item in list)
{
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}/"))
{
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
await scriptRepository.SaveScriptCachePathAsync(item, default);
}
if (!CheckFile(item))
{
var temp = await DeleteScriptAsync(item);
@@ -391,6 +393,38 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
//toast.Show($"脚本:{item.Name}_文件丢失,删除失败去尝试手动删除");
Toast.Show(ToastIcon.Error, Strings.Script_NoFileDeleteError_.Format(item.Name));
}
continue;
}
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}{Path.DirectorySeparatorChar}"))
{
var oldCachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
if (File.Exists(oldCachePath))
{
File.Delete(oldCachePath);
}
await TryReadFileAsync(item, true);
//清理 脚本内容 后续 IPC 传递 插件进程读取。
item.Content = string.Empty;
await scriptRepository.SaveScriptCachePathAsync(item, default);
}
else
{
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
var file = new FileInfo(cachePath);
//检查缓存文件是否为空 为空则 刷新
if (file.Exists)
{
if (file.Length == 0)
{
file.Delete();
await TryReadFileAsync(item, true);
//清理 脚本内容 后续 IPC 传递 插件进程读取。
item.Content = string.Empty;
}
}
}
}
return scripts;
@@ -405,8 +439,9 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
/// 修改为仅尝试判断文件是否存在
/// </summary>
/// <param name="item"></param>
/// <param name="isReadContent"></param>
/// <returns></returns>
public async Task<ScriptDTO> TryReadFileAsync(ScriptDTO item)
public async Task<ScriptDTO> TryReadFileAsync(ScriptDTO item, bool isReadContent = false)
{
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
if (File.Exists(cachePath))
@@ -419,7 +454,10 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
var infoPath = Path.Combine(Plugin.Instance.AppDataDirectory, item.FilePath);
if (File.Exists(infoPath))
{
//item.Content = File.ReadAllText(infoPath);
if (isReadContent)
{
item.Content = File.ReadAllText(infoPath);
}
if (!await BuildScriptAsync(item, fileInfo, item.IsCompile))
{
toast.Show(ToastIcon.Error, AppResources.Script_ReadFileError_.Format(item.Name));
@@ -0,0 +1,89 @@
namespace BD.WTTS.Services;
partial interface IAcceleratorService // XunYou
{
/// <summary>
/// 迅游是否安装
/// </summary>
/// <returns></returns>
Task<ApiRsp<bool>> XY_IsInstall(CancellationToken cancellationToken = default);
/// <summary>
/// 卸载迅游
/// </summary>
/// <returns></returns>
Task<ApiRsp<XunYouUninstallCode>> XY_Uninstall(CancellationToken cancellationToken = default);
/// <summary>
/// 迅游启动加速
/// </summary>
/// <param name="openid">OpenId</param>
/// <param name="nickname">用户昵称</param>
/// <param name="gameid">游戏ID</param>
/// <param name="area">区服Id</param>
/// <param name="server">游戏服Id</param>
/// <param name="areaPath">区服名称</param>
/// <param name="svrPath">游戏服名</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<ApiRsp<int>> XY_StartEx2(
string openid,
string nickname,
int gameid,
int area,
int server,
string? areaPath = default,
string? svrPath = default,
CancellationToken cancellationToken = default);
/// <summary>
/// 同步发送加速命令
/// </summary>
/// <param name="gameid">游戏Id</param>
/// <param name="area">区服Id</param>
/// <param name="serverid"></param>
/// <param name="areaName">区服名称</param>
/// <param name="svrName">游戏服名</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<ApiRsp<int>> XY_StartAccel(
int gameid,
int area,
int serverid = default,
string? areaName = default,
string? svrName = default,
CancellationToken cancellationToken = default);
/// <summary>
/// 安装迅游
/// </summary>
/// <param name="installPath">安装路径</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IAsyncEnumerable<ApiRsp<int>> XY_Install(string installPath, CancellationToken cancellationToken = default);
/// <summary>
/// 获取迅游加速状态
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<ApiRsp<XunYouAccelStateModel?>> XY_GetAccelStateEx(CancellationToken cancellationToken = default);
/// <summary>
/// 停止加速
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<ApiRsp<XunYouSendResultCode>> XY_StopAccel(CancellationToken cancellationToken = default);
/// <summary>
/// 迅游加速器是否在运行
/// </summary>
/// <returns></returns>
Task<ApiRsp<XunYouIsRunningCode>> XY_IsRunning(CancellationToken cancellationToken = default);
}
partial interface IXunYouAccelStateToFrontendCallback
{
void XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m);
}
@@ -0,0 +1,12 @@
using dotnetCampus.Ipc.CompilerServices.Attributes;
namespace BD.WTTS.Services;
/// <summary>
/// 加速模块服务,连接前后端的胶水服务
/// </summary>
//[IpcPublic(Timeout = AssemblyInfo.IpcTimeout, IgnoresIpcException = false)]
public partial interface IAcceleratorService
{
void InitStateSubscribe();
}
@@ -0,0 +1,646 @@
// ReSharper disable once CheckNamespace
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using BD.Common.Models;
using BD.WTTS.UI.Views.Controls;
using BD.WTTS.UI.Views.Pages;
using FluentAvalonia.UI.Controls;
namespace BD.WTTS.Services;
public sealed partial class GameAcceleratorService
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
: ReactiveObject
#endif
{
static GameAcceleratorService? mCurrent;
public static GameAcceleratorService Current => mCurrent ??= new();
public SourceCache<XunYouGameViewModel, int> Games { get; }
[Reactive]
public IReadOnlyCollection<XunYouGame>? AllGames { get; set; }
[Reactive]
public bool IsLoadingGames { get; set; }
[Reactive]
public string? SearchText { get; set; }
[Reactive]
public XunYouGameViewModel? CurrentAcceleratorGame { get; set; }
[Reactive]
public XunYouAccelStateModel? XYAccelState { get; set; }
public ICommand DeleteMyGameCommand { get; }
public ICommand GameAcceleratorCommand { get; }
public ICommand InstallAcceleratorCommand { get; }
public ICommand UninstallAcceleratorCommand { get; }
public ICommand AcceleratorChangeAreaCommand { get; }
GameAcceleratorService()
{
Ioc.Get<IAcceleratorService>().InitStateSubscribe(); // 仅旧项目(WattToolkit)上监听
Games = new(t => t.Id);
DeleteMyGameCommand = ReactiveCommand.Create<XunYouGameViewModel>(DeleteMyGame);
GameAcceleratorCommand = ReactiveCommand.CreateFromTask<XunYouGameViewModel>(GameAccelerator);
InstallAcceleratorCommand = ReactiveCommand.CreateFromTask(InstallAccelerator);
UninstallAcceleratorCommand = ReactiveCommand.Create(UninstallAccelerator);
AcceleratorChangeAreaCommand = ReactiveCommand.Create<XunYouGameViewModel>(AcceleratorChangeArea);
if (XunYouSDK.IsSupported)
{
this.WhenValueChanged(x => x.XYAccelState, false)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x =>
{
if (x == null)
return;
switch (x.State)
{
case XunYouState.加速中:
if (x.GameId != 0 && CurrentAcceleratorGame == null)
{
var game2 = Games.Items.FirstOrDefault(s => s.Id == x.GameId);
if (game2 != null)
{
game2.IsAccelerated = false;
game2.IsAccelerating = true;
game2.LastAccelerateTime = DateTimeOffset.Now;
CurrentAcceleratorGame = game2;
}
else
{
var game = AllGames?.FirstOrDefault(s => s.Id == x.GameId);
if (game != null)
{
var cApp = new XunYouGameViewModel(game)
{
IsAccelerated = false,
LastAccelerateTime = DateTimeOffset.Now,
IsAccelerating = true,
};
CurrentAcceleratorGame = cApp;
}
}
}
break;
case XunYouState.加速已完成:
if (x.GameId != 0)
{
var game2 = Games.Items.FirstOrDefault(s => s.Id == x.GameId);
if (game2 != null)
{
SetGameStatus(game2, x.AreaId, x.ServerId);
}
else
{
var game = AllGames?.FirstOrDefault(s => s.Id == x.GameId);
if (game != null)
{
SetGameStatus(game, x.AreaId, x.ServerId);
}
else
{
RefreshAllGames();
}
}
}
break;
case XunYouState.未加速:
case XunYouState.停止加速中:
case XunYouState.加速已断开:
//先停止测速
XunYouSDK.StopTestSpeded();
Games.Items.Where(s => s.IsAccelerating || s.IsAccelerated).ForEach(s =>
{
RestoreGameStatus(s);
Games.Refresh(s);
});
if (CurrentAcceleratorGame != null)
{
RestoreGameStatus(CurrentAcceleratorGame);
CurrentAcceleratorGame = null;
}
break;
case XunYouState.登录中:
case XunYouState.登录失败:
case XunYouState.登录成功:
case XunYouState.启动中:
case XunYouState.启动成功:
break;
default:
Games.Items.Where(s => s.IsAccelerating || s.IsAccelerated).ForEach(s =>
{
RestoreGameStatus(s);
Games.Refresh(s);
});
if (CurrentAcceleratorGame != null)
{
RestoreGameStatus(CurrentAcceleratorGame);
CurrentAcceleratorGame = null;
}
Toast.Show(ToastIcon.Warning, x.State.ToString());
break;
}
});
RefreshAllGames();
LoadGames();
}
}
/// <summary>
/// 恢复加速游戏状态
/// </summary>
private static void RestoreGameStatus(XunYouGameViewModel app)
{
app.SetPingValue(0);
app.PingSpeedLoss = 0;
app.IsAccelerating = false;
app.IsAccelerated = false;
app.IsStopAccelerating = false;
}
/// <summary>
/// 设置加速游戏状态
/// </summary>
void SetGameStatus(XunYouGameViewModel game, int areaId = 0, int serverId = 0)
{
if (CurrentAcceleratorGame != null && CurrentAcceleratorGame.Id == game.Id)
{
CurrentAcceleratorGame.IsAccelerating = false;
CurrentAcceleratorGame.IsAccelerated = true;
CurrentAcceleratorGame.LastAccelerateTime = DateTimeOffset.Now;
if (CurrentAcceleratorGame.SelectedArea?.Id != areaId)
{
var gameInfo = XunYouSDK.GetGameInfo(game.Id);
CurrentAcceleratorGame.SelectedArea = gameInfo?.Areas?.FirstOrDefault(s => s.Id == areaId);
}
if (CurrentAcceleratorGame.SelectedServer?.Id != serverId)
{
CurrentAcceleratorGame.SelectedServer = CurrentAcceleratorGame.SelectedArea?.Servers?.FirstOrDefault(s => s.Id == serverId);
}
}
else
{
var cApp = new XunYouGameViewModel(game)
{
IsAccelerated = true,
LastAccelerateTime = DateTimeOffset.Now,
IsAccelerating = false,
};
var gameInfo = XunYouSDK.GetGameInfo(game.Id);
cApp.SelectedArea = gameInfo?.Areas?.FirstOrDefault(s => s.Id == areaId);
cApp.SelectedServer = cApp.SelectedArea?.Servers?.FirstOrDefault(s => s.Id == serverId);
CurrentAcceleratorGame = cApp;
}
if (GameAcceleratorSettings.MyGames.ContainsKey(CurrentAcceleratorGame.Id))
{
GameAcceleratorSettings.MyGames.Remove(CurrentAcceleratorGame.Id);
}
GameAcceleratorSettings.MyGames.TryAdd(CurrentAcceleratorGame.Id, CurrentAcceleratorGame);
Games.AddOrUpdate(CurrentAcceleratorGame);
//加速后
Toast.Show(ToastIcon.Success, "加速成功");
int testSpeedCallback(SpeedCallbackWrapper w)
{
if (CurrentAcceleratorGame != null)
{
CurrentAcceleratorGame.SetPingValue(w.Struct.PingSpeed);
CurrentAcceleratorGame.PingSpeedLoss = w.Struct.PingSpeedLoss;
}
#if DEBUG
Console.WriteLine($"测速通知状态:{w.State},SpeedCallbackInfo: ErrorDesc/{w.ErrorDesc}, ErrorCode/{w.Struct.ErrorCode}, PingSpeed/{w.Struct.PingSpeed}, PingLocal/{w.Struct.PingLocal}, PingSpeedLoss/{w.Struct.PingSpeedLoss}, PingLocalLoss/{w.Struct.PingLocalLoss}");
#endif
return 0;
}
var speedCode = XunYouSDK.TestSpeed(CurrentAcceleratorGame.Id,
CurrentAcceleratorGame.SelectedArea?.Id ?? 0,
CurrentAcceleratorGame.SelectedServer?.Id ?? 0,
testSpeedCallback);
#if DEBUG
if (speedCode == XunYouTestSpeedCode.成功)
{
Toast.Show(ToastIcon.Info, "发送测速指令");
}
#endif
}
public async Task GameAccelerator(XunYouGameViewModel app)
{
if (app.IsAccelerating)
return;
app.IsAccelerating = true;
if (!app.IsAccelerated)
{
app.LastAccelerateTime = DateTimeOffset.Now;
CurrentAcceleratorGame = app;
if (UserService.Current.User?.WattOpenId == null)
{
Toast.Show(ToastIcon.Warning, "需要登录账号才能使用游戏加速功能!");
app.IsAccelerating = false;
return;
}
var xunYouIsInstall = await Ioc.Get<IAcceleratorService>().XY_IsInstall();
if (xunYouIsInstall.HandleUI(out var isInstall))
{
if (!isInstall)
{
//if (!await IWindowManager.Instance.ShowTaskDialogAsync(
// new MessageBoxWindowViewModel() { Content = "需要下载 Watt 加速器插件才可使用,确定要下载吗?" }, title: "未安装加速插件", isCancelButton: true))
//{
// app.IsAccelerating = false;
// return;
//}
if (!await IWindowManager.Instance.ShowTaskDialogAsync(new MessageBoxWindowViewModel(),
pageContent: new AcceleratorPathAskBox(), title: "未安装加速插件", isCancelButton: true))
{
app.IsAccelerating = false;
return;
}
await InstallAccelerator();
}
#region 通过 XY_GetAccelStateEx 判断是否已启动迅游
//var accStateR = await IAcceleratorService.Instance.XY_GetAccelStateEx();
//bool isStartXY = false;
//if (accStateR.HandleUI(out var accStateResponse))
//{
// isStartXY = accStateResponse.AccelState != XunYouAccelStateEx.获取失败;
// if (isStartXY && accStateResponse.State != XunYouState.未加速)
// {
// Toast.Show(ToastIcon.Warning, accStateResponse.State.ToString() ?? "失败");
// return;
// }
//}
#endregion
#region 通过 XY_IsRunning 判断是否已启动迅游
var apiRspIsRunning = await Ioc.Get<IAcceleratorService>().XY_IsRunning();
if (!apiRspIsRunning.HandleUI(out var isRunningCode))
{
// 调用后端失败,显示错误并中止逻辑
app.IsAccelerating = false;
return;
}
bool isStartXY = isRunningCode == XunYouIsRunningCode.加速器已启动;
#endregion
//加速中
var gameInfo = XunYouSDK.GetGameInfo(app.Id);
if (gameInfo == null)
{
Toast.Show(ToastIcon.Warning, "获取游戏信息失败");
app.IsAccelerating = false;
return;
}
app.GameInfo = gameInfo;
var vm = new GameInfoPageViewModel(app);
var result = await IWindowManager.Instance.ShowTaskDialogAsync(vm, $"{app.Name} - 区服选择", pageContent: new GameInfoPage(), isOkButton: false, disableScroll: true);
if (!result || app.SelectedArea == null)
{
app.IsAccelerating = false;
return;
}
if (GameAcceleratorSettings.MyGames.ContainsKey(app.Id))
{
GameAcceleratorSettings.MyGames.Remove(app.Id);
}
GameAcceleratorSettings.MyGames.TryAdd(app.Id, app);
Current.Games.AddOrUpdate(app);
var start = isStartXY ? await Ioc.Get<IAcceleratorService>().XY_StartAccel(app.Id,
app.SelectedArea.Id,
app.SelectedServer?.Id ?? 0,
app.SelectedArea.Name,
app.SelectedServer?.Name)
: await Ioc.Get<IAcceleratorService>().XY_StartEx2(
UserService.Current.User.WattOpenId,
UserService.Current.User.NickName,
app.Id,
app.SelectedArea.Id,
app.SelectedServer?.Id ?? 0,
app.SelectedArea.Name,
app.SelectedServer?.Name);
if (start.HandleUI(out var startCode))
{
if (startCode == 101)
{
app.AcceleratingProgress = 0;
Toast.Show(ToastIcon.Info, "正在加速中...");
}
else
{
Toast.Show(ToastIcon.Error, "加速启动失败");
app.IsAccelerating = false;
}
}
else
{
app.IsAccelerating = false;
}
}
}
else
{
var stopRequest = await Ioc.Get<IAcceleratorService>().XY_StopAccel();
if (stopRequest.HandleUI(out var code))
{
// 停止加速
if (code == XunYouSendResultCode.发送成功)
{
Toast.Show(ToastIcon.Info, "正在停止加速中...");
}
else
{
app.IsAccelerating = false;
Toast.Show(ToastIcon.Error, "停止加速失败,请尝试去加速器客户端停止加速");
}
}
}
}
public static void DeleteMyGame(XunYouGameViewModel app)
{
//if (await WindowManagerService.Current.ShowTaskDialogAsync(
// new MessageBoxWindowViewModel() { Content = Strings.Script_ReplaceTips }, isCancelButton: true, isDialog: false))
//{
//}
Current.Games.RemoveKey(app.Id);
GameAcceleratorSettings.MyGames.Remove(app.Id);
Toast.Show(ToastIcon.Success, "已移除");
}
public static void AddMyGame(XunYouGameViewModel app)
{
app.LastAccelerateTime = DateTimeOffset.Now;
GameAcceleratorSettings.MyGames.Remove(app.Id);
if (GameAcceleratorSettings.MyGames.TryAdd(app.Id, app))
{
Current.Games.AddOrUpdate(app);
Toast.Show(ToastIcon.Success, "已添加到游戏列表");
}
}
/// <summary>
/// 加载迅游游戏数据
/// </summary>
public void LoadGames()
{
if (!XunYouSDK.IsSupported)
return;
Task2.InBackground(() =>
{
if (!IsLoadingGames)
{
IsLoadingGames = true;
if (GameAcceleratorSettings.MyGames.Any_Nullable())
{
Games.Clear();
Games.AddOrUpdate(GameAcceleratorSettings.MyGames.Value!.Values);
}
else
{
var games = XunYouSDK.GetHotGames();
if (games != null)
{
Games.Clear();
GameAcceleratorSettings.MyGames.AddRange(games.Select(s => new KeyValuePair<int, XunYouGameViewModel>(s.Id, new XunYouGameViewModel(s))));
Games.AddOrUpdate(GameAcceleratorSettings.MyGames.Value!.Values);
}
}
IsLoadingGames = false;
}
});
}
public void RefreshAllGames()
{
Task2.InBackground(async () =>
{
var games = XunYouSDK.GetAllGames();
if (games != null)
{
AllGames = new ReadOnlyCollection<XunYouGame>(games);
}
//判断是否已经在加速
var accState = await Ioc.Get<IAcceleratorService>().XY_GetAccelStateEx();
if (accState.HandleUI() && accState.Content != null)
{
if (accState.Content.GameId > 0 && accState.Content.AccelState is
XunYouAccelStateEx.启动加速中 or
XunYouAccelStateEx.已加速)
{
//var game = Games.Lookup(accState.Content.GameId);
//if (game.HasValue)
//{
// game.Value.IsAccelerated = true;
// game.Value.LastAccelerateTime = DateTime.Now;
// var gameinfo = XunYouSDK.GetGameInfo(game.Value.Id);
// game.Value.SelectedArea = gameinfo?.Areas?.FirstOrDefault(s => s.Id == accState.Content.AreaId);
//}
var game = games?.FirstOrDefault(s => s.Id == accState.Content.GameId);
if (game != null)
{
MainThread2.BeginInvokeOnMainThread(() =>
{
SetGameStatus(game, accState.Content.AreaId, accState.Content.ServerId);
});
}
}
}
});
}
public static async Task InstallAccelerator()
{
var xunYouIsInstall = await Ioc.Get<IAcceleratorService>().XY_IsInstall();
if (xunYouIsInstall.HandleUI(out var isInstall))
{
if (isInstall)
{
Toast.Show(ToastIcon.Info, "已安装Watt加速器");
return;
}
var td = new TaskDialog
{
Title = "下载插件",
ShowProgressBar = true,
IconSource = new SymbolIconSource { Symbol = FluentAvalonia.UI.Controls.Symbol.Download },
SubHeader = "下载 Watt 加速器 插件",
Content = "正在初始化,请稍候",
XamlRoot = AvaloniaWindowManagerImpl.GetWindowTopLevel(),
//Buttons =
//{
// new TaskDialogButton(Strings.Cancel, TaskDialogStandardResult.Cancel)
//}
};
var install = Ioc.Get<IAcceleratorService>().XY_Install(GameAcceleratorSettings.WattAcceleratorDirPath.Value!);
td.Opened += async (s, e) =>
{
await foreach (var item in install)
{
if (item.HandleUI(out var content))
{
switch (content)
{
case < 100:
Dispatcher.UIThread.Post(() => { td.Content = $"正在下载 {item.Content}%"; });
td.SetProgressBarState(item.Content, TaskDialogProgressState.Normal);
break;
case 100:
td.SetProgressBarState(item.Content, TaskDialogProgressState.Indeterminate);
Dispatcher.UIThread.Post(() => { td.Content = $"下载完成,正在安装..."; });
break;
case (int)XunYouDownLoadCode.安装成功:
//处理成功
//Dispatcher.UIThread.Post(() => { td.Content = $"安装完成"; });
Dispatcher.UIThread.Post(() => { td.Hide(TaskDialogStandardResult.OK); });
td.Hide();
break;
case int n when n > 101 && n < (int)XunYouDownLoadCode.启动安装程序失败:
//处理失败
break;
// Code 和进度重叠 递进 1000 XunYouInstallOrStartCode.默认 XunYouInstallOrStartCode.已安装
case 1000:
Dispatcher.UIThread.Post(() => { td.Content = $"默认"; });
// XunYouInstallOrStartCode.默认
break;
case 1001:
Dispatcher.UIThread.Post(() => { td.Content = $"已安装"; });
// XunYouInstallOrStartCode.已安装
Dispatcher.UIThread.Post(() => { td.Hide(TaskDialogStandardResult.OK); });
break;
}
}
}
};
//_ = Task.Run(() => { XunYouSDK.InstallAsync(progress, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WattAccelerator")); });
var result = await td.ShowAsync(true);
}
}
public static async void UninstallAccelerator()
{
var xunYouIsInstall = await Ioc.Get<IAcceleratorService>().XY_IsInstall();
if (xunYouIsInstall.HandleUI(out var isInstall))
{
if (!isInstall)
{
Toast.Show(ToastIcon.Info, "未安装,不需要卸载");
return;
}
var uninstall = await Ioc.Get<IAcceleratorService>().XY_Uninstall();
if (uninstall.HandleUI(out var code))
{
if (code == 0)
{
Toast.Show(ToastIcon.Success, "卸载成功");
}
else
{
Toast.Show(ToastIcon.Error, "卸载失败");
}
}
}
}
public async void AcceleratorChangeArea(XunYouGameViewModel app)
{
var gameInfo = XunYouSDK.GetGameInfo(app.Id);
if (gameInfo == null)
{
Toast.Show(ToastIcon.Warning, "获取游戏信息失败");
return;
}
var vm = new GameInfoPageViewModel(new XunYouGameViewModel { Name = app.Name, GameInfo = gameInfo, SelectedArea = app.SelectedArea, SelectedServer = app.SelectedServer });
var result = await IWindowManager.Instance.ShowTaskDialogAsync(vm, $"{app.Name} - 区服选择", pageContent: new GameInfoPage(), isOkButton: false, disableScroll: true);
if (!result || vm.XunYouGame.SelectedArea == null)
{
return;
}
if (app.SelectedArea?.Id == vm.XunYouGame.SelectedArea?.Id && app.SelectedServer?.Id == vm.XunYouGame.SelectedServer?.Id)
{
// 切换区服选择不变时不重新加速
return;
}
app.GameInfo = gameInfo;
app.SelectedArea = vm.XunYouGame.SelectedArea;
app.SelectedServer = vm.XunYouGame.SelectedServer;
if (app.IsAccelerated)
{
//重新加速
var start = await Ioc.Get<IAcceleratorService>().XY_StartAccel(app.Id, app.SelectedArea?.Id ?? 0, app.SelectedServer?.Id ?? 0, app.SelectedArea?.Name);
if (start.HandleUI(out var startCode))
{
if (startCode == 101)
{
app.IsAccelerating = true;
app.IsAccelerated = false;
app.AcceleratingProgress = 0;
CurrentAcceleratorGame = app;
Toast.Show(ToastIcon.Info, "正在加速中...");
}
else
{
Toast.Show(ToastIcon.Error, "加速启动失败");
app.IsAccelerating = false;
}
}
else
{
app.IsAccelerating = false;
}
}
else
{
await GameAccelerator(app);
}
}
public async Task RefreshXYAccelState()
{
var result = await Ioc.Get<IAcceleratorService>().XY_GetAccelStateEx();
if (result.HandleUI(out var content))
{
XYAccelState = content;
}
}
}
@@ -183,6 +183,13 @@ partial class ProxyService
return Strings.CommunityFix_SetAsSystemPACProxyFail;
}
break;
#if WINDOWS
case ProxyMode.DNSIntercept:
{
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
}
break;
#endif
}
if (!OperatingSystem.IsWindows())
@@ -336,6 +343,14 @@ partial class ProxyService
await platformService.SetAsSystemPACProxyAsync(false);
}
break;
#if WINDOWS
case ProxyMode.DNSIntercept:
{
// 停止时也调用初始化
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
}
break;
#endif
}
StopTimer(); // 停止 UI 计时器
#if MACOS
@@ -12,7 +12,7 @@ public sealed partial class ProxyService
{
static ProxyService? mCurrent;
public static ProxyService Current => mCurrent ?? new();
public static ProxyService Current => mCurrent ??= new();
readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance;
readonly IScriptManager scriptManager = IScriptManager.Instance;
@@ -21,8 +21,6 @@ public sealed partial class ProxyService
ProxyService()
{
mCurrent = this;
ProxyDomains = new SourceCache<AccelerateProjectGroupDTO, string>(s => s.Name);
ProxyScripts = new SourceList<ScriptDTO>();
@@ -34,6 +32,7 @@ public sealed partial class ProxyService
.Subscribe(_ => SelectGroup = ProxyDomains.Items.FirstOrDefault());
this.WhenValueChanged(x => x.ProxyStatus, false)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async proxyStatusLeft =>
{
bool proxyStatusRight;
@@ -330,23 +329,24 @@ public sealed partial class ProxyService
#endif
if (result.IsSuccess)
{
if (ProxySettings.SupportProxyServicesStatus.Value.Any_Nullable() && result.Content.Any_Nullable())
{
var items = result.Content!.SelectMany(s => s.Items!);
foreach (var item in items)
{
if (ProxySettings.SupportProxyServicesStatus.Value.Contains(item.Id.ToString()))
{
item.ThreeStateEnable = true;
}
}
}
ProxyDomains.AddOrUpdate(result.Content!);
}
LoadOrSaveLocalAccelerate();
if (ProxySettings.SupportProxyServicesStatus.Value.Any_Nullable() && ProxyDomains.Items.Any_Nullable())
{
var items = ProxyDomains.Items!.SelectMany(s => s.Items!);
var enableItems = ProxySettings.SupportProxyServicesStatus.Value;
foreach (var item in items)
{
if (enableItems.Contains(item.Id.ToString()))
{
item.ThreeStateEnable = true;
}
}
}
//if (IsLoadImage && ProxyDomains.Items.Any_Nullable())
//{
// foreach (var item in ProxyDomains.Items)
@@ -443,6 +443,8 @@ public sealed partial class ProxyService
//拉取 GM.js
await BasicsInfoAsync();
//初始化后检查脚本更新
CheckScriptUpdate();
}
/// <summary>
@@ -0,0 +1,56 @@
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Settings;
[JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyProperties = true)]
[JsonSerializable(typeof(GameAcceleratorSettings_))]
internal partial class GameAcceleratorSettingsContext : JsonSerializerContext
{
static GameAcceleratorSettingsContext? instance;
public static GameAcceleratorSettingsContext Instance
=> instance ??= new GameAcceleratorSettingsContext(ISettings.GetDefaultOptions());
}
[MPObj, MP2Obj(SerializeLayout.Explicit)]
public sealed partial class GameAcceleratorSettings_ : ISettings, ISettings<GameAcceleratorSettings_>
{
public const string Name = nameof(GameAcceleratorSettings);
static string ISettings.Name => Name;
static JsonSerializerContext ISettings.JsonSerializerContext
=> GameAcceleratorSettingsContext.Instance;
static JsonTypeInfo ISettings.JsonTypeInfo
=> GameAcceleratorSettingsContext.Instance.GameAcceleratorSettings_;
static JsonTypeInfo<GameAcceleratorSettings_> ISettings<GameAcceleratorSettings_>.JsonTypeInfo
=> GameAcceleratorSettingsContext.Instance.GameAcceleratorSettings_;
/// <summary>
/// 我的加速游戏列表
/// </summary>
[MP2Key(0), JsonPropertyOrder(0)]
public Dictionary<int, XunYouGameViewModel> MyGames { get; set; } = [];
/// <summary>
/// 加速插件安装路径
/// </summary>
[MP2Key(1), JsonPropertyOrder(1)]
public string WattAcceleratorDirPath { get; set; } = DefaultWattAcceleratorDirPath;
public static readonly string DefaultWattAcceleratorDirPath = OperatingSystem.IsWindows() ?
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "WattAccelerator") : string.Empty;
}
public static partial class GameAcceleratorSettings
{
/// <inheritdoc cref="GameAcceleratorSettingsModel.MyGames"/>
public static SettingsProperty<int, XunYouGameViewModel, Dictionary<int, XunYouGameViewModel>, GameAcceleratorSettings_> MyGames { get; }
= new();
/// <inheritdoc cref="GameAcceleratorSettingsModel.WattAcceleratorDirPath"/>
public static SettingsProperty<string, GameAcceleratorSettings_> WattAcceleratorDirPath { get; }
= new(GameAcceleratorSettings_.DefaultWattAcceleratorDirPath);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

@@ -37,6 +37,9 @@ public sealed partial class AcceleratorPageViewModel
await ProxyService.Current.InitializeAccelerateAsync();
else
Toast.Show(ToastIcon.Warning, AppResources.Warning_PleaseStopAccelerate);
// 刷新时重新加载迅游游戏数据
GameAcceleratorService.Current.LoadGames();
});
ProxySettingsCommand = ReactiveCommand.Create(() =>

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