diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.cs b/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.cs index 8e67da5e5d0..2dd74a1e1f6 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.cs @@ -1,19 +1,17 @@ -using AppResources = BD.WTTS.Client.Resources.Strings; - using BD.WTTS.UI.Views.Pages; -using STUN.StunResult; -using STUN.Enums; +using AppResources = BD.WTTS.Client.Resources.Strings; namespace BD.WTTS.UI.ViewModels; public sealed partial class AcceleratorPageViewModel { + public NetworkCheckControlViewModel NetworkCheckControlViewModel { get; } = new(); + DateTime _initializeTime; readonly IHostsFileService? hostsFileService; readonly IPlatformService platformService = IPlatformService.Instance; readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance; readonly ICertificateManager certificateManager = ICertificateManager.Constants.Instance; - readonly INetworkTestService networkTestService = INetworkTestService.Instance; public AcceleratorPageViewModel() { @@ -71,82 +69,6 @@ public AcceleratorPageViewModel() GameAcceleratorService.Current.LoadGames(); }); - SelectedSTUNAddress = STUNAddress[0]; - - NATCheckCommand = ReactiveCommand.CreateFromTask(async () => - { - var natCheckResult = await networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown }; - var (netSucc, _) = await networkTestService.TestOpenUrlAsync("https://www.baidu.com"); - - var natStatus = natCheckResult.NatType switch - { - NatType.OpenInternet or NatType.FullCone => NatTypeSimple.Open, - NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => NatTypeSimple.Moderate, - NatType.Symmetric or NatType.UdpBlocked => NatTypeSimple.Strict, - NatType.Unknown or NatType.UnsupportedServer or _ => NatTypeSimple.Unknown, - }; - - PublicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown"; - LocalEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown"; - - return (natStatus, netSucc); - }); - NATCheckCommand - .IsExecuting - .ToPropertyEx(this, x => x.IsNATChecking); - - DNSCheckCommand = ReactiveCommand.CreateFromTask(async () => - { - var testDomain = DomainPendingTest == string.Empty ? "store.steampowered.com" : DomainPendingTest; - try - { - long delayMs; - IPAddress[] address; - if (ProxySettings.UseDoh) - { - var configDoh = ProxySettings.CustomDohAddres2.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty; - (delayMs, address) = await networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh); - } - else - { - var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty; - (delayMs, address) = await networkTestService.TestDNSAsync(testDomain, configDns, 53); - } - if (address.Length == 0) - throw new Exception("Parsing failed. Return empty ip address."); - - DNSTestDelay = delayMs + "ms "; - DNSTestResult = string.Empty + address.FirstOrDefault(); - } - catch (Exception ex) - { - Log.Error(nameof(AcceleratorPageViewModel), ex.ToString()); - DNSTestDelay = string.Empty; - DNSTestResult = "error"; - } - }); - DNSCheckCommand - .IsExecuting - .ToPropertyEx(this, x => x.IsDNSChecking); - - IPv6CheckCommand = ReactiveCommand.CreateFromTask(async () => - { - var result = await IMicroServiceClient.Instance.Accelerate.GetMyIP(ipV6: true); - if (result.IsSuccess) - { - IsSupportIPv6 = true; - IPv6Address = result.Content ?? string.Empty; - } - else - { - IsSupportIPv6 = false; - IPv6Address = string.Empty; - } - }); - IPv6CheckCommand - .IsExecuting - .ToPropertyEx(this, x => x.IsIPv6Checking); - ProxySettingsCommand = ReactiveCommand.Create(() => { var vm = new ProxySettingsWindowViewModel(); diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.props.cs b/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.props.cs index 7ae8707dd4a..d0c4bf29fa9 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.props.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/AcceleratorPageViewModel.props.cs @@ -4,66 +4,11 @@ namespace BD.WTTS.UI.ViewModels; public sealed partial class AcceleratorPageViewModel : TabItemViewModel { - public enum NatTypeSimple - { - Unknown, - Open, - Moderate, - Strict, - } - public override string Name => Strings.Welcome; - [Reactive] - public string SelectedSTUNAddress { get; set; } - - public string[] STUNAddress { get; } = - [ - "stun.syncthing.net", - "stun.hot-chilli.net", - "stun.fitauto.ru", - "stun.miwifi.com", - ]; - - [ObservableAsProperty] - public bool IsNATChecking { get; } - - [ObservableAsProperty] - public bool IsDNSChecking { get; } - - [ObservableAsProperty] - public bool IsIPv6Checking { get; } - - [Reactive] - public string DomainPendingTest { get; set; } = string.Empty; - [Reactive] public ReadOnlyCollection? EnableProxyDomainGroupVMs { get; set; } - [Reactive] - public string LocalEndPoint { get; set; } = string.Empty; - - [Reactive] - public string PublicEndPoint { get; set; } = string.Empty; - - [Reactive] - public string DNSTestDelay { get; set; } = string.Empty; - - [Reactive] - public string DNSTestResult { get; set; } = string.Empty; - - [Reactive] - public string IPv6Address { get; set; } = string.Empty; - - [Reactive] - public bool IsSupportIPv6 { get; set; } - - public ReactiveCommand NATCheckCommand { get; } - - public ReactiveCommand DNSCheckCommand { get; } - - public ReactiveCommand IPv6CheckCommand { get; } - public ICommand StartProxyCommand { get; } public ICommand RefreshCommand { get; } diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/NetworkCheckControlViewModel.cs b/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/NetworkCheckControlViewModel.cs new file mode 100644 index 00000000000..ab7c574cb6a --- /dev/null +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/ViewModels/NetworkCheckControlViewModel.cs @@ -0,0 +1,183 @@ +using STUN.Enums; +using STUN.StunResult; +using System.Reactive; + +namespace BD.WTTS.UI.ViewModels; + +public class NetworkCheckControlViewModel : ViewModelBase +{ + private readonly INetworkTestService _networkTestService = INetworkTestService.Instance; + + public record NATFetchResult(string PublicEndPoint, string LocalEndPoint, string NATLevel, string NATTypeTip, bool PingResult); + + [Reactive] + public string SelectedSTUNAddress { get; set; } + + public string[] STUNAddress { get; } = + [ + "stun.syncthing.net", + "stun.hot-chilli.net", + "stun.fitauto.ru", + "stun.miwifi.com", + ]; + + [ObservableAsProperty] + public string NATLevel { get; } = string.Empty; + + [ObservableAsProperty] + public string NATTypeTip { get; } = string.Empty; + + [ObservableAsProperty] + public string LocalEndPoint { get; } = string.Empty; + + [ObservableAsProperty] + public string PublicEndPoint { get; } = string.Empty; + + [ObservableAsProperty] + public bool PingOkVisible { get; } + + [ObservableAsProperty] + public bool PingErrorVisible { get; } + + [ObservableAsProperty] + public bool IsNATChecking { get; } + + [ObservableAsProperty] + public bool IsDNSChecking { get; } + + [ObservableAsProperty] + public bool IsIPv6Checking { get; } + + [Reactive] + public string DomainPendingTest { get; set; } = string.Empty; + + [Reactive] + public string DNSTestDelay { get; set; } = string.Empty; + + [Reactive] + public string DNSTestResult { get; set; } = string.Empty; + + [Reactive] + public string IPv6Address { get; set; } = string.Empty; + + [Reactive] + public bool IsSupportIPv6 { get; set; } + + public ReactiveCommand NATCheckCommand { get; } + + public ReactiveCommand DNSCheckCommand { get; } + + public ReactiveCommand IPv6CheckCommand { get; } + + public NetworkCheckControlViewModel() + { + SelectedSTUNAddress = STUNAddress[0]; + + NATCheckCommand = ReactiveCommand.CreateFromTask(async () => + { + var natCheckResult = await _networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown }; + var (netSucc, _) = await _networkTestService.TestOpenUrlAsync("https://www.baidu.com"); + + var publicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown"; + var localEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown"; + + var (natLevel, natTypeTip) = natCheckResult.NatType switch + { + // Open + NatType.OpenInternet or NatType.FullCone => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"), + // Moderate + NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"), + // Strict + NatType.Symmetric or NatType.UdpBlocked => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"), + // Unknown + NatType.Unknown or NatType.UnsupportedServer or _ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"), + }; + + return new NATFetchResult(publicEndPoint, localEndPoint, natLevel, natTypeTip, netSucc); + }); + NATCheckCommand + .IsExecuting + .ToPropertyEx(this, x => x.IsNATChecking); + NATCheckCommand + .Select(x => x.PublicEndPoint) + .ToPropertyEx(this, x => x.PublicEndPoint); + NATCheckCommand + .Select(x => x.LocalEndPoint) + .ToPropertyEx(this, x => x.LocalEndPoint); + NATCheckCommand + .Select(x => x.NATLevel) + .Merge(Observable.Return("未知")) + .ToPropertyEx(this, x => x.NATLevel); + NATCheckCommand + .Select(x => x.NATTypeTip) + .Merge(Observable.Return("未知类型")) + .ToPropertyEx(this, x => x.NATTypeTip); + + var hidePingResultStream = NATCheckCommand + .IsExecuting + .Where(x => x == true) + .Select(x => false); + NATCheckCommand + .Select(x => x.PingResult == true) + .Merge(hidePingResultStream) + .ToPropertyEx(this, x => x.PingOkVisible); + NATCheckCommand + .Select(x => x.PingResult == false) + .Merge(hidePingResultStream) + .ToPropertyEx(this, x => x.PingErrorVisible); + + DNSCheckCommand = ReactiveCommand.CreateFromTask(async () => + { + var testDomain = DomainPendingTest == string.Empty ? "store.steampowered.com" : DomainPendingTest; + try + { + long delayMs; + IPAddress[] address; + if (ProxySettings.UseDoh) + { + var configDoh = ProxySettings.CustomDohAddres2.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty; + (delayMs, address) = await _networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh); + } + else + { + var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty; + (delayMs, address) = await _networkTestService.TestDNSAsync(testDomain, configDns, 53); + } + if (address.Length == 0) + throw new Exception("Parsing failed. Return empty ip address."); + + DNSTestDelay = delayMs + "ms "; + DNSTestResult = string.Empty + address.FirstOrDefault(); + } + catch (Exception ex) + { + Log.Error(nameof(AcceleratorPageViewModel), ex.ToString()); + DNSTestDelay = string.Empty; + DNSTestResult = "error"; + } + }); + DNSCheckCommand + .IsExecuting + .ToPropertyEx(this, x => x.IsDNSChecking); + + IPv6CheckCommand = ReactiveCommand.CreateFromTask(async () => + { + var result = await IMicroServiceClient.Instance.Accelerate.GetMyIP(ipV6: true); + if (result.IsSuccess) + { + IsSupportIPv6 = true; + IPv6Address = result.Content ?? string.Empty; + } + else + { + IsSupportIPv6 = false; + IPv6Address = string.Empty; + } + }); + IPv6CheckCommand + .IsExecuting + .ToPropertyEx(this, x => x.IsIPv6Checking); + + IPv6CheckCommand.Execute().Subscribe(); + } +} \ No newline at end of file diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/NetworkCheck.axaml b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/NetworkCheck.axaml index 952f15c5adf..dd217d38212 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/NetworkCheck.axaml +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/NetworkCheck.axaml @@ -11,7 +11,7 @@ xmlns:ui="using:FluentAvalonia.UI.Controls" d:DesignHeight="450" d:DesignWidth="800" - x:DataType="spp:AcceleratorPageViewModel" + x:DataType="spp:NetworkCheckControlViewModel" mc:Ignorable="d"> @@ -63,14 +62,14 @@ x:Name="NATTextBlock" VerticalAlignment="Center" Foreground="Gray" - Text="未知" /> + Text="{Binding NATLevel}" /> - + @@ -126,13 +125,13 @@ + Glyph="" + IsVisible="{Binding PingOkVisible}" /> - + - + diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml.cs b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml.cs index ebf109666a8..441eef8035b 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml.cs @@ -73,31 +73,6 @@ public AcceleratorPage2() GameAccTab.IsVisible = false; AcceleratorTabs.SelectedIndex = 0; } - - NetworkCheckControl.PingOK.IsVisible = false; - NetworkCheckControl.PingError.IsVisible = false; - NetworkCheckControl.NATCheckButton.Click += (_, _) => NetworkCheckControl.PingError.IsVisible = NetworkCheckControl.PingOK.IsVisible = false; - ViewModel!.NATCheckCommand - .Subscribe(result => - { - (NetworkCheckControl.NATTextBlock.Text, NetworkCheckControl.NATTypeTip.Text) = result.Nat switch - { - AcceleratorPageViewModel.NatTypeSimple.Open => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"), - AcceleratorPageViewModel.NatTypeSimple.Moderate => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"), - AcceleratorPageViewModel.NatTypeSimple.Strict => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"), - _ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"), - }; - - if (result.PingSuccess) - { - NetworkCheckControl.PingOK.IsVisible = true; - } - else - { - NetworkCheckControl.PingError.IsVisible = true; - } - }).DisposeWith(disposables); - ViewModel.IPv6CheckCommand.Execute().Subscribe(); }); SearchGameBox.DropDownClosed += SearchGameBox_DropDownClosed;