diff --git a/NatTypeTester.ViewModels/MainWindowViewModel.cs b/NatTypeTester.ViewModels/MainWindowViewModel.cs index 018b8d0647..f37732331e 100644 --- a/NatTypeTester.ViewModels/MainWindowViewModel.cs +++ b/NatTypeTester.ViewModels/MainWindowViewModel.cs @@ -4,6 +4,7 @@ using NatTypeTester.Models; using ReactiveUI; using STUN; +using System.Collections.Frozen; using System.Reactive.Linq; using Volo.Abp.DependencyInjection; @@ -19,15 +20,15 @@ public class MainWindowViewModel : ViewModelBase, IScreen public Config Config => LazyServiceProvider.LazyGetRequiredService(); - private readonly IEnumerable _defaultServers = new HashSet + private static readonly FrozenSet DefaultServers = new[] { @"stunserver.stunprotocol.org", - @"stun.fitauto.ru", @"stun.hot-chilli.net", + @"stun.fitauto.ru", @"stun.syncthing.net", @"stun.qq.com", @"stun.miwifi.com" - }; + }.ToFrozenSet(); private SourceList List { get; } = new(); public readonly IObservableCollection StunServers = new ObservableCollectionExtended(); @@ -43,12 +44,12 @@ public MainWindowViewModel() public void LoadStunServer() { - foreach (string? server in _defaultServers) + foreach (string? server in DefaultServers) { List.Add(server); } - Config.StunServer = _defaultServers.First(); + Config.StunServer = DefaultServers.First(); Task.Run(() => { diff --git a/NatTypeTester.ViewModels/ViewModelBase.cs b/NatTypeTester.ViewModels/ViewModelBase.cs index edb1072c19..c9723bb263 100644 --- a/NatTypeTester.ViewModels/ViewModelBase.cs +++ b/NatTypeTester.ViewModels/ViewModelBase.cs @@ -5,5 +5,5 @@ namespace NatTypeTester.ViewModels; public abstract class ViewModelBase : ReactiveObject, ISingletonDependency { - public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = null!; + public IAbpLazyServiceProvider LazyServiceProvider { get; init; } = null!; } diff --git a/NatTypeTester.WinUI/App.xaml b/NatTypeTester.WinUI/App.xaml new file mode 100644 index 0000000000..81924a9d76 --- /dev/null +++ b/NatTypeTester.WinUI/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/NatTypeTester.WinUI/App.xaml.cs b/NatTypeTester.WinUI/App.xaml.cs new file mode 100644 index 0000000000..58befe40da --- /dev/null +++ b/NatTypeTester.WinUI/App.xaml.cs @@ -0,0 +1,19 @@ +namespace NatTypeTester; + +public partial class App +{ + private readonly IAbpApplicationWithInternalServiceProvider _application; + + public App() + { + InitializeComponent(); + _application = AbpApplicationFactory.Create(options => options.UseAutofac()); + _application.Initialize(); + _application.ServiceProvider.UseMicrosoftDependencyResolver(); + } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + _application.ServiceProvider.GetRequiredService().Activate(); + } +} diff --git a/NatTypeTester.WinUI/Assets/icon.ico b/NatTypeTester.WinUI/Assets/icon.ico new file mode 100644 index 0000000000..a18b538b60 Binary files /dev/null and b/NatTypeTester.WinUI/Assets/icon.ico differ diff --git a/NatTypeTester.WinUI/Extensions/ContentDialogExtensions.cs b/NatTypeTester.WinUI/Extensions/ContentDialogExtensions.cs new file mode 100644 index 0000000000..ff852d7fbf --- /dev/null +++ b/NatTypeTester.WinUI/Extensions/ContentDialogExtensions.cs @@ -0,0 +1,22 @@ +namespace NatTypeTester.Extensions; + +internal static class ContentDialogExtensions +{ + public static async ValueTask HandleExceptionWithContentDialogAsync(this Exception ex, XamlRoot root) + { + ContentDialog dialog = new(); + try + { + dialog.XamlRoot = root; + dialog.Title = nameof(NatTypeTester); + dialog.Content = ex.Message; + dialog.PrimaryButtonText = @"OK"; + + await dialog.ShowAsync(); + } + finally + { + dialog.Hide(); + } + } +} diff --git a/NatTypeTester.WinUI/Extensions/DIExtension.cs b/NatTypeTester.WinUI/Extensions/DIExtension.cs new file mode 100644 index 0000000000..569356ab17 --- /dev/null +++ b/NatTypeTester.WinUI/Extensions/DIExtension.cs @@ -0,0 +1,15 @@ +namespace NatTypeTester.Extensions; + +internal static class DIExtension +{ + public static T GetRequiredService(this IReadonlyDependencyResolver resolver, string? contract = null) where T : notnull + { + Requires.NotNull(resolver); + + T? service = resolver.GetService(contract); + + Verify.Operation(service is not null, $@"No service for type {typeof(T)} has been registered."); + + return service; + } +} diff --git a/NatTypeTester.WinUI/MainWindow.xaml b/NatTypeTester.WinUI/MainWindow.xaml new file mode 100644 index 0000000000..384f12c344 --- /dev/null +++ b/NatTypeTester.WinUI/MainWindow.xaml @@ -0,0 +1,10 @@ + + + diff --git a/NatTypeTester.WinUI/MainWindow.xaml.cs b/NatTypeTester.WinUI/MainWindow.xaml.cs new file mode 100644 index 0000000000..a296edc780 --- /dev/null +++ b/NatTypeTester.WinUI/MainWindow.xaml.cs @@ -0,0 +1,25 @@ +namespace NatTypeTester; + +public sealed partial class MainWindow : ISingletonDependency +{ + public MainWindow() + { + InitializeComponent(); + + Title = nameof(NatTypeTester); + ExtendsContentIntoTitleBar = true; + + AppWindow.Resize(new SizeInt32(500, 590)); + AppWindow.SetIcon(@"Assets\icon.ico"); + + // CenterScreen + { + DisplayArea displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest); + int x = (displayArea.WorkArea.Width - AppWindow.Size.Width) / 2; + int y = (displayArea.WorkArea.Height - AppWindow.Size.Height) / 2; + AppWindow.Move(new PointInt32(x, y)); + } + + MainFrame.Navigate(typeof(MainPage)); + } +} diff --git a/NatTypeTester.WinUI/NatTypeTester.WinUI.csproj b/NatTypeTester.WinUI/NatTypeTester.WinUI.csproj new file mode 100644 index 0000000000..05fbbb8cf9 --- /dev/null +++ b/NatTypeTester.WinUI/NatTypeTester.WinUI.csproj @@ -0,0 +1,37 @@ + + + + + + WinExe + net8.0-windows10.0.22621.0 + 10.0.17763.0 + NatTypeTester + NatTypeTester + app.manifest + x64;ARM64 + win-x64;win-arm64 + true + Assets\icon.ico + 8.0.0 + true + None + true + + + + + + + + + + + + + + + + + + diff --git a/NatTypeTester.WinUI/NatTypeTesterModule.cs b/NatTypeTester.WinUI/NatTypeTesterModule.cs new file mode 100644 index 0000000000..b8f9db0a6f --- /dev/null +++ b/NatTypeTester.WinUI/NatTypeTesterModule.cs @@ -0,0 +1,46 @@ +global using JetBrains.Annotations; +global using Microsoft; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.UI.Windowing; +global using Microsoft.UI.Xaml; +global using Microsoft.UI.Xaml.Controls; +global using Microsoft.VisualStudio.Threading; +global using NatTypeTester.Extensions; +global using NatTypeTester.ViewModels; +global using NatTypeTester.Views; +global using ReactiveMarbles.ObservableEvents; +global using ReactiveUI; +global using Splat; +global using Splat.Microsoft.Extensions.DependencyInjection; +global using STUN.Enums; +global using System.Reactive.Disposables; +global using System.Reactive.Linq; +global using Volo.Abp; +global using Volo.Abp.Autofac; +global using Volo.Abp.DependencyInjection; +global using Volo.Abp.Modularity; +global using Windows.Graphics; +global using Windows.System; + +namespace NatTypeTester; + +[DependsOn( + typeof(AbpAutofacModule), + typeof(NatTypeTesterViewModelModule) +)] +[UsedImplicitly] +internal class NatTypeTesterModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.UseMicrosoftDependencyResolver(); + Locator.CurrentMutable.InitializeSplat(); + Locator.CurrentMutable.InitializeReactiveUI(RegistrationNamespace.WinUI); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.TryAddTransient(); + } +} diff --git a/NatTypeTester.WinUI/Views/MainPage.xaml b/NatTypeTester.WinUI/Views/MainPage.xaml new file mode 100644 index 0000000000..acad7f369d --- /dev/null +++ b/NatTypeTester.WinUI/Views/MainPage.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NatTypeTester.WinUI/Views/MainPage.xaml.cs b/NatTypeTester.WinUI/Views/MainPage.xaml.cs new file mode 100644 index 0000000000..e57374c1b1 --- /dev/null +++ b/NatTypeTester.WinUI/Views/MainPage.xaml.cs @@ -0,0 +1,58 @@ +namespace NatTypeTester.Views; + +internal sealed partial class MainPage +{ + public MainPage() + { + InitializeComponent(); + ViewModel = Locator.Current.GetRequiredService(); + + IAbpLazyServiceProvider serviceProvider = Locator.Current.GetRequiredService(); + + this.WhenActivated(d => + { + this.Bind(ViewModel, + vm => vm.Config.StunServer, + v => v.ServersComboBox.Text + ).DisposeWith(d); + + this.OneWayBind(ViewModel, + vm => vm.StunServers, + v => v.ServersComboBox.ItemsSource + ).DisposeWith(d); + + this.OneWayBind(ViewModel, vm => vm.Router, v => v.RoutedViewHost.Router).DisposeWith(d); + + NavigationView.Events().SelectionChanged.Subscribe(parameter => + { + if (parameter.args.IsSettingsSelected) + { + ViewModel.Router.Navigate.Execute(serviceProvider.LazyGetRequiredService()).Subscribe().Dispose(); + return; + } + + if (parameter.args.SelectedItem is not NavigationViewItem { Tag: string tag }) + { + return; + } + + switch (tag) + { + case @"1": + { + ViewModel.Router.Navigate.Execute(serviceProvider.LazyGetRequiredService()).Subscribe().Dispose(); + break; + } + case @"2": + { + ViewModel.Router.Navigate.Execute(serviceProvider.LazyGetRequiredService()).Subscribe().Dispose(); + break; + } + } + }).DisposeWith(d); + NavigationView.SelectedItem = NavigationView.MenuItems.OfType().First(); + + ViewModel.LoadStunServer(); + }); + } +} diff --git a/NatTypeTester.WinUI/Views/RFC3489Page.xaml b/NatTypeTester.WinUI/Views/RFC3489Page.xaml new file mode 100644 index 0000000000..f2d62e53fe --- /dev/null +++ b/NatTypeTester.WinUI/Views/RFC3489Page.xaml @@ -0,0 +1,33 @@ + + + + + + + 0.0.0.0:0 + [::]:0 + + + +