Compare commits

...
277 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
Aigio Liu 7f06198bb6 ⬆️ DirectoryPackages 2023-09-25 14:15:25 +08:00
Aigio Liu 8202df3a65 🚧 Misc 2023-09-25 13:40:19 +08:00
Aigio Liu 5033261a64 ⬆️ DirectoryPackages 2023-09-25 13:27:41 +08:00
Aigio Liu 191c957a23 🚧 3.0.0-rc.3 2023-09-25 13:21:55 +08:00
Aigio Liu 9eb5def6f0 🚧 Misc 2023-09-23 16:40:51 +08:00
Mossimo 2467ce9bb4 🐛 Fix macOS 加速错误 等待输入密码 过长导致 IPC 异常闪退 2023-09-23 15:41:33 +08:00
Aigio Liu f968f3f66a 🐛 SetBootAutoStart PrivilegedProcess 2023-09-23 13:50:08 +08:00
Aigio Liu 511a2fa366 🚧 Migrations.MoveAccelerator 2023-09-23 13:24:30 +08:00
RMBGAME 5ed54bd72e 🚧 托盘 2023-09-22 17:19:32 +08:00
Aigio Liu 91ecfa342a 🚧 ReverseProxy 2023-09-22 15:38:42 +08:00
RMBGAME be1481d063 ✨ 托盘改进 2023-09-22 15:18:59 +08:00
Aigio Liu fce80b18ee 🐛 GameAccount 2023-09-22 10:30:28 +08:00
Aigio Liu 211daa428a 🎨 Authenticator 2023-09-21 19:19:06 +08:00
Aigio Liu 781fd9489f 🐛 Authenticator 2023-09-21 18:52:45 +08:00
Aigio Liu 680586b6e9 🐛 InitializeScriptAsync 2023-09-21 18:14:59 +08:00
Aigio Liu 35ee526efc 🐛 -clt steam -account 2023-09-21 15:08:35 +08:00
Aigio Liu 2f0090d571 ⬆️ DirectoryPackages 2023-09-21 14:52:26 +08:00
Aigio Liu 2f6ad43382 🐛 Misc 2023-09-21 14:31:57 +08:00
Aigio Liu 20056583a3 🚧 ASF 2023-09-20 16:50:11 +08:00
Aigio Liu 77dc0abd2a 🐛 Accelerator 2023-09-20 14:25:31 +08:00
Aigio Liu ddf6486af6 🚧 ASF 2023-09-20 14:25:31 +08:00
Aigio Liu 0229003e19 🐛 Cert 2023-09-19 16:37:43 +08:00
Aigio Liu 8486edf6b8 🎨 Cert 2023-09-19 16:01:03 +08:00
RMBGAME fe941e1271 ✨ 托盘改进 2023-09-19 15:44:40 +08:00
RMBGAME 81129afc8f 🚧 托盘菜单 2023-09-18 18:51:08 +08:00
Aigio Liu 566cc4c017 🚧 Misc 2023-09-18 17:56:06 +08:00
Aigio Liu 0925ec9104 🐛 DoH 2023-09-16 18:03:02 +08:00
Aigio Liu cac4a9370a 🧪 DoH 2023-09-16 17:42:27 +08:00
Aigio Liu e32fade0ea 🚧 DNS over HTTPS 2023-09-16 15:52:42 +08:00
Aigio Liu f6554a3b21 🎨 IDnsAnalysisService 2023-09-16 12:20:45 +08:00
Aigio Liu d00572d97d 💄 DNS over HTTPS 2023-09-16 11:44:05 +08:00
Aigio Liu 56e3563634 ✨ DNS over HTTPS 2023-09-15 19:42:06 +08:00
Aigio Liu 9355c317ac 🔥 StartAsAdministratorByBypassUAC 2023-09-15 14:22:03 +08:00
Aigio Liu 35b5edd09a 🚧 Misc 2023-09-14 16:31:05 +08:00
Aigio Liu ab22ea1113 🐛 GameAccount 2023-09-14 16:07:45 +08:00
Aigio Liu 62c015ad0b 🚧 Misc 2023-09-14 15:38:57 +08:00
Aigio Liu 195f5269be ⬆️ .NET 7.0.11 & SDK 7.0.401 & VS 17.7.4 2023-09-14 14:32:43 +08:00
Aigio Liu b1fa5b4a4d 🐛 AppUpdate 2023-09-14 12:06:58 +08:00
Mossimo 7778854007 🐛修复迁移后脚本缓存路径问题 2023-09-13 18:00:25 +08:00
Aigio Liu 68bbd8b3e3 🐛 macOS AppCenter 2023-09-13 17:33:15 +08:00
Aigio Liu 137a2dcde9 🚧 Misc 2023-09-12 19:08:44 +08:00
Aigio Liu ef239c5dc2 🐛 SetBootAutoStart 2023-09-12 19:03:23 +08:00
ff m 1fd4d4e35a 🐛 修复 macOS 代码签名 2023-09-12 16:41:12 +08:00
Aigio Liu a71ff13d49 🐛 Misc 2023-09-12 14:46:24 +08:00
Aigio Liu 56f9be173a 🐛 dotnetfx 2023-09-12 14:36:44 +08:00
Aigio Liu 6e3e84c9c3 🐛 2023-09-12 14:27:04 +08:00
RMBGAME a55e28600f 🐛 SDA某些情况下导入错误修复 2023-09-12 13:56:34 +08:00
RMBGAME 31dd805a6d 修改启动时加速UI 2023-09-11 21:49:50 +08:00
Aigio Liu aeac99fc1c 🎨 IPCMainProcessServiceImpl / IpcLogger 2023-09-09 10:36:07 +08:00
RMBGAME 5b9bc9e00b 🚑 Misc 2023-09-09 03:17:27 +08:00
RMBGAME 5c3ab25271 🐛 修复因 HTTP/2 导致 discord websocket 验证失败问题 2023-09-09 03:14:37 +08:00
RMBGAME 1e8d373df5 🐛 修复最小化启动无效 2023-09-08 17:55:10 +08:00
Aigio Liu f226aeb8a9 🐛 fix #2830 2023-09-08 17:41:20 +08:00
Aigio Liu 3f0422cfbb ✨ pub hash command 2023-09-08 17:05:05 +08:00
john.jiang af89dddc4c 🎨 doh 2023-09-08 16:19:25 +08:00
Aigio Liu 55f2c519b2 🚧 3.0.207.0 2023-09-08 10:32:22 +08:00
Aigio Liu 527f5fb8a4 🐛 AvaloniaApplicationUpdateServiceImpl 2023-09-08 10:27:04 +08:00
Aigio Liu 8139a7414d 🚧 3.0.0-rc.2 2023-09-08 10:10:16 +08:00
418 changed files with 22916 additions and 2724 deletions
+23 -52
View File
@@ -89,22 +89,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TFM", "TFM", "{3B0CFC2A-F61
src\TFM_NETX_WITH_WINDOWS.props = src\TFM_NETX_WITH_WINDOWS.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm.Library", "ref\ArchiSteamFarm\ArchiSteamFarm.Library\ArchiSteamFarm.Library.csproj", "{6981D5CA-837C-47F8-9568-E3BD451A6C68}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibAPNG", "ref\Avalonia.Image2\LibAPNG\LibAPNG.csproj", "{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gif", "ref\Avalonia.Image2\Avalonia.Gif\Avalonia.Gif.csproj", "{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArchiSteamFarm", "ArchiSteamFarm", "{A3A703CC-F22E-40CE-9064-6DF3E55DD556}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Avalonia", "Avalonia", "{E5732454-7455-4078-88A2-E727DCC64D26}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReverseProxy", "ReverseProxy", "{4FA9B783-ADAE-46F6-9AAD-ED32F1839F13}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Steam", "Steam", "{99D97BD4-3F51-48BC-8BD2-647F6D268BB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "ref\Titanium-Web-Proxy\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{826716DC-65C2-456A-BB6D-5FDEC423E4E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameloop.Vdf", "ref\Gameloop.Vdf\Gameloop.Vdf\Gameloop.Vdf.csproj", "{FE91B979-FE45-43F3-B9C4-430F2360FDDF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam4NET", "ref\Steam4NET\Steam4NET\Steam4NET.csproj", "{C8792BC7-33C0-41D4-97F6-684AD4B0B6A5}"
@@ -224,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
@@ -238,26 +232,6 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|ARM64.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x64.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x64.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x86.ActiveCfg = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Debug|x86.Build.0 = Debug|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|Any CPU.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM64.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|ARM64.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x64.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x64.Build.0 = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x86.ActiveCfg = Release|Any CPU
{6981D5CA-837C-47F8-9568-E3BD451A6C68}.Release|x86.Build.0 = Release|Any CPU
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -298,26 +272,6 @@ Global
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x64.Build.0 = Release|Any CPU
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x86.ActiveCfg = Release|Any CPU
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29}.Release|x86.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|ARM64.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x64.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x64.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x86.ActiveCfg = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Debug|x86.Build.0 = Debug|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|Any CPU.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM64.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|ARM64.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x64.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x64.Build.0 = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x86.ActiveCfg = Release|Any CPU
{826716DC-65C2-456A-BB6D-5FDEC423E4E6}.Release|x86.Build.0 = Release|Any CPU
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE91B979-FE45-43F3-B9C4-430F2360FDDF}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -1308,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
@@ -1317,14 +1291,10 @@ Global
{202026AE-894E-4E18-9846-B0739CDBE3E9} = {46259E8D-32A4-4065-953D-D6F835B57A28}
{F776BA87-0B9B-4C3C-B04F-688DCC021BF1} = {202026AE-894E-4E18-9846-B0739CDBE3E9}
{3B0CFC2A-F61B-4000-AC12-6E0AFB5C475C} = {46259E8D-32A4-4065-953D-D6F835B57A28}
{6981D5CA-837C-47F8-9568-E3BD451A6C68} = {A3A703CC-F22E-40CE-9064-6DF3E55DD556}
{47B2D651-8EE0-4C05-B451-F2DA264DD5B3} = {E5732454-7455-4078-88A2-E727DCC64D26}
{5D6D28A7-1CD8-4C30-A0E4-1F8ACF389A29} = {E5732454-7455-4078-88A2-E727DCC64D26}
{A3A703CC-F22E-40CE-9064-6DF3E55DD556} = {90E88965-DB90-4B49-94F5-6D2965491988}
{E5732454-7455-4078-88A2-E727DCC64D26} = {90E88965-DB90-4B49-94F5-6D2965491988}
{4FA9B783-ADAE-46F6-9AAD-ED32F1839F13} = {90E88965-DB90-4B49-94F5-6D2965491988}
{99D97BD4-3F51-48BC-8BD2-647F6D268BB0} = {90E88965-DB90-4B49-94F5-6D2965491988}
{826716DC-65C2-456A-BB6D-5FDEC423E4E6} = {4FA9B783-ADAE-46F6-9AAD-ED32F1839F13}
{FE91B979-FE45-43F3-B9C4-430F2360FDDF} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
{C8792BC7-33C0-41D4-97F6-684AD4B0B6A5} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
{123F532B-4178-4D03-A4EE-2A8F3E01433F} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
@@ -1378,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
+34 -19
View File
@@ -10,11 +10,12 @@ Base_Path="/Users/moxiansheng/Documents/GitHub/SteamTools"
App_Path="$Base_Path/src/BD.WTTS.Client.Avalonia.App"
Publish_Path="bin/Release/Publish/assemblies"
Plugins_Release_Path="bin/Release"
CodesignKey_Name="mossimo CodesignKey"
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"
echo "编译插件"
@@ -29,53 +30,67 @@ 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/net8.0-macos/Steam++.app"
# #代码签名重新手动签名
codesign --remove-signature "$App_Publish_Path"
codesign --remove-signature "$App_Publish_Path/Contents/MacOS/Steam++"
dylibs=$(find "$App_Publish_Path/Contents/MonoBundle" -type f -name "*.dylib")
for dylib in $dylibs; do
codesign --remove-signature "$dylib"
codesign -f -s "$CodesignKey_Name" "$dylib"
done
codesign -f -s "$CodesignKey_Name" "$App_Publish_Path/Contents/MacOS/Steam++"
codesign -f -s "$CodesignKey_Name" "$App_Publish_Path"
sudo xattr -rd com.apple.quarantine "$App_Publish_Path"
+222 -1
View File
@@ -498,7 +498,7 @@
{
"TypeName": "bool",
"PropertyName": "GameInstalledFilter",
"DefaultValue": "true",
"DefaultValue": "false",
"DefaultValueIsConst": true,
"Summary": "已安装游戏筛选",
"IsRegionOrEndregion": null,
@@ -943,6 +943,227 @@
},
{
"Sharp": "endif"
},
{
"TypeName": "bool",
"PropertyName": "UseDoh",
"DefaultValue": "true",
"DefaultValueIsConst": true,
"Summary": "启用 DNS over HTTPS",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "string",
"PropertyName": "CustomDohAddres",
"DefaultValue": "null",
"DefaultValueIsConst": true,
"Summary": "自定义 DNS over HTTPS 地址",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

+5 -3
View File
@@ -5,11 +5,13 @@ namespace BD.WTTS;
/// </summary>
public static partial class AssemblyInfo
{
const string Version2 = "3.0";
/// <summary>
/// 语义化应用程序版本
/// https://semver.org/lang/zh-CN/
/// </summary>
public const string Version = "3.0.0";
public const string Version = $"{Version2}.0";
/// <summary>
/// 预览版本号,范围 1~9,只增不减
@@ -19,9 +21,9 @@ public static partial class AssemblyInfo
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "1";
const string ver_for_rc = "5";
public const string FileVersion = $"{Version}.{ver_for_rc}0{ver_for_preview}";
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
//public const string InformationalVersion = Version;
//public const string InformationalVersion = $"{Version}-preview.{ver_for_preview}";
+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")]
@@ -11,7 +11,7 @@
<Identity
Name="4651ED44255E.47979655102CE"
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
Version="3.0.0.0" />
Version="3.0.207.0" />
<Properties>
<DisplayName>Watt Toolkit</DisplayName>
@@ -62,9 +62,9 @@
<Extensions>
<desktop2:Extension Category="windows.firewallRules">
<desktop2:FirewallRules Executable="Steam++\Steam++.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all" />
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all" />
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
</desktop2:FirewallRules>
</desktop2:Extension>
</Extensions>
+1 -1
View File
@@ -9,6 +9,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="7.0.10"/>
<add key="d" value="7.0.11"/>
</appSettings>
</configuration>
+1 -1
View File
@@ -10,6 +10,6 @@
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
<appSettings>
<add key="d" value="7.0.10"/>
<add key="d" value="8.0.0"/>
</appSettings>
</configuration>
+3 -3
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 = "10";
dotnet_version_build = "0";
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
}
@@ -27,7 +27,7 @@ static unsafe partial class Program
//#else
// public const string dotnet_version_major = "7";
// public const string dotnet_version_minor = "0";
// public const string dotnet_version_build = "10";
// public const string dotnet_version_build = "11";
// public const string dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
//#endif
@@ -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>
@@ -1,8 +1,7 @@
{
"profiles": {
"BD.WTTS.Client.Avalonia.App": {
"commandName": "Project",
"commandLineArgs": "-clt devtools -steamrun"
"commandName": "Project"
}
}
}
+1 -1
View File
@@ -248,7 +248,7 @@ sealed partial class Program : Startup
sealed class Essentials_AppVerS : IApplicationVersionService
{
string IApplicationVersionService.ApplicationVersion => AssemblyInfo.Version;
string IApplicationVersionService.ApplicationVersion => AssemblyInfo.FileVersion;
string IApplicationVersionService.AssemblyTrademark => AssemblyInfo.Trademark;
}
@@ -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();
}
}
@@ -5,46 +5,19 @@ namespace BD.WTTS.Services.Implementation;
/// <inheritdoc cref="IAppUpdateService"/>
sealed class AvaloniaApplicationUpdateServiceImpl : AppUpdateServiceBaseImpl
{
readonly IWindowManager windowManager;
public AvaloniaApplicationUpdateServiceImpl(
IApplication application,
INotificationService notificationService,
IWindowManager windowManager,
IToast toast,
IMicroServiceClient client,
IOptions<AppSettings> options) : base(application, notificationService, toast, client, options)
{
this.windowManager = windowManager;
}
protected override async Task ShowNewVersionWindowAsync()
protected override bool HasActiveWindow()
{
await windowManager.ShowAsync(typeof(object), AppEndPoint.NewVersion, isParent: false);
}
public override async void OnMainOpenTryShowNewVersionWindow()
{
if (ShowNewVersionWindowOnMainOpen)
{
ShowNewVersionWindowOnMainOpen = false;
await ShowNewVersionWindowAsync();
}
}
protected override async void OnExistNewVersion()
{
base.OnExistNewVersion();
var hasActiveWindow = App.Instance.HasActiveWindow();
if (hasActiveWindow)
{
await ShowNewVersionWindowAsync();
}
else
{
ShowNewVersionWindowOnMainOpen = true;
notification.Notify(AppResources.NewVersionUpdateNotifyText_.Format(NewVersionInfo?.Version), NotificationType.NewVersion);
}
return hasActiveWindow;
}
}
@@ -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;
@@ -10,7 +10,9 @@ public sealed class NavigationService : INavigationService
{
public static NavigationService Instance { get; } = (NavigationService)Ioc.Get<INavigationService>();
public Control? PreviousPage { get; set; }
//public Type? PreviousPage { get; set; }
public Type? CurrnetPage => _frame?.CurrentSourcePageType;
public object? GetViewModelToPageContent(object viewModel, bool isCreateInstance = true)
{
@@ -54,7 +56,8 @@ public sealed class NavigationService : INavigationService
public void Navigate(Type? t, NavigationTransitionEffect effect = NavigationTransitionEffect.None, bool useCache = true)
{
_frame.ThrowIsNull();
if (_frame == null)
return;
var navOptions = new FrameNavigationOptions
{
+111 -43
View File
@@ -1,12 +1,16 @@
using Avalonia.Controls;
using BD.SteamClient.Services;
using BD.WTTS.Client.Resources;
using BD.WTTS.Converters;
using System.Collections.Specialized;
using static BD.WTTS.Services.INotificationService;
namespace BD.WTTS.UI;
partial class App
{
public Dictionary<string, TrayMenuItem> TrayMenus { get; } = new Dictionary<string, TrayMenuItem>();
public TrayIcons TrayIcons { get; init; } = new TrayIcons();
readonly Dictionary<string, ICommand> trayIconMenus = new();
@@ -32,51 +36,11 @@ partial class App
ToolTipText = AssemblyInfo.Trademark,
Command = ReactiveCommand.Create(RestoreMainWindow),
//[!TrayIcon.IsVisibleProperty] = new Binding { Path = "Value", Source = GeneralSettings.IsEnableTrayIcon, Mode = BindingMode.OneWay },
Menu = new NativeMenu
{
//new NativeMenuItem
//{
// Header = "Test",
//},
//new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new MultiBinding {
Bindings = new List<Avalonia.Data.IBinding>()
{
new Binding { Path = "IsRunningSteamProcess", Source = SteamConnectService.Current, Mode = BindingMode.OneWay },
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")!,
},
Command = ReactiveCommand.Create(() =>
{
if(ISteamService.Instance.IsRunningSteamProcess)
{
ISteamService.Instance.TryKillSteamProcess();
}
else
{
ISteamService.Instance.StartSteamWithParameter();
}
})
},
new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.OpenMainWindow", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(RestoreMainWindow)
},
new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.Exit", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(() => { Shutdown(); })
}
}
Menu = new NativeMenu(),
});
UpdateMenuItems();
//IViewModelManager.Instance.InitTaskBarWindowViewModel();
//NotifyIconHelper.Init(this,
// notifyIconClick: (_, _) => RestoreMainWindow());
@@ -102,4 +66,108 @@ partial class App
}
}
public void UpdateMenuItems()
{
MainThread2.BeginInvokeOnMainThread(() =>
{
var menus = TrayIcons.FirstOrDefault()?.Menu;
if (menus != null)
{
menus.Items.Clear();
var defaultTrayMenus = new List<NativeMenuItemBase>()
{
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new MultiBinding {
Bindings = new List<Avalonia.Data.IBinding>()
{
new Binding { Path = "IsRunningSteamProcess", Source = SteamConnectService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
},
Converter = (VisibleStringConverter)App.Instance.FindResource(nameof(VisibleStringConverter))!,
},
Command = ReactiveCommand.Create(async () =>
{
if(ISteamService.Instance.IsRunningSteamProcess)
{
await ISteamService.Instance.TryKillSteamProcess();
}
else
{
ISteamService.Instance.StartSteamWithParameter();
}
}),
},
//new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.OpenMainWindow", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(RestoreMainWindow),
},
new NativeMenuItemSeparator(),
new NativeMenuItem
{
[!NativeMenuItem.HeaderProperty] = new Binding { Path = "Res.Exit", Source = ResourceService.Current, Mode = BindingMode.OneWay },
Command = ReactiveCommand.Create(() => { Shutdown(); })
},
};
if (TrayMenus.Any_Nullable())
{
foreach (var item in TrayMenus)
{
NativeMenu? subMenu = null;
if (item.Value.Items.Any_Nullable())
{
subMenu = new NativeMenu();
foreach (var sub in item.Value.Items)
subMenu.Add(new NativeMenuItem
{
Header = sub.Name,
Command = sub.Command,
CommandParameter = sub.CommandParameter,
});
menus.Add(new NativeMenuItem
{
Header = item.Value.Name,
Menu = subMenu,
});
}
else
{
menus.Add(new NativeMenuItem
{
Header = item.Value.Name,
Command = item.Value.Command,
CommandParameter = item.Value.CommandParameter,
});
}
}
menus.Add(new NativeMenuItemSeparator());
}
foreach (var item in defaultTrayMenus)
{
menus.Add(item);
}
}
});
}
public void UpdateMenuItems(string menuKey, TrayMenuItem trayMenuItem)
{
if (TrayMenus != null)
{
if (TrayMenus.ContainsKey(menuKey))
TrayMenus[menuKey] = trayMenuItem;
else
TrayMenus.Add(menuKey, trayMenuItem);
UpdateMenuItems();
}
}
}
+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" />
+15 -2
View File
@@ -44,6 +44,9 @@ public sealed partial class App : Application
AvaloniaXamlLoader.Load(this);
#if WINDOWS || LINUX || MACOS
if (GeneralSettings.MinimizeOnStartup.Value)
Startup.Instance.IsMinimize = true;
if (Startup.Instance.IsSteamRun)
{
try
@@ -90,8 +93,18 @@ public sealed partial class App : Application
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
MainWindow = InitializeMainWindow?.Invoke(this);
desktop.MainWindow = MainWindow ??= new MainWindow();
MainWindow = InitializeMainWindow?.Invoke(this) ?? new MainWindow();
desktop.MainWindow =
#if !UI_DEMO
Startup.Instance.IsMinimize && MainWindow is MainWindow ? null :
#endif
MainWindow;
if (Startup.Instance.IsMinimize && MainWindow is AppWindow appWindow)
{
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}" />
@@ -73,15 +73,19 @@ public sealed partial class MainView : ReactiveUserControl<MainWindowViewModel>
NavView.SelectedItem = null;
foreach (var nvi in NavView.MenuItemsSource)
{
SetSelectedItem(nvi);
}
var menuItemsSource = NavView.MenuItemsSource;
if (menuItemsSource != null)
foreach (var nvi in menuItemsSource)
{
SetSelectedItem(nvi);
}
foreach (var nvi in NavView.FooterMenuItemsSource)
{
SetSelectedItem(nvi);
}
var footerMenuItemsSource = NavView.FooterMenuItemsSource;
if (footerMenuItemsSource != null)
foreach (var nvi in footerMenuItemsSource)
{
SetSelectedItem(nvi);
}
if (FrameView.BackStackDepth > 0 && !NavView.IsBackButtonVisible)
{
@@ -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)
{
@@ -13,6 +13,7 @@
mc:Ignorable="d">
<Panel>
<TextBlock
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding Path=!!!Notices.Count, Mode=OneWay, Source={x:Static s:NoticeService.Current}}"
@@ -46,11 +46,24 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
}
if (AppSplashScreen.IsInitialized)
{
if (NavigationService.Instance.CurrnetPage == null)
{
INavigationService.Instance.Navigate(typeof(HomePage));
}
Task2.InBackground(async () =>
{
IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
await AdvertiseService.Current.RefrshAdvertiseAsync();
NoticeService.Current.GetNewsAsync();
});
try // 在主窗口显示时调用此函数检查是否需要显示新版本通知窗口
{
Ioc.Get_Nullable<IAppUpdateService>()?.OnMainOpenTryShowNewVersionWindow();
}
catch
{
}
}
}
}
@@ -78,134 +91,138 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
{
return Task.Run(
async () =>
{
{
if (!AppSplashScreen.IsInitialized)
{
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Start();
WatchTrace.Start();
#endif
var s = Instance;
if (s.IsMainProcess)
{
VersionTracking2.Track();
var s = Instance;
if (s.IsMainProcess)
{
VersionTracking2.Track();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("VersionTracking2.Track");
WatchTrace.Record("VersionTracking2.Track");
#endif
Migrations.Up();
Migrations.Up();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("Migrations.Up");
WatchTrace.Record("Migrations.Up");
#endif
// 仅在主进程中启动 IPC 服务端
IPCMainProcessService.Instance.Run();
// 仅在主进程中启动 IPC 服务端
IPCMainProcessService.Instance.Run();
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("IPC.StartServer");
WatchTrace.Record("IPC.StartServer");
#endif
}
}
LiveCharts.Configure(config =>
{
config
// registers SkiaSharp as the library backend
// REQUIRED unless you build your own
.AddSkiaSharp();
// adds the default supported types
// OPTIONAL but highly recommend
//.AddDefaultMappers()
LiveCharts.Configure(config =>
{
config
// registers SkiaSharp as the library backend
// REQUIRED unless you build your own
.AddSkiaSharp();
// adds the default supported types
// OPTIONAL but highly recommend
//.AddDefaultMappers()
// select a theme, default is Light
// OPTIONAL
//.AddDarkTheme()
// select a theme, default is Light
// OPTIONAL
//.AddDarkTheme()
// In case you need a non-Latin based font, you must register a typeface for SkiaSharp
config.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('汉')); // <- Chinese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('أ')) // <- Arabic // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('あ')) // <- Japanese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('헬')) // <- Korean // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('Ж')) // <- Russian // mark
// In case you need a non-Latin based font, you must register a typeface for SkiaSharp
config.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('汉')); // <- Chinese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('أ')) // <- Arabic // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('あ')) // <- Japanese // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('헬')) // <- Korean // mark
//.HasGlobalSKTypeface(SKFontManager.Default.MatchCharacter('Ж')) // <- Russian // mark
if (App.Instance.Theme != AppTheme.FollowingSystem)
{
if (App.Instance.Theme == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
else
{
var dps = IPlatformService.Instance;
var isLightOrDarkTheme = dps.IsLightOrDarkTheme;
if (isLightOrDarkTheme.HasValue)
{
var mThemeFS = IApplication.GetAppThemeByIsLightOrDarkTheme(isLightOrDarkTheme.Value);
if (mThemeFS == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
}
});
if (App.Instance.Theme != AppTheme.FollowingSystem)
{
if (App.Instance.Theme == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
else
{
var dps = IPlatformService.Instance;
var isLightOrDarkTheme = dps.IsLightOrDarkTheme;
if (isLightOrDarkTheme.HasValue)
{
var mThemeFS = IApplication.GetAppThemeByIsLightOrDarkTheme(isLightOrDarkTheme.Value);
if (mThemeFS == AppTheme.Light)
config.AddLightTheme();
else
config.AddDarkTheme();
}
}
});
AdvertiseService.Current.InitAdvertise();
NoticeService.Current.GetNewsAsync();
AdvertiseService.Current.InitAdvertise();
NoticeService.Current.GetNewsAsync();
var mainWindow = App.Instance.MainWindow;
mainWindow.ThrowIsNull();
var mainWindow = App.Instance.MainWindow;
mainWindow.ThrowIsNull();
#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>(
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>(
#if DEBUG
new MenuTabItemViewModel("Debug")
{
PageType = typeof(DebugPage),
IsResourceGet = false,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
},
#endif
new MenuTabItemViewModel("Plugin_Store")
{
PageType = null,
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
},
new MenuTabItemViewModel("Settings")
{
PageType = typeof(SettingsPage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/settings.ico",
}));
new MenuTabItemViewModel("Debug")
{
PageType = typeof(DebugPage),
IsResourceGet = false,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/bug.ico",
},
#endif
new MenuTabItemViewModel("Plugin_Store")
{
PageType = null,
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
},
new MenuTabItemViewModel("Settings")
{
PageType = typeof(SettingsPage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/settings.ico",
}));
#pragma warning restore SA1114 // Parameter list should follow declaration
IViewModelManager.Instance.MainWindow.ThrowIsNull();
IViewModelManager.Instance.MainWindow.ThrowIsNull();
await Dispatcher.UIThread.InvokeAsync(() =>
{
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
s.InitSettingSubscribe();
await Dispatcher.UIThread.InvokeAsync(() =>
{
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
s.InitSettingSubscribe();
INavigationService.Instance.Navigate(typeof(HomePage));
});
INavigationService.Instance.Navigate(typeof(HomePage));
});
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("InitMainWindowViewModel");
WatchTrace.Record("InitMainWindowViewModel");
#endif
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Stop();
WatchTrace.Stop();
#endif
s.OnStartup();
await IViewModelManager.Instance.MainWindow.Initialize();
s.OnStartup();
await IViewModelManager.Instance.MainWindow.Initialize();
App.Instance.CompositeDisposable.Add(IViewModelManager.Instance.MainWindow);
App.Instance.CompositeDisposable.Add(SteamConnectService.Current.Dispose);
App.Instance.CompositeDisposable.Add(IViewModelManager.Instance.MainWindow);
App.Instance.CompositeDisposable.Add(SteamConnectService.Current.Dispose);
IsInitialized = true;
}, cancellationToken: token);
IsInitialized = true;
}
}, cancellationToken: token);
}
}
@@ -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);
}
@@ -101,6 +101,9 @@
<PackageReference Include="Aigio.Any.AppCenter" />
<PackageReference Include="Aigio.Any.AppCenter.Analytics" />
<PackageReference Include="Aigio.Any.AppCenter.Crashes" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Runtime.Caching" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
@@ -6,8 +6,8 @@ public static partial class ServiceCollectionExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IServiceCollection AddDnsAnalysisService(this IServiceCollection services)
{
//services.AddSingleton<IDnsAnalysisService, DnsDohAnalysisService>();
services.AddSingleton<IDnsAnalysisService, DnsAnalysisServiceImpl>();
services.AddSingleton<DnsDohAnalysisService>();
services.AddSingleton<DnsAnalysisServiceImpl>();
return services;
}
}
@@ -89,6 +89,7 @@ static void ConfigureServices(IServiceCollection services)
services.AddHttpClient();
services.AddCommonHttpClientFactory();
services.AddSingleton<IHttpPlatformHelperService, HttpPlatformHelperConsoleService>();
services.AddDnsAnalysisService();
// 添加反向代理服务(子进程实现)
@@ -147,4 +148,11 @@ sealed class RepositorySecureStorage : Repository<KeyValuePair, string>, ISecure
var item = await FirstOrDefaultAsync(x => x.Id == key);
return item != null;
}
}
sealed class HttpPlatformHelperConsoleService : HttpPlatformHelperService
{
new const string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.51";
public override string UserAgent => DefaultUserAgent;
}
@@ -23,6 +23,20 @@ sealed partial class CertificateManagerImpl : ICertificateManager
public X509CertificatePackable RootCertificatePackable { get; set; }
readonly object lock_RootCertificatePackable = new();
byte[]? ICertificateManager.RootCertificatePackable
{
get
{
lock (lock_RootCertificatePackable)
{
RootCertificate ??= LoadRootCertificate();
}
return RootCertificate == default ? default : Serializable.SMP2(RootCertificatePackable);
}
}
/// <inheritdoc cref="ICertificateManager.PfxPassword"/>
public byte[]? PfxPassword { get; set; }
@@ -74,6 +88,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
}
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
void SharedTrustRootCertificate()
{
if (RootCertificate == null)
@@ -180,7 +195,12 @@ sealed partial class CertificateManagerImpl : ICertificateManager
{
if (!File.Exists(filePath))
{
if (!GenerateCertificateUnlock(filePath)) return null;
if (!GenerateCertificateUnlock(filePath))
return null;
}
else if (RootCertificate == null)
{
RootCertificate = LoadRootCertificate();
}
return filePath;
}
@@ -222,44 +242,66 @@ sealed partial class CertificateManagerImpl : ICertificateManager
}
}
public void TrustRootCertificate()
bool? ICertificateManager.GenerateCertificate()
{
try
{
if (OperatingSystem.IsWindows())
{
try
{
SharedTrustRootCertificate();
}
#if DEBUG
catch (Exception e)
{
Log.Error(TAG, e, "SharedTrustRootCertificate Error");
}
#else
catch { }
#endif
}
else if (OperatingSystem.IsMacOS())
{
TrustRootCertificateMacOS();
}
else if (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid())
{
TrustRootCertificateLinux();
}
}
#if DEBUG
catch (Exception e)
{
e.LogAndShowT(TAG, msg: "PlatformTrustRootCertificateGuide Error");
}
#else
catch { }
#endif
return GenerateCertificate(null);
}
// public void TrustRootCertificate()
// {
// try
// {
// if (OperatingSystem.IsWindows())
// {
// try
// {
// SharedTrustRootCertificate();
// }
//#if DEBUG
// catch (Exception e)
// {
// Log.Error(TAG, e, "SharedTrustRootCertificate Error");
// }
//#else
// catch { }
//#endif
// }
// else if (OperatingSystem.IsMacOS())
// {
// TrustRootCertificateMacOS();
// }
// else if (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid())
// {
// TrustRootCertificateLinux();
// }
// }
//#if DEBUG
// catch (Exception e)
// {
// e.LogAndShowT(TAG, msg: "PlatformTrustRootCertificateGuide Error");
// }
//#else
// catch { }
//#endif
// }
public void TrustRootCertificate()
{
if (RootCertificate == null)
{
GenerateCertificate();
}
if (RootCertificate == null)
{
throw new ApplicationException(
"Could not install certificate as it is null or empty.");
}
ICertificateManager.Constants.TrustRootCertificate(GetCerFilePathGeneratedWhenNoFileExists, platformService, RootCertificate);
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
[SupportedOSPlatform("macOS")]
void TrustRootCertificateMacOS()
{
@@ -271,6 +313,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
TrustRootCertificateMacOS();
}
[Obsolete("use ICertificateManager.Constants.TrustRootCertificate")]
[SupportedOSPlatform("Linux")]
void TrustRootCertificateLinux()
{
@@ -390,12 +433,13 @@ sealed partial class CertificateManagerImpl : ICertificateManager
{
get
{
if (RootCertificate == null)
if (GetCerFilePathGeneratedWhenNoFileExists() == null) return false;
return IsCertificateInstalled(RootCertificatePackable);
var result = ICertificateManager.Constants.IsRootCertificateInstalled(this, platformService, RootCertificatePackable);
return result;
}
}
bool? ICertificateManager.IsRootCertificateInstalled2 => IsRootCertificateInstalled;
/// <summary>
/// 检查证书是否已安装并信任
/// </summary>
@@ -403,21 +447,7 @@ sealed partial class CertificateManagerImpl : ICertificateManager
/// <returns></returns>
bool IsCertificateInstalled(X509CertificatePackable packable)
{
X509Certificate2? certificate2 = packable;
if (certificate2 == null)
return false;
if (certificate2.NotAfter <= DateTime.Now)
return false;
if (OperatingSystem.IsAndroid() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
return platformService.IsCertificateInstalled(packable);
}
else
{
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
return store.Certificates.Contains(certificate2);
}
var result = ICertificateManager.Constants.IsCertificateInstalled(platformService, packable);
return result;
}
}
@@ -14,10 +14,18 @@ sealed class LazyCertificateManager : ICertificateManager
public byte[]? PfxPassword => impl().PfxPassword;
public X509CertificatePackable RootCertificatePackable { get => impl().RootCertificatePackable; set => impl().RootCertificatePackable = value; }
public byte[]? RootCertificatePackable { get => impl().RootCertificatePackable; }
[Obsolete]
public bool IsRootCertificateInstalled => impl().IsRootCertificateInstalled;
public bool? IsRootCertificateInstalled2 => impl().IsRootCertificateInstalled2;
public bool? GenerateCertificate()
{
return impl().GenerateCertificate();
}
public string? GetCerFilePathGeneratedWhenNoFileExists()
{
return impl().GetCerFilePathGeneratedWhenNoFileExists();
@@ -20,7 +20,7 @@ sealed class LifetimeHttpHandlerCleaner
/// <summary>
/// 获取或设置清理的时间间隔
/// 默认10s
/// 默认 10s
/// </summary>
public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d);
@@ -14,7 +14,7 @@ sealed class RequestContext
public bool IsHttps { get; }
/// <summary>
/// 获取或设置Sni值
/// 获取或设置 Sni 值
/// </summary>
public TlsSniPattern TlsSniValue { get; }
@@ -24,11 +24,8 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
/// <returns></returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var uri = request.RequestUri;
if (uri == null)
{
var uri = request.RequestUri ??
throw new ApplicationException("The requested URI must be specified.");
}
// 请求上下文信息
var isHttps = uri.Scheme == Uri.UriSchemeHttps;
@@ -52,7 +49,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
/// 创建转发代理的 <see cref="HttpMessageHandler"/>
/// </summary>
/// <returns></returns>
SocketsHttpHandler CreateSocketsHttpHandler() => new()
HttpHandlerType CreateSocketsHttpHandler() => new()
{
Proxy = HttpNoProxy.Instance,
UseProxy = false,
@@ -20,12 +20,12 @@ sealed class CertService
/// <summary>
/// 获取 CER 证书文件路径
/// </summary>
public string CaCerFilePath => ReverseProxyService.CertificateManager.CerFilePath;
public string CaCerFilePath => ((ICertificateManager)ReverseProxyService.CertificateManager).CerFilePath;
/// <summary>
/// 获取 PFX 证书文件路径
/// </summary>
public string CaPfxFilePath => ReverseProxyService.CertificateManager.PfxFilePath;
public string CaPfxFilePath => ((ICertificateManager)ReverseProxyService.CertificateManager).PfxFilePath;
public CertService(
IMemoryCache serverCertCache,
@@ -69,10 +69,7 @@ sealed class CertService
/// <returns></returns>
public X509Certificate2? GetOrCreateServerCert(string? domain)
{
if (this.caCert == null)
{
this.caCert = new X509Certificate2(fileName: this.CaPfxFilePath, password: default(string));
}
caCert ??= new X509Certificate2(fileName: CaPfxFilePath, password: default(string));
var key = $"{nameof(CertService)}:{domain}";
return serverCertCache.GetOrCreate(key, GetOrCreateCert);
@@ -86,7 +83,7 @@ sealed class CertService
entry.SetAbsoluteExpiration(notAfter);
var subjectName = new X500DistinguishedName($"CN={domain}");
using var serverCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, GetDomains(), notBefore, notAfter);
using var serverCert = CertGenerator.CreateEndCertificate(caCert, subjectName, GetDomains(), notBefore, notAfter);
var serverCertPfx = serverCert.Export(X509ContentType.Pfx);
// 将生成的证书导出后重新创建一个
return new X509Certificate2(serverCertPfx);
@@ -96,14 +96,54 @@ sealed class HttpLocalRequestMiddleware
{
try
{
if (string.IsNullOrEmpty(context.Request.QueryString.Value))
var url = context.Request.QueryString.Value;
if (string.IsNullOrEmpty(url))
return;
url = HttpUtility.UrlDecode(url.Replace("?request=", ""));
Uri requestUri;
try
{
requestUri = new Uri(url);
}
catch
{
return;
}
var method = context.Request.Method;
var methodObj = method switch
{
"GET" => HttpMethod.Get,
"PUT" => HttpMethod.Put,
"POST" => HttpMethod.Post,
"DELETE" => HttpMethod.Delete,
"HEAD" => HttpMethod.Head,
"OPTIONS" => HttpMethod.Options,
"TRACE" => HttpMethod.Trace,
"PATCH" => HttpMethod.Patch,
"CONNECT" => HttpMethod.Connect,
_ => TryParse(method),
};
if (methodObj == null)
return;
var url = HttpUtility.UrlDecode(context.Request.QueryString.Value.Replace("?request=", ""));
string? cookie = context.Request.Headers["cookie-steamTool"];
if (string.IsNullOrEmpty(cookie))
static HttpMethod? TryParse(string method)
{
try
{
return new HttpMethod(method.ToUpperInvariant());
}
catch
{
}
return null;
}
StringValues cookie = context.Request.Headers["cookie-steamTool"];
if (StringValues.IsNullOrEmpty(cookie))
cookie = context.Request.Headers["Cookie"];
string? referer = context.Request.Headers["Referer-steamTool"];
StringValues referer = context.Request.Headers["Referer-steamTool"];
context.Response.Headers.AccessControlAllowOrigin = context.Request.Headers.Origin.Count == 0 ? "*" : context.Request.Headers.Origin;
context.Response.Headers.AccessControlAllowHeaders = "*";
@@ -119,28 +159,39 @@ sealed class HttpLocalRequestMiddleware
async Task SendAsync()
{
var method = context.Request.Method;
var hasContent = method != HttpMethods.Get && context.Request.ContentLength > 0;
var hasReqContent = !HttpMethods.IsGet(method) && context.Request.ContentLength > 0;
var req = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = new(method),
Content = hasContent ? new StreamContent(context.Request.Body) : null,
RequestUri = requestUri,
Method = methodObj,
Content = hasReqContent ? new StreamContent(context.Request.Body) : null,
};
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
if (cookie != null)
if (!StringValues.IsNullOrEmpty(cookie))
{
CookieHttpClient.CookieContainer.Add(new Cookie
{
CommentUri = req.RequestUri,
Domain = req.RequestUri.Host,
CommentUri = requestUri,
Domain = requestUri.Host,
Value = cookie,
});
// req.Headers.Add("Cookie", cookie);
}
if (referer != null) req.Headers.Referrer = new Uri(referer);
if (hasContent)
if (!StringValues.IsNullOrEmpty(referer))
{
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
try
{
var refererUri = new Uri(referer.ToString());
req.Headers.Referrer = refererUri;
}
catch
{
}
}
if (hasReqContent)
{
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);
@@ -157,12 +208,12 @@ sealed class HttpLocalRequestMiddleware
}
}
if (context.Request.Method == HttpMethods.Get)
if (HttpMethods.IsGet(context.Request.Method))
{
await SendAsync();
return;
}
else if (context.Request.Method == HttpMethods.Post)
else if (HttpMethods.IsPost(context.Request.Method))
{
if (context.Request.ContentLength > 0)
{
@@ -8,9 +8,7 @@ namespace BD.WTTS.Services.Implementation;
/// </summary>
sealed class HttpProxyMiddleware
{
private readonly HttpParser<HttpRequestHandler> httpParser = new();
private static readonly byte[] http200 = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");
private static readonly byte[] http400 = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\n\r\n");
readonly HttpParser<HttpRequestHandler> httpParser = new();
/// <summary>
/// 执行中间件
@@ -39,6 +37,7 @@ sealed class HttpProxyMiddleware
if (request.ProxyProtocol == ProxyProtocol.TunnelProxy)
{
input.AdvanceTo(consumed);
ReadOnlyMemory<byte> http200 = "HTTP/1.1 200 Connection Established\r\n\r\n"u8.ToArray();
await output.WriteAsync(http200, context.ConnectionClosed);
}
else
@@ -63,6 +62,7 @@ sealed class HttpProxyMiddleware
}
catch (Exception)
{
ReadOnlyMemory<byte> http400 = "HTTP/1.1 400 Bad Request\r\n\r\n"u8.ToArray();
await output.WriteAsync(http400, context.ConnectionClosed);
break;
}
@@ -120,7 +120,7 @@ sealed class HttpProxyMiddleware
void IHttpRequestLineHandler.OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
{
method = versionAndMethod.Method;
var host = Encoding.ASCII.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
var host = Encoding.UTF8.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
if (versionAndMethod.Method == AspNetCoreHttpMethod.Connect)
{
ProxyHost = HostString.FromUriComponent(host);
@@ -87,7 +87,12 @@ sealed partial class HttpReverseProxyMiddleware
context.Request.Headers.UserAgent = domainConfig.UserAgent.Replace("${origin}", context.Request.Headers.UserAgent, StringComparison.OrdinalIgnoreCase);
}
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);
var forwarderRequestConfig = new ForwarderRequestConfig()
{
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);
if (error != ForwarderError.None)
{
@@ -282,8 +287,8 @@ sealed partial class HttpReverseProxyMiddleware
// var html_start_string = encoding.GetString(html_start.Span);
//#endif
await bodyCoreWriter.WriteAsync(html_start);
var script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
var script_xml_end = "\"></script>"u8.ToArray();
ReadOnlyMemory<byte> script_xml_start = "<script type=\"text/javascript\" src=\"https://local.steampp.net/"u8.ToArray();
ReadOnlyMemory<byte> script_xml_end = "\"></script>"u8.ToArray();
foreach (var script in scripts)
{
await bodyCoreWriter.WriteAsync(script_xml_start);
@@ -9,9 +9,9 @@ namespace BD.WTTS.Services.Implementation;
/// </summary>
abstract class TcpReverseProxyHandler : ConnectionHandler
{
private readonly IDomainResolver domainResolver;
private readonly DnsEndPoint endPoint;
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
readonly IDomainResolver domainResolver;
readonly DnsEndPoint endPoint;
readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
public TcpReverseProxyHandler(IDomainResolver domainResolver, DnsEndPoint endPoint)
{
@@ -5,9 +5,9 @@ using static BD.WTTS.Services.IDnsAnalysisService;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
sealed class DnsAnalysisServiceImpl
{
static readonly LookupClient lookupClient = new();
readonly LookupClient lookupClient = new();
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
{
@@ -24,7 +24,7 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
return 0;
}
public static IEnumerable<ARecord>? GetPingHostNameData(string url)
public IEnumerable<ARecord>? GetPingHostNameData(string url)
{
if (!string.IsNullOrEmpty(url))
{
@@ -119,11 +119,18 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
{
Retries = 1,
Timeout = TimeSpan.FromSeconds(1),
UseCache = false
UseCache = false,
};
var client = new LookupClient(options);
var response = await client.QueryServerAsync(new[] { IPAddress.Parse(PrimaryDNS_IPV6_Ali) }, IPV6_TESTDOMAIN, QueryType.AAAA, QueryClass.IN);
if (response.Answers.AaaaRecords().Any(s => s.Address.Equals(IPAddress.Parse(IPV6_TESTDOMAIN_SUCCESS))))
var response = await client.QueryServerAsync(
new[] {
IPAddress.Parse(PrimaryDNS_IPV6_Ali),
},
IPV6_TESTDOMAIN,
QueryType.AAAA,
QueryClass.IN);
if (response.Answers.AaaaRecords().Any(s =>
s.Address.Equals(IPAddress.Parse(IPV6_TESTDOMAIN_SUCCESS))))
{
return true;
}
@@ -133,9 +140,4 @@ sealed class DnsAnalysisServiceImpl : IDnsAnalysisService
}
return false;
}
public Task<IPAddress?> GetHostIpv6AddresAsync()
{
return Task.FromResult<IPAddress?>(null);
}
}
@@ -0,0 +1,61 @@
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
sealed class DnsAnalysisServiceSwitchImpl : IDnsAnalysisService
{
readonly DnsAnalysisServiceImpl dnsAnalysisServiceImpl;
readonly DnsDohAnalysisService dnsDohAnalysisService;
readonly IReverseProxySettings settings;
public DnsAnalysisServiceSwitchImpl(
IReverseProxySettings settings,
DnsAnalysisServiceImpl dnsAnalysisServiceImpl,
DnsDohAnalysisService dnsDohAnalysisService)
{
this.settings = settings;
this.dnsAnalysisServiceImpl = dnsAnalysisServiceImpl;
this.dnsDohAnalysisService = dnsDohAnalysisService;
}
public async Task<int> AnalysisHostnameTimeAsync(string url, CancellationToken cancellationToken = default)
{
int r;
if (settings.UseDoh)
{
r = await dnsDohAnalysisService.DohAnalysisHostnameTimeAsync(
settings.CustomDohAddres, url, cancellationToken);
}
else
{
r = await dnsAnalysisServiceImpl.AnalysisHostnameTimeAsync(url, cancellationToken);
}
return r;
}
public IAsyncEnumerable<IPAddress> AnalysisDomainIpAsync(string hostNameOrAddress, IPAddress[]? dnsServers, bool isIPv6, CancellationToken cancellationToken = default)
{
IAsyncEnumerable<IPAddress> r;
if (settings.UseDoh)
{
r = dnsDohAnalysisService.DohAnalysisDomainIpAsync(settings.CustomDohAddres, hostNameOrAddress, isIPv6, true, cancellationToken);
}
else
{
r = dnsAnalysisServiceImpl.AnalysisDomainIpAsync(hostNameOrAddress, dnsServers, isIPv6, cancellationToken);
}
return r;
}
public async Task<string?> GetHostByIPAddressAsync(IPAddress ip)
{
var r = await dnsAnalysisServiceImpl.GetHostByIPAddressAsync(ip);
return r;
}
public async Task<bool> GetIsIpv6SupportAsync()
{
var r = await dnsAnalysisServiceImpl.GetIsIpv6SupportAsync();
return r;
}
}

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