Creating a new desktop app with avaloniaui

This commit is contained in:
2dust 2024-08-29 15:48:51 +08:00
parent f0dbb6b22c
commit 6c9db51fd5
59 changed files with 7498 additions and 6 deletions

View file

@ -0,0 +1,31 @@
<Application
x:Class="v2rayN.Desktop.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
RequestedThemeVariant="Default">
<Application.Styles>
<StyleInclude Source="Styles/GlobalStyles.axaml" />
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Clicked="TrayIcon_Clicked" Icon="/Assets/NotifyIcon1.ico">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Click="MenuAddServerViaClipboardClick" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
<NativeMenuItem Header="{x:Static resx:ResUI.menuAddServerViaScan}" IsVisible="False" />
<NativeMenuItem Click="MenuSubUpdate_Click" Header="{x:Static resx:ResUI.menuSubUpdate}" />
<NativeMenuItem Click="MenuSubUpdateViaProxy_Click" Header="{x:Static resx:ResUI.menuSubUpdateViaProxy}" />
<NativeMenuItemSeparator />
<NativeMenuItem Click="TrayIcon_Clicked" Header="{x:Static resx:ResUI.menuShowOrHideMainWindow}" />
<NativeMenuItem Click="MenuExit_Click" Header="{x:Static resx:ResUI.menuExit}" />
</NativeMenu>
</TrayIcon.Menu>
</TrayIcon>
</TrayIcons>
</TrayIcon.Icons>
</Application>

View file

@ -0,0 +1,121 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Splat;
using v2rayN.Desktop.Common;
using v2rayN.Desktop.Views;
namespace v2rayN.Desktop;
public partial class App : Application
{
public static EventWaitHandle ProgramStarted;
private static Config _config;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
OnStartup(desktop.Args);
desktop.Exit += OnExit;
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
private void OnStartup(string[]? Args)
{
var exePathKey = Utils.GetMD5(Utils.GetExePath());
var rebootas = (Args ?? new string[] { }).Any(t => t == Global.RebootAs);
//ProgramStarted = new EventWaitHandle(false, EventResetMode.AutoReset, exePathKey, out bool bCreatedNew);
//if (!rebootas && !bCreatedNew)
//{
// ProgramStarted.Set();
// Environment.Exit(0);
// return;
//}
Logging.Setup();
Init();
Logging.LoggingEnabled(_config.guiItem.enableLog);
Logging.SaveLog($"v2rayN start up | {Utils.GetVersion()} | {Utils.GetExePath()}");
Logging.SaveLog($"{Environment.OSVersion} - {(Environment.Is64BitOperatingSystem ? 64 : 32)}");
Logging.ClearLogs();
Thread.CurrentThread.CurrentUICulture = new(_config.uiItem.currentLanguage);
}
private void Init()
{
if (ConfigHandler.LoadConfig(ref _config) != 0)
{
Logging.SaveLog($"Loading GUI configuration file is abnormal,please restart the application{Environment.NewLine}加载GUI配置文件异常,请重启应用");
Environment.Exit(0);
return;
}
LazyConfig.Instance.SetConfig(_config);
Locator.CurrentMutable.RegisterLazySingleton(() => new NoticeHandler(), typeof(NoticeHandler));
//Under Win10
if (Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)
{
Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User);
}
}
private void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e)
{
}
private void TrayIcon_Clicked(object? sender, EventArgs e)
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (desktop.MainWindow.IsVisible)
{
desktop.MainWindow?.Hide();
}
else
{
desktop.MainWindow?.Show();
}
}
}
private void MenuAddServerViaClipboardClick(object? sender, EventArgs e)
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var clipboardData = AvaUtils.GetClipboardData(desktop.MainWindow).Result;
Locator.Current.GetService<MainWindowViewModel>()?.AddServerViaClipboardAsync(clipboardData);
}
}
private void MenuSubUpdate_Click(object? sender, EventArgs e)
{
Locator.Current.GetService<MainWindowViewModel>()?.UpdateSubscriptionProcess("", false);
}
private void MenuSubUpdateViaProxy_Click(object? sender, EventArgs e)
{
Locator.Current.GetService<MainWindowViewModel>()?.UpdateSubscriptionProcess("", true);
}
private void MenuExit_Click(object? sender, EventArgs e)
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
Locator.Current.GetService<MainWindowViewModel>()?.MyAppExitAsync(false);
desktop.Shutdown();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

View file

@ -0,0 +1,64 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using System.Reflection;
namespace v2rayN.Desktop.Common
{
internal class AvaUtils
{
public static async Task<string?> GetClipboardData(Window owner)
{
try
{
var clipboard = TopLevel.GetTopLevel(owner)?.Clipboard;
if (clipboard == null) return null;
return await clipboard.GetTextAsync();
}
catch
{
return null;
}
}
public static async Task SetClipboardData(Visual? visual, string strData)
{
try
{
var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard;
if (clipboard == null) return;
var dataObject = new DataObject();
dataObject.Set(DataFormats.Text, strData);
await clipboard.SetDataObjectAsync(dataObject);
}
catch
{
}
}
public static WindowIcon GetAppIcon(ESysProxyType sysProxyType)
{
int index = 1;
switch (sysProxyType)
{
case ESysProxyType.ForcedClear:
index = 1;
break;
case ESysProxyType.ForcedChange:
case ESysProxyType.Pac:
index = 2;
break;
case ESysProxyType.Unchanged:
index = 3;
break;
}
var uri = new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/NotifyIcon{index}.ico");
using var bitmap = new Bitmap(AssetLoader.Open(uri));
return new(bitmap);
}
}
}

View file

@ -0,0 +1,50 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
namespace v2rayN.Desktop.Common
{
internal class UI
{
private static readonly string caption = Global.AppName;
public static async Task<ButtonResult> ShowYesNo(Window owner, string msg)
{
var box = MessageBoxManager.GetMessageBoxStandard(caption, msg, ButtonEnum.YesNo);
return await box.ShowWindowDialogAsync(owner);
}
public static async Task<string?> OpenFileDialog(Window owner, FilePickerFileType? filter)
{
var topLevel = TopLevel.GetTopLevel(owner);
if (topLevel == null)
{
return null;
}
// Start async operation to open the dialog.
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = false,
FileTypeFilter = filter is null ? [FilePickerFileTypes.All, FilePickerFileTypes.ImagePng] : [filter]
});
return files.FirstOrDefault()?.TryGetLocalPath();
}
public static async Task<string?> SaveFileDialog(Window owner, string filter)
{
var topLevel = TopLevel.GetTopLevel(owner);
if (topLevel == null)
{
return null;
}
// Start async operation to open the dialog.
var files = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
});
return files?.TryGetLocalPath();
}
}
}

View file

@ -0,0 +1,26 @@
using Avalonia.Data.Converters;
using Avalonia.Media;
using System.Globalization;
namespace v2rayN.Desktop.Converters
{
public class DelayColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
int.TryParse(value?.ToString(), out var delay);
if (delay <= 0)
return new SolidColorBrush(Colors.Red);
if (delay <= 500)
return new SolidColorBrush(Colors.Green);
else
return new SolidColorBrush(Colors.IndianRed);
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return null;
}
}
}

View file

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ReactiveUI />
</Weavers>

View file

@ -0,0 +1,8 @@
global using ServiceLib;
global using ServiceLib.Base;
global using ServiceLib.Common;
global using ServiceLib.Enums;
global using ServiceLib.Handler;
global using ServiceLib.Models;
global using ServiceLib.Resx;
global using ServiceLib.ViewModels;

View file

@ -0,0 +1,61 @@
namespace v2rayN.Desktop.Handler
{
public static class SysProxyHandler
{
public static async Task<bool> UpdateSysProxy(Config config, bool forceDisable)
{
var type = config.systemProxyItem.sysProxyType;
if (forceDisable && type != ESysProxyType.Unchanged)
{
type = ESysProxyType.ForcedClear;
}
try
{
int port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
if (port <= 0)
{
return false;
}
if (type == ESysProxyType.ForcedChange)
{
var strProxy = $"{Global.Loopback}:{port}";
await SetProxy(strProxy);
}
else if (type == ESysProxyType.ForcedClear)
{
await UnsetProxy();
}
else if (type == ESysProxyType.Unchanged)
{
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
return true;
}
private static async Task SetProxy(string? strProxy)
{
await Task.Run(() =>
{
var httpProxy = strProxy is null ? null : $"{Global.HttpProtocol}{strProxy}";
var socksProxy = strProxy is null ? null : $"{Global.SocksProtocol}{strProxy}";
var noProxy = $"localhost,127.0.0.0/8,::1";
Environment.SetEnvironmentVariable("http_proxy", httpProxy, EnvironmentVariableTarget.User);
Environment.SetEnvironmentVariable("https_proxy", httpProxy, EnvironmentVariableTarget.User);
Environment.SetEnvironmentVariable("all_proxy", socksProxy, EnvironmentVariableTarget.User);
Environment.SetEnvironmentVariable("no_proxy", noProxy, EnvironmentVariableTarget.User);
});
}
private static async Task UnsetProxy()
{
await SetProxy(null);
}
}
}

View file

@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.ReactiveUI;
namespace v2rayN.Desktop;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseReactiveUI();
}

View file

@ -0,0 +1,39 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
</Border>
</Design.PreviewWith>
<Style Selector="TextBlock.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="StackPanel.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="DockPanel.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="WrapPanel.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="Grid.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="TextBlock.Margin8">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="StackPanel.Margin8">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="DockPanel.Margin8">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="WrapPanel.Margin8">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="Grid.Margin8">
<Setter Property="Margin" Value="8" />
</Style>
</Styles>

View file

@ -0,0 +1,143 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Styling;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
using System.Reactive.Linq;
namespace v2rayN.Desktop.ViewModels
{
public class ThemeSettingViewModel : MyReactiveObject
{
[Reactive]
public bool ColorModeDark { get; set; }
[Reactive]
public int CurrentFontSize { get; set; }
[Reactive]
public string CurrentLanguage { get; set; }
public ThemeSettingViewModel()
{
_config = LazyConfig.Instance.Config;
_noticeHandler = Locator.Current.GetService<NoticeHandler>();
BindingUI();
RestoreUI();
}
private void RestoreUI()
{
ModifyTheme(_config.uiItem.colorModeDark);
}
private void BindingUI()
{
ColorModeDark = _config.uiItem.colorModeDark;
CurrentFontSize = _config.uiItem.currentFontSize;
CurrentLanguage = _config.uiItem.currentLanguage;
this.WhenAnyValue(x => x.ColorModeDark)
.Subscribe(c =>
{
if (_config.uiItem.colorModeDark != ColorModeDark)
{
_config.uiItem.colorModeDark = ColorModeDark;
ModifyTheme(ColorModeDark);
ConfigHandler.SaveConfig(_config);
}
});
this.WhenAnyValue(
x => x.CurrentFontSize,
y => y > 0)
.Subscribe(c =>
{
if (CurrentFontSize >= Global.MinFontSize)
{
_config.uiItem.currentFontSize = CurrentFontSize;
double size = CurrentFontSize;
ModifyFontSize(size);
ConfigHandler.SaveConfig(_config);
}
});
this.WhenAnyValue(
x => x.CurrentLanguage,
y => y != null && !y.IsNullOrEmpty())
.Subscribe(c =>
{
if (!Utils.IsNullOrEmpty(CurrentLanguage) && _config.uiItem.currentLanguage != CurrentLanguage)
{
_config.uiItem.currentLanguage = CurrentLanguage;
Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage);
ConfigHandler.SaveConfig(_config);
_noticeHandler?.Enqueue(ResUI.NeedRebootTips);
}
});
}
private void ModifyTheme(bool isDarkTheme)
{
var app = Application.Current;
if (app is not null)
{
app.RequestedThemeVariant = isDarkTheme ? ThemeVariant.Dark : ThemeVariant.Light;
}
}
private void ModifyFontSize(double size)
{
Style buttonStyle = new(x => x.OfType<Button>());
buttonStyle.Add(new Setter()
{
Property = Button.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(buttonStyle);
Style textStyle = new(x => x.OfType<TextBox>());
textStyle.Add(new Setter()
{
Property = TextBox.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(textStyle);
Style textBlockStyle = new(x => x.OfType<TextBlock>());
textBlockStyle.Add(new Setter()
{
Property = TextBlock.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(textBlockStyle);
Style menuStyle = new(x => x.OfType<Menu>());
menuStyle.Add(new Setter()
{
Property = Menu.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(menuStyle);
Style dataStyle = new(x => x.OfType<DataGridRow>());
dataStyle.Add(new Setter()
{
Property = DataGridRow.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(dataStyle);
Style listStyle = new(x => x.OfType<ListBoxItem>());
listStyle.Add(new Setter()
{
Property = ListBoxItem.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(listStyle);
}
}
}

View file

@ -0,0 +1,179 @@
<Window
x:Class="v2rayN.Desktop.Views.AddServer2Window"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Width="700"
Height="500"
x:DataType="vms:AddServer2ViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Text="{x:Static resx:ResUI.menuServers}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
Width="400"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbAddress}" />
<TextBox
x:Name="txtAddress"
Grid.Row="2"
Grid.Column="1"
Width="400"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
IsReadOnly="True" />
<StackPanel
Grid.Row="2"
Grid.Column="2"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="btnBrowse"
Margin="2,0"
Content="{x:Static resx:ResUI.TbBrowse}" />
<Button
x:Name="btnEdit"
Margin="2,0"
Content="{x:Static resx:ResUI.TbEdit}" />
</StackPanel>
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbCoreType}" />
<ComboBox
x:Name="cmbCoreType"
Grid.Row="3"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin4"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbDisplayLog}" />
<StackPanel
Grid.Row="4"
Grid.Column="1"
Classes="Margin4"
Orientation="Horizontal">
<ToggleSwitch
x:Name="togDisplayLog"
HorizontalAlignment="Left"
Classes="Margin4" />
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TipDisplayLog}" />
</StackPanel>
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbPreSocksPort}" />
<TextBox
x:Name="txtPreSocksPort"
Grid.Row="5"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin4" />
<StackPanel
Grid.Row="6"
Grid.Column="1"
Grid.ColumnSpan="2"
Classes="Margin4">
<TextBlock
Width="500"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TipPreSocksPort}"
TextWrapping="Wrap" />
<TextBlock
Width="500"
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.CustomServerTips}"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</DockPanel>
</Window>

View file

@ -0,0 +1,72 @@
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class AddServer2Window : ReactiveWindow<AddServer2ViewModel>
{
public AddServer2Window()
{
InitializeComponent();
}
public AddServer2Window(ProfileItem profileItem)
{
InitializeComponent();
this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close();
ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler);
foreach (ECoreType it in Enum.GetValues(typeof(ECoreType)))
{
if (it == ECoreType.v2rayN)
continue;
cmbCoreType.Items.Add(it.ToString());
}
cmbCoreType.Items.Add(string.Empty);
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.SelectedSource.remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.address, v => v.txtAddress.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.displayLog, v => v.togDisplayLog.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.preSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.BrowseServerCmd, v => v.btnBrowse).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.btnEdit).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveServerCmd, v => v.btnSave).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
case EViewAction.BrowseServer:
var fileName = await UI.OpenFileDialog(this, null);
if (fileName.IsNullOrEmpty())
{
return false;
}
ViewModel?.BrowseServer(fileName);
break;
}
return await Task.FromResult(true);
}
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
txtRemarks.Focus();
}
}
}

View file

@ -0,0 +1,842 @@
<Window
x:Class="v2rayN.Desktop.Views.AddServerWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuServers}"
Width="900"
Height="700"
x:DataType="vms:AddServerViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
Classes="Margin4"
HorizontalAlignment="Center"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Text="{x:Static resx:ResUI.menuServers}" />
<StackPanel
Grid.Row="0"
Grid.Column="2"
Orientation="Horizontal">
<ComboBox
x:Name="cmbCoreType"
Width="100"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.TbCoreType}" />
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddress}" />
<TextBox
x:Name="txtAddress"
Grid.Row="2"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPort}" />
<TextBox
x:Name="txtPort"
Grid.Row="3"
Grid.Column="1"
Width="100"
Classes="Margin4"
HorizontalAlignment="Left" />
</Grid>
<Separator Grid.Row="1" Margin="0,2" />
<Grid
x:Name="gridVMess"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<Button
x:Name="btnGUID"
Grid.Row="1"
Grid.Column="2"
Margin="4,0"
Content="{x:Static resx:ResUI.TbGUID}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAlterId}" />
<TextBox
x:Name="txtAlterId"
Grid.Row="2"
Grid.Column="1"
Width="100"
Classes="Margin4"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSecurity}" />
<ComboBox
x:Name="cmbSecurity"
Grid.Row="3"
Grid.Column="1"
Width="200"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridSs"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId3"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSecurity3}" />
<ComboBox
x:Name="cmbSecurity3"
Grid.Row="2"
Grid.Column="1"
Width="300"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridSocks"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSecurity4}" />
<TextBox
x:Name="txtSecurity4"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId4}" />
<TextBox
x:Name="txtId4"
Grid.Row="2"
Grid.Column="1"
Width="400"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridVLESS"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId5}" />
<TextBox
x:Name="txtId5"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<Button
x:Name="btnGUID5"
Grid.Row="1"
Grid.Column="2"
Margin="4,0"
Content="{x:Static resx:ResUI.TbGUID}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFlow5}" />
<ComboBox
x:Name="cmbFlow5"
Grid.Row="2"
Grid.Column="1"
Width="200"
Classes="Margin4" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSecurity5}" />
<TextBox
x:Name="txtSecurity5"
Grid.Row="3"
Grid.Column="1"
Width="200"
Classes="Margin4"
HorizontalAlignment="Left" />
</Grid>
<Grid
x:Name="gridTrojan"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId6"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFlow5}" />
<ComboBox
x:Name="cmbFlow6"
Grid.Row="2"
Grid.Column="1"
Width="200"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridHysteria2"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId7"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPath7}" />
<TextBox
x:Name="txtPath7"
Grid.Row="2"
Grid.Column="1"
Width="400"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridTuic"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId8"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtSecurity8"
Grid.Row="2"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHeaderType8}" />
<ComboBox
x:Name="cmbHeaderType8"
Grid.Row="3"
Grid.Column="1"
Width="100"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridWireguard"
Grid.Row="2"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPrivateKey}" />
<TextBox
x:Name="txtId9"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPublicKey}" />
<TextBox
x:Name="txtPublicKey9"
Grid.Row="2"
Grid.Column="1"
Width="400"
Classes="Margin4"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbReserved}" />
<TextBox
x:Name="txtPath9"
Grid.Row="3"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbLocalAddress}" />
<TextBox
x:Name="txtRequestHost9"
Grid.Row="4"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="Mtu" />
<TextBox
x:Name="txtShortId9"
Grid.Row="5"
Grid.Column="1"
Width="100"
Classes="Margin4"
HorizontalAlignment="Left" />
</Grid>
<Separator
x:Name="sepa2"
Grid.Row="3"
Margin="0,2" />
<Grid x:Name="gridTransport" Grid.Row="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.ColumnSpan="2"
Classes="Margin4"
Text="{x:Static resx:ResUI.GbTransport}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbNetwork}" />
<ComboBox
x:Name="cmbNetwork"
Grid.Row="1"
Grid.Column="1"
Width="100"
Classes="Margin4" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TipNetwork}" />
<TextBlock
x:Name="labHeaderType"
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHeaderType}" />
<ComboBox
x:Name="cmbHeaderType"
Grid.Row="2"
Grid.Column="1"
Width="100"
Classes="Margin4" />
<TextBlock
x:Name="tipHeaderType"
Grid.Row="2"
Grid.Column="2"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHeaderType}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRequestHost}" />
<TextBox
x:Name="txtRequestHost"
Grid.Row="3"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
x:Name="tipRequestHost"
Grid.Row="3"
Grid.Column="2"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRequestHost}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPath}" />
<TextBox
x:Name="txtPath"
Grid.Row="4"
Grid.Column="1"
Width="400"
Classes="Margin4" />
<TextBlock
x:Name="tipPath"
Grid.Row="4"
Grid.Column="2"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPath}" />
</Grid>
<Separator Grid.Row="5" Margin="0,2" />
<Grid x:Name="gridTls" Grid.Row="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbStreamSecurity}" />
<ComboBox
x:Name="cmbStreamSecurity"
Grid.Row="0"
Grid.Column="1"
Width="100"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridTlsMore"
Grid.Row="7"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSNI}" />
<TextBox
x:Name="txtSNI"
Grid.Row="1"
Grid.Column="1"
Width="400"
Classes="Margin4"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFingerprint}" />
<ComboBox
x:Name="cmbFingerprint"
Grid.Row="2"
Grid.Column="1"
Width="200"
Classes="Margin4" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAlpn}" />
<ComboBox
x:Name="cmbAlpn"
Grid.Row="3"
Grid.Column="1"
Width="200"
Classes="Margin4" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAllowInsecure}" />
<ComboBox
x:Name="cmbAllowInsecure"
Grid.Row="4"
Grid.Column="1"
Width="100"
Classes="Margin4" />
</Grid>
<Grid
x:Name="gridRealityMore"
Grid.Row="7"
IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSNI}" />
<TextBox
x:Name="txtSNI2"
Grid.Row="0"
Grid.Column="1"
Width="400"
Classes="Margin4"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFingerprint}" />
<ComboBox
x:Name="cmbFingerprint2"
Grid.Row="1"
Grid.Column="1"
Width="200"
Classes="Margin4" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPublicKey}" />
<TextBox
x:Name="txtPublicKey"
Grid.Row="2"
Grid.Column="1"
Width="400"
Classes="Margin4"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbShortId}" />
<TextBox
x:Name="txtShortId"
Grid.Row="3"
Grid.Column="1"
Width="200"
Classes="Margin4"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSpiderX}" />
<TextBox
x:Name="txtSpiderX"
Grid.Row="4"
Grid.Column="1"
Width="400"
Classes="Margin4"
HorizontalAlignment="Left" />
</Grid>
<Separator Grid.Row="8" Margin="0,2" />
</Grid>
</ScrollViewer>
</DockPanel>
</Window>

View file

@ -0,0 +1,365 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class AddServerWindow : ReactiveWindow<AddServerViewModel>
{
public AddServerWindow()
{
InitializeComponent();
}
public AddServerWindow(ProfileItem profileItem)
{
InitializeComponent();
this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close();
cmbNetwork.SelectionChanged += CmbNetwork_SelectionChanged;
cmbStreamSecurity.SelectionChanged += CmbStreamSecurity_SelectionChanged;
btnGUID.Click += btnGUID_Click;
btnGUID5.Click += btnGUID_Click;
ViewModel = new AddServerViewModel(profileItem, UpdateViewHandler);
if (profileItem.configType == EConfigType.VLESS)
{
Global.CoreTypes4VLESS.ForEach(it =>
{
cmbCoreType.Items.Add(it);
});
}
else
{
Global.CoreTypes.ForEach(it =>
{
cmbCoreType.Items.Add(it);
});
}
cmbCoreType.Items.Add(string.Empty);
cmbStreamSecurity.Items.Add(string.Empty);
cmbStreamSecurity.Items.Add(Global.StreamSecurity);
Global.Networks.ForEach(it =>
{
cmbNetwork.Items.Add(it);
});
Global.Fingerprints.ForEach(it =>
{
cmbFingerprint.Items.Add(it);
cmbFingerprint2.Items.Add(it);
});
Global.AllowInsecure.ForEach(it =>
{
cmbAllowInsecure.Items.Add(it);
});
Global.Alpns.ForEach(it =>
{
cmbAlpn.Items.Add(it);
});
switch (profileItem.configType)
{
case EConfigType.VMess:
gridVMess.IsVisible = true;
Global.VmessSecurities.ForEach(it =>
{
cmbSecurity.Items.Add(it);
});
if (profileItem.security.IsNullOrEmpty())
{
profileItem.security = Global.DefaultSecurity;
}
break;
case EConfigType.Shadowsocks:
gridSs.IsVisible = true;
LazyConfig.Instance.GetShadowsocksSecurities(profileItem).ForEach(it =>
{
cmbSecurity3.Items.Add(it);
});
break;
case EConfigType.Socks:
case EConfigType.Http:
gridSocks.IsVisible = true;
break;
case EConfigType.VLESS:
gridVLESS.IsVisible = true;
cmbStreamSecurity.Items.Add(Global.StreamSecurityReality);
Global.Flows.ForEach(it =>
{
cmbFlow5.Items.Add(it);
});
if (profileItem.security.IsNullOrEmpty())
{
profileItem.security = Global.None;
}
break;
case EConfigType.Trojan:
gridTrojan.IsVisible = true;
cmbStreamSecurity.Items.Add(Global.StreamSecurityReality);
Global.Flows.ForEach(it =>
{
cmbFlow6.Items.Add(it);
});
break;
case EConfigType.Hysteria2:
gridHysteria2.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
break;
case EConfigType.Tuic:
gridTuic.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
Global.TuicCongestionControls.ForEach(it =>
{
cmbHeaderType8.Items.Add(it);
});
break;
case EConfigType.Wireguard:
gridWireguard.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
gridTls.IsVisible = false;
cmbCoreType.IsEnabled = false;
break;
}
gridTlsMore.IsVisible = false;
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.address, v => v.txtAddress.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.port, v => v.txtPort.Text).DisposeWith(disposables);
switch (profileItem.configType)
{
case EConfigType.VMess:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.alterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.Socks:
case EConfigType.Http:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.txtSecurity4.Text).DisposeWith(disposables);
break;
case EConfigType.VLESS:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.txtSecurity5.Text).DisposeWith(disposables);
break;
case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.path, v => v.txtPath7.Text).DisposeWith(disposables);
break;
case EConfigType.Tuic:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.headerType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.Wireguard:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.publicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.path, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.requestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.shortId, v => v.txtShortId9.Text).DisposeWith(disposables);
break;
}
this.Bind(ViewModel, vm => vm.SelectedSource.network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.headerType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.requestHost, v => v.txtRequestHost.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.path, v => v.txtPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.streamSecurity, v => v.cmbStreamSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.sni, v => v.txtSNI.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.allowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
//reality
this.Bind(ViewModel, vm => vm.SelectedSource.sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.publicKey, v => v.txtPublicKey.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.shortId, v => v.txtShortId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.spiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
this.Title = $"{profileItem.configType}";
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
}
return await Task.FromResult(true);
}
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
txtRemarks.Focus();
}
private void CmbNetwork_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
SetHeaderType();
SetTips();
}
private void CmbStreamSecurity_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
var security = cmbStreamSecurity.SelectedItem.ToString();
if (security == Global.StreamSecurityReality)
{
gridRealityMore.IsVisible = true;
gridTlsMore.IsVisible = false;
}
else if (security == Global.StreamSecurity)
{
gridRealityMore.IsVisible = false;
gridTlsMore.IsVisible = true;
}
else
{
gridRealityMore.IsVisible = false;
gridTlsMore.IsVisible = false;
}
}
private void btnGUID_Click(object? sender, RoutedEventArgs e)
{
txtId.Text =
txtId5.Text = Utils.GetGUID();
}
private void SetHeaderType()
{
cmbHeaderType.Items.Clear();
var network = cmbNetwork.SelectedItem.ToString();
if (Utils.IsNullOrEmpty(network))
{
cmbHeaderType.Items.Add(Global.None);
return;
}
if (network == nameof(ETransport.tcp))
{
cmbHeaderType.Items.Add(Global.None);
cmbHeaderType.Items.Add(Global.TcpHeaderHttp);
}
else if (network is nameof(ETransport.kcp) or nameof(ETransport.quic))
{
cmbHeaderType.Items.Add(Global.None);
Global.KcpHeaderTypes.ForEach(it =>
{
cmbHeaderType.Items.Add(it);
});
}
else if (network == nameof(ETransport.grpc))
{
cmbHeaderType.Items.Add(Global.GrpcGunMode);
cmbHeaderType.Items.Add(Global.GrpcMultiMode);
}
else
{
cmbHeaderType.Items.Add(Global.None);
}
cmbHeaderType.SelectedIndex = 0;
}
private void SetTips()
{
var network = cmbNetwork.SelectedItem.ToString();
if (Utils.IsNullOrEmpty(network))
{
network = Global.DefaultNetwork;
}
labHeaderType.IsVisible = true;
tipRequestHost.Text =
tipPath.Text =
tipHeaderType.Text = string.Empty;
switch (network)
{
case nameof(ETransport.tcp):
tipRequestHost.Text = ResUI.TransportRequestHostTip1;
tipHeaderType.Text = ResUI.TransportHeaderTypeTip1;
break;
case nameof(ETransport.kcp):
tipHeaderType.Text = ResUI.TransportHeaderTypeTip2;
tipPath.Text = ResUI.TransportPathTip5;
break;
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
case nameof(ETransport.splithttp):
tipRequestHost.Text = ResUI.TransportRequestHostTip2;
tipPath.Text = ResUI.TransportPathTip1;
break;
case nameof(ETransport.h2):
tipRequestHost.Text = ResUI.TransportRequestHostTip3;
tipPath.Text = ResUI.TransportPathTip2;
break;
case nameof(ETransport.quic):
tipRequestHost.Text = ResUI.TransportRequestHostTip4;
tipPath.Text = ResUI.TransportPathTip3;
tipHeaderType.Text = ResUI.TransportHeaderTypeTip3;
break;
case nameof(ETransport.grpc):
tipRequestHost.Text = ResUI.TransportRequestHostTip5;
tipPath.Text = ResUI.TransportPathTip4;
tipHeaderType.Text = ResUI.TransportHeaderTypeTip4;
labHeaderType.IsVisible = false;
break;
}
}
}
}

View file

@ -0,0 +1,109 @@
<UserControl
x:Class="v2rayN.Desktop.Views.ClashConnectionsView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:ClashConnectionsViewModel"
mc:Ignorable="d">
<DockPanel Margin="2">
<WrapPanel
VerticalAlignment="Center"
Classes="Margin8"
DockPanel.Dock="Top"
Orientation="Horizontal">
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSorting}" />
<ComboBox
x:Name="cmbSorting"
Width="100"
Margin="8,0">
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingUpSpeed}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDownSpeed}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingUpTraffic}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDownTraffic}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingTime}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingHost}" />
</ComboBox>
<Button
x:Name="btnConnectionCloseAll"
Width="24"
Height="24"
Margin="8,0"
Classes="Success"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuConnectionCloseAll}">
<Button.Content>
<Image
Width="16"
Height="16"
Source="/Assets/delete.png" />
</Button.Content>
</Button>
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAutoRefresh}" />
<ToggleSwitch
x:Name="togAutoRefresh"
Margin="8,0"
HorizontalAlignment="Left" />
</WrapPanel>
<DataGrid
x:Name="lstConnections"
AutoGenerateColumns="False"
BorderThickness="1"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding ConnectionItems}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuConnectionClose" Header="{x:Static resx:ResUI.menuConnectionClose}" />
<MenuItem x:Name="menuConnectionCloseAll" Header="{x:Static resx:ResUI.menuConnectionCloseAll}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn
Width="240"
Binding="{Binding host}"
Header="{x:Static resx:ResUI.TbSortingHost}" />
<DataGridTextColumn
Width="160"
Binding="{Binding chain}"
Header="{x:Static resx:ResUI.TbSortingChain}" />
<DataGridTextColumn
Width="80"
Binding="{Binding network}"
Header="{x:Static resx:ResUI.TbSortingNetwork}" />
<DataGridTextColumn
Width="100"
Binding="{Binding type}"
Header="{x:Static resx:ResUI.TbSortingType}" />
<DataGridTextColumn
Width="100"
Binding="{Binding uploadTraffic}"
Header="{x:Static resx:ResUI.TbSortingUpTraffic}" />
<DataGridTextColumn
Width="100"
Binding="{Binding downloadTraffic}"
Header="{x:Static resx:ResUI.TbSortingDownTraffic}" />
<DataGridTextColumn
Width="100"
Binding="{Binding elapsed}"
Header="{x:Static resx:ResUI.TbSortingTime}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,50 @@
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class ClashConnectionsView : ReactiveUserControl<ClashConnectionsViewModel>
{
public ClashConnectionsView()
{
InitializeComponent();
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.ConnectionItems, v => v.lstConnections.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstConnections.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.DispatcherRefreshConnections:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.RefreshConnections((List<ConnectionItem>?)obj),
DispatcherPriority.Default);
break;
}
return await Task.FromResult(true);
}
private void btnClose_Click(object? sender, RoutedEventArgs e)
{
ViewModel?.ClashConnectionClose(false);
}
}
}

View file

@ -0,0 +1,179 @@
<UserControl
x:Class="v2rayN.Desktop.Views.ClashProxiesView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="using:v2rayN.Desktop.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:ClashProxiesViewModel"
mc:Ignorable="d">
<UserControl.Resources>
<conv:DelayColorConverter x:Key="DelayColorConverter" />
</UserControl.Resources>
<DockPanel Margin="2">
<WrapPanel
VerticalAlignment="Center"
Classes="Margin8"
DockPanel.Dock="Top"
Orientation="Horizontal">
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.menuRulemode}" />
<ComboBox
x:Name="cmbRulemode"
Width="80"
Margin="8,0">
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeRule}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeGlobal}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeDirect}" />
</ComboBox>
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSorting}" />
<ComboBox
x:Name="cmbSorting"
Width="60"
Margin="8,0">
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDelay}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingName}" />
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDefault}" />
</ComboBox>
<Button
x:Name="menuProxiesReload"
Width="24"
Height="24"
Margin="8,0"
Classes="Success"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProxiesReload}">
<Button.Content>
<Image
Width="16"
Height="16"
Source="/Assets/refresh.png" />
</Button.Content>
</Button>
<Button
x:Name="menuProxiesDelaytest"
Width="24"
Height="24"
Margin="8,0"
Classes="Success"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProxiesDelaytest}">
<Button.Content>
<Image
Width="16"
Height="16"
Source="/Assets/light.png" />
</Button.Content>
</Button>
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAutoRefresh}" />
<ToggleSwitch
x:Name="togAutoRefresh"
Margin="8,0"
HorizontalAlignment="Left" />
</WrapPanel>
<DockPanel>
<ListBox
x:Name="lstProxyGroups"
Margin="0,0,5,0"
DockPanel.Dock="Left"
ItemsSource="{Binding ProxyGroups}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Width="160"
Margin="4,0"
Padding="0"
Theme="{StaticResource CardBorder}">
<DockPanel>
<Grid Grid.Column="0" Classes="Margin4">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<TextBlock DockPanel.Dock="Right" Text="{Binding type}" />
<TextBlock Text="{Binding name}" />
</DockPanel>
<TextBlock Grid.Row="1" Text="{Binding now}" />
</Grid>
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ListBox x:Name="lstProxyDetails" ItemsSource="{Binding ProxyDetails}">
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuProxiesDelaytestPart" Header="{x:Static resx:ResUI.menuProxiesDelaytestPart}" />
<MenuItem x:Name="menuProxiesSelectActivity" Header="{x:Static resx:ResUI.menuProxiesSelectActivity}" />
</ContextMenu>
</ItemsControl.ContextMenu>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Width="160"
Padding="0"
Theme="{StaticResource CardBorder}">
<DockPanel>
<Border
Width="5"
Height="30"
Margin="0,1"
Background="YellowGreen"
CornerRadius="4"
DockPanel.Dock="Left"
IsVisible="{Binding isActive}" />
<Grid Classes="Margin4">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Text="{Binding name}"
TextWrapping="WrapWithOverflow" />
<DockPanel Grid.Row="1">
<TextBlock
DockPanel.Dock="Right"
Foreground="{Binding Path=delay, Converter={StaticResource DelayColorConverter}}"
Text="{Binding delayName}" />
<TextBlock Text="{Binding type}" />
</DockPanel>
</Grid>
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</DockPanel>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,81 @@
using Avalonia.Input;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using DynamicData;
using ReactiveUI;
using Splat;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewModel>
{
public ClashProxiesView()
{
InitializeComponent();
ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ClashProxiesViewModel));
lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped;
this.KeyDown += ClashProxiesView_KeyDown;
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.ProxyGroups, v => v.lstProxyGroups.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedGroup, v => v.lstProxyGroups.SelectedItem).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ProxyDetails, v => v.lstProxyDetails.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ProxiesDelaytestCmd, v => v.menuProxiesDelaytest).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ProxiesDelaytestPartCmd, v => v.menuProxiesDelaytestPart).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedIndex).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.DispatcherRefreshProxyGroups:
Dispatcher.UIThread.Post(() =>
ViewModel?.RefreshProxyGroups(),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherProxiesDelayTest:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.ProxiesDelayTestResult((SpeedTestResult)obj),
DispatcherPriority.Default);
break;
}
return await Task.FromResult(true);
}
private void ClashProxiesView_KeyDown(object? sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.F5:
ViewModel?.ProxiesReload();
break;
case Key.Enter:
ViewModel?.SetActiveProxy();
break;
}
}
private void LstProxyDetails_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
ViewModel?.SetActiveProxy();
}
}
}

View file

@ -0,0 +1,174 @@
<Window
x:Class="v2rayN.Desktop.Views.DNSSettingWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuDNSSetting}"
Width="1000"
Height="700"
x:DataType="vms:DNSSettingViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<TabControl HorizontalContentAlignment="Left">
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreDns}">
<DockPanel Classes="Margin8">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsRemoteDNS}" />
<TextBlock VerticalAlignment="Center" Classes="Margin8">
<Button Click="linkDnsObjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbDnsObjectDoc}" />
</Button>
</TextBlock>
<Button
x:Name="btnImportDefConfig4V2ray"
Classes="Margin8"
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
Cursor="Hand" />
</StackPanel>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsUseSystemHosts}" />
<ToggleSwitch
x:Name="togUseSystemHosts"
HorizontalAlignment="Left"
Classes="Margin8" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDomainStrategy4Freedom}" />
<ComboBox
x:Name="cmbdomainStrategy4Freedom"
Width="150"
Classes="Margin8" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDomainDNSAddress}" />
<ComboBox
x:Name="cmbdomainDNSAddress"
Width="150"
Classes="Margin8" />
</StackPanel>
</WrapPanel>
<TextBox
x:Name="txtnormalDNS"
VerticalAlignment="Stretch"
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap"
ToolTip.Tip="Http/Socks" />
</DockPanel>
</TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreDnsSingbox}">
<DockPanel Classes="Margin8">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Classes="Margin8">
<Button Click="linkDnsSingboxObjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbDnsSingboxObjectDoc}" />
</Button>
</TextBlock>
<Button
x:Name="btnImportDefConfig4Singbox"
Classes="Margin8"
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
Cursor="Hand" />
</StackPanel>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDomainStrategy4Out}" />
<ComboBox
x:Name="cmbdomainStrategy4Out"
Width="150"
Classes="Margin8" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDomainDNSAddress}" />
<ComboBox
x:Name="cmbdomainDNSAddress2"
Width="150"
Classes="Margin8" />
</StackPanel>
</WrapPanel>
<Grid Classes="Margin8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox
x:Name="txtnormalDNS2"
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap"
ToolTip.Tip="Http/Socks" />
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
<TextBox
x:Name="txttunDNS2"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.TbSettingsTunMode}" />
</Grid>
</DockPanel>
</TabItem>
</TabControl>
</DockPanel>
</Window>

View file

@ -0,0 +1,76 @@
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class DNSSettingWindow : ReactiveWindow<DNSSettingViewModel>
{
private static Config _config;
public DNSSettingWindow()
{
InitializeComponent();
_config = LazyConfig.Instance.Config;
btnCancel.Click += (s, e) => this.Close();
ViewModel = new DNSSettingViewModel(UpdateViewHandler);
Global.DomainStrategy4Freedoms.ForEach(it =>
{
cmbdomainStrategy4Freedom.Items.Add(it);
});
Global.SingboxDomainStrategy4Out.ForEach(it =>
{
cmbdomainStrategy4Out.Items.Add(it);
});
Global.DomainDNSAddress.ForEach(it =>
{
cmbdomainDNSAddress.Items.Add(it);
});
Global.SingboxDomainDNSAddress.ForEach(it =>
{
cmbdomainDNSAddress2.Items.Add(it);
});
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.useSystemHosts, v => v.togUseSystemHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainStrategy4Freedom, v => v.cmbdomainStrategy4Freedom.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainDNSAddress, v => v.cmbdomainDNSAddress.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.normalDNS, v => v.txtnormalDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainStrategy4Freedom2, v => v.cmbdomainStrategy4Out.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainDNSAddress2, v => v.cmbdomainDNSAddress2.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.normalDNS2, v => v.txtnormalDNS2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.tunDNS2, v => v.txttunDNS2.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ImportDefConfig4V2rayCmd, v => v.btnImportDefConfig4V2ray).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ImportDefConfig4SingboxCmd, v => v.btnImportDefConfig4Singbox).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
}
return await Task.FromResult(true);
}
private void linkDnsObjectDoc_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart("https://xtls.github.io/config/dns.html#dnsobject");
}
private void linkDnsSingboxObjectDoc_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/dns/");
}
}
}

View file

@ -0,0 +1,144 @@
<Window
x:Class="v2rayN.Desktop.Views.GlobalHotkeySettingWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSetting}"
Width="700"
Height="500"
x:DataType="vms:SubEditViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
Classes="Margin4"
HorizontalAlignment="Center"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnReset"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbReset}" />
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="gridText" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="400" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbGlobalHotkeySetting}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbDisplayGUI}" />
<TextBox
x:Name="txtGlobalHotkey0"
Grid.Row="1"
Grid.Column="1"
Classes="Margin4"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbClearSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey1"
Grid.Row="2"
Grid.Column="1"
Classes="Margin4"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSetSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey2"
Grid.Row="3"
Grid.Column="1"
Classes="Margin4"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbNotChangeSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey3"
Grid.Row="4"
Grid.Column="1"
Classes="Margin4"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSystemProxyPac}" />
<TextBox
x:Name="txtGlobalHotkey4"
Grid.Row="5"
Grid.Column="1"
Classes="Margin4"
VerticalAlignment="Center"
IsReadOnly="True" />
</Grid>
<TextBlock
Grid.Row="1"
Classes="Margin4"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbGlobalHotkeySettingTip}" />
</Grid>
</ScrollViewer>
</DockPanel>
</Window>

View file

@ -0,0 +1,129 @@
using Avalonia.Controls;
namespace v2rayN.Desktop.Views
{
public partial class GlobalHotkeySettingWindow : Window
{
private static Config _config = default!;
private Dictionary<object, KeyEventItem> _TextBoxKeyEventItem = default!;
public GlobalHotkeySettingWindow()
{
InitializeComponent();
btnCancel.Click += (s, e) => this.Close();
_config = LazyConfig.Instance.Config;
//_config.globalHotkeys ??= new List<KeyEventItem>();
//txtGlobalHotkey0.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey1.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey2.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey3.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey4.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//HotkeyHandler.Instance.IsPause = true;
//this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
//InitData();
}
//private void InitData()
//{
// _TextBoxKeyEventItem = new()
// {
// { txtGlobalHotkey0,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.ShowForm) },
// { txtGlobalHotkey1,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxyClear) },
// { txtGlobalHotkey2,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxySet) },
// { txtGlobalHotkey3,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxyUnchanged)},
// { txtGlobalHotkey4,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxyPac)}
// };
// BindingData();
//}
//private void TxtGlobalHotkey_PreviewKeyDown(object? sender, KeyEventArgs e)
//{
// e.Handled = true;
// var _ModifierKeys = new Key[] { Key.LeftCtrl, Key.RightCtrl, Key.LeftShift,
// Key.RightShift, Key.LeftAlt, Key.RightAlt, Key.LWin, Key.RWin};
// _TextBoxKeyEventItem[sender].KeyCode = (int)(e.Key == Key.System ? (_ModifierKeys.Contains(e.SystemKey) ? Key.None : e.SystemKey) : (_ModifierKeys.Contains(e.Key) ? Key.None : e.Key));
// _TextBoxKeyEventItem[sender].Alt = (Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt;
// _TextBoxKeyEventItem[sender].Control = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
// _TextBoxKeyEventItem[sender].Shift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
// (sender as TextBox)!.Text = KeyEventItemToString(_TextBoxKeyEventItem[sender]);
//}
//private KeyEventItem GetKeyEventItemByEGlobalHotkey(List<KeyEventItem> KEList, EGlobalHotkey eg)
//{
// return JsonUtils.DeepCopy(KEList.Find((it) => it.eGlobalHotkey == eg) ?? new()
// {
// eGlobalHotkey = eg,
// Control = false,
// Alt = false,
// Shift = false,
// KeyCode = null
// });
//}
//private string KeyEventItemToString(KeyEventItem item)
//{
// var res = new StringBuilder();
// if (item.Control) res.Append($"{ModifierKeys.Control}+");
// if (item.Shift) res.Append($"{ModifierKeys.Shift}+");
// if (item.Alt) res.Append($"{ModifierKeys.Alt}+");
// if (item.KeyCode != null && (Key)item.KeyCode != Key.None)
// res.Append($"{(Key)item.KeyCode}");
// return res.ToString();
//}
//private void BindingData()
//{
// foreach (var item in _TextBoxKeyEventItem)
// {
// if (item.Value.KeyCode != null && (Key)item.Value.KeyCode != Key.None)
// {
// (item.Key as TextBox)!.Text = KeyEventItemToString(item.Value);
// }
// else
// {
// (item.Key as TextBox)!.Text = string.Empty;
// }
// }
//}
//private void btnSave_Click(object? sender, RoutedEventArgs e)
//{
// _config.globalHotkeys = _TextBoxKeyEventItem.Values.ToList();
// if (ConfigHandler.SaveConfig(_config, false) == 0)
// {
// HotkeyHandler.Instance.ReLoad();
// this.Close();
// }
// else
// {
// UI.Show(ResUI.OperationFailed);
// }
//}
//private void btnReset_Click(object? sender, RoutedEventArgs e)
//{
// foreach (var k in _TextBoxKeyEventItem.Keys)
// {
// _TextBoxKeyEventItem[k].Alt = false;
// _TextBoxKeyEventItem[k].Control = false;
// _TextBoxKeyEventItem[k].Shift = false;
// _TextBoxKeyEventItem[k].KeyCode = (int)Key.None;
// }
// BindingData();
//}
//private void GlobalHotkeySettingWindow_KeyDown(object? sender, KeyEventArgs e)
//{
// if (e.Key == Key.Escape)
// {
// this.Close();
// }
//}
}
}

View file

@ -0,0 +1,292 @@
<Window
x:Class="v2rayN.Desktop.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Width="900"
Height="700"
MinWidth="900"
x:DataType="vms:MainWindowViewModel"
Icon="/Assets/NotifyIcon1.ico"
ShowInTaskbar="True"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel>
<DockPanel Classes="Margin8" DockPanel.Dock="Top">
<ContentControl x:Name="conTheme" DockPanel.Dock="Right" />
<Menu Margin="0,1">
<MenuItem Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuServers}" />
</StackPanel>
</MenuItem.Header>
<MenuItem x:Name="menuAddServerViaClipboard" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
<MenuItem
x:Name="menuAddServerViaScan"
Header="{x:Static resx:ResUI.menuAddServerViaScan}"
IsVisible="False" />
<MenuItem x:Name="menuAddCustomServer" Header="{x:Static resx:ResUI.menuAddCustomServer}" />
<Separator />
<MenuItem x:Name="menuAddVmessServer" Header="{x:Static resx:ResUI.menuAddVmessServer}" />
<MenuItem x:Name="menuAddVlessServer" Header="{x:Static resx:ResUI.menuAddVlessServer}" />
<MenuItem x:Name="menuAddShadowsocksServer" Header="{x:Static resx:ResUI.menuAddShadowsocksServer}" />
<MenuItem x:Name="menuAddSocksServer" Header="{x:Static resx:ResUI.menuAddSocksServer}" />
<MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" />
<MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
<Separator />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem x:Name="menuAddWireguardServer" Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
</MenuItem>
<MenuItem Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuSubscription}" />
</StackPanel>
</MenuItem.Header>
<MenuItem x:Name="menuSubSetting" Header="{x:Static resx:ResUI.menuSubSetting}" />
<Separator />
<MenuItem x:Name="menuSubUpdate" Header="{x:Static resx:ResUI.menuSubUpdate}" />
<MenuItem x:Name="menuSubUpdateViaProxy" Header="{x:Static resx:ResUI.menuSubUpdateViaProxy}" />
<MenuItem x:Name="menuSubGroupUpdate" Header="{x:Static resx:ResUI.menuSubGroupUpdate}" />
<MenuItem x:Name="menuSubGroupUpdateViaProxy" Header="{x:Static resx:ResUI.menuSubGroupUpdateViaProxy}" />
</MenuItem>
<MenuItem Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuSetting}" />
</StackPanel>
</MenuItem.Header>
<MenuItem x:Name="menuOptionSetting" Header="{x:Static resx:ResUI.menuOptionSetting}" />
<MenuItem x:Name="menuRoutingSetting" Header="{x:Static resx:ResUI.menuRoutingSetting}" />
<MenuItem x:Name="menuDNSSetting" Header="{x:Static resx:ResUI.menuDNSSetting}" />
<MenuItem x:Name="menuGlobalHotkeySetting" Header="{x:Static resx:ResUI.menuGlobalHotkeySetting}" />
<Separator />
<MenuItem x:Name="menuRebootAsAdmin" Header="{x:Static resx:ResUI.menuRebootAsAdmin}" />
<MenuItem x:Name="menuSettingsSetUWP" Header="{x:Static resx:ResUI.TbSettingsSetUWP}" />
<MenuItem x:Name="menuClearServerStatistics" Header="{x:Static resx:ResUI.menuClearServerStatistics}" />
<Separator />
<MenuItem x:Name="menuOpenTheFileLocation" Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
</MenuItem>
<MenuItem x:Name="menuReload" Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuReload}" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuCheckUpdate}" />
</StackPanel>
</MenuItem.Header>
<MenuItem x:Name="menuCheckUpdateN" Header="V2rayN" />
<MenuItem x:Name="menuCheckUpdateXrayCore" Header="Xray Core" />
<MenuItem x:Name="menuCheckUpdateMihomoCore" Header="Mihomo Core" />
<MenuItem x:Name="menuCheckUpdateSingBoxCore" Header="Sing-box Core" />
<Separator />
<MenuItem x:Name="menuCheckUpdateGeo" Header="Geo files" />
</MenuItem>
<MenuItem x:Name="menuHelp" Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuHelp}" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="menuPromotion" Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuPromotion}" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="menuClose" Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuClose}" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
</Menu>
</DockPanel>
<StackPanel Height="50" DockPanel.Dock="Bottom">
<DockPanel>
<StackPanel
Margin="8,0"
VerticalAlignment="Center"
DockPanel.Dock="Right">
<TextBlock x:Name="txtSpeedProxyDisplay" />
<Border Margin="2" />
<TextBlock x:Name="txtSpeedDirectDisplay" />
</StackPanel>
<StackPanel
Width="240"
Margin="8,0"
VerticalAlignment="Center"
DockPanel.Dock="Left">
<TextBlock x:Name="txtInboundDisplay" />
<Border Margin="2" />
<TextBlock x:Name="txtInboundLanDisplay" />
</StackPanel>
<StackPanel
x:Name="spEnableTun"
Width="100"
Margin="8,0"
VerticalAlignment="Center"
DockPanel.Dock="Left">
<TextBlock Text="{x:Static resx:ResUI.TbEnableTunAs}" />
<ToggleSwitch
x:Name="togEnableTun"
HorizontalAlignment="Left"
Classes="Margin4" />
</StackPanel>
<StackPanel
Margin="8,0"
VerticalAlignment="Center"
DockPanel.Dock="Left"
Orientation="Horizontal">
<ComboBox
x:Name="cmbSystemProxy"
Width="120"
Margin="8,0"
ToolTip.Tip="{x:Static resx:ResUI.menuSystemproxy}">
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyClear}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxySet}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyNothing}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyPac}" />
</ComboBox>
<ComboBox
x:Name="cmbRoutings2"
Width="150"
Margin="8,0"
DisplayMemberBinding="{Binding remarks}"
ItemsSource="{Binding RoutingItems}"
ToolTip.Tip="{x:Static resx:ResUI.menuRouting}" />
</StackPanel>
<StackPanel Margin="8,0" VerticalAlignment="Center">
<TextBlock x:Name="txtRunningServerDisplay" Tapped="TxtRunningServerDisplay_Tapped" />
<Border Margin="2" />
<TextBlock x:Name="txtRunningInfoDisplay" Tapped="TxtRunningServerDisplay_Tapped" />
</StackPanel>
</DockPanel>
</StackPanel>
<Grid>
<Grid x:Name="gridMain" IsVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<ContentControl x:Name="tabProfiles" Grid.Column="0" />
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
<TabControl
x:Name="tabMain"
Grid.Column="2"
HorizontalContentAlignment="Left">
<TabItem x:Name="tabMsgView" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
<TabItem x:Name="tabClashProxies" Header="{x:Static resx:ResUI.TbProxies}" />
<TabItem x:Name="tabClashConnections" Header="{x:Static resx:ResUI.TbConnections}" />
</TabControl>
</Grid>
<Grid x:Name="gridMain1" IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="10" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<ContentControl x:Name="tabProfiles1" Grid.Row="0" />
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" />
<TabControl
x:Name="tabMain1"
Grid.Row="2"
TabStripPlacement="Left">
<TabItem x:Name="tabMsgView1">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.MsgInformationTitle}" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem x:Name="tabClashProxies1">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.TbProxies}" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem x:Name="tabClashConnections1">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.TbConnections}" />
</StackPanel>
</TabItem.Header>
</TabItem>
</TabControl>
</Grid>
<Grid x:Name="gridMain2" IsVisible="False">
<TabControl x:Name="tabMain2" TabStripPlacement="Left">
<TabItem x:Name="tabProfiles2">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.menuServers}" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem x:Name="tabMsgView2">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.MsgInformationTitle}" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem x:Name="tabClashProxies2">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.TbProxies}" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem x:Name="tabClashConnections2">
<TabItem.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{x:Static resx:ResUI.TbConnections}" />
</StackPanel>
</TabItem.Header>
</TabItem>
</TabControl>
</Grid>
</Grid>
</DockPanel>
</Window>

View file

@ -0,0 +1,474 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
using Splat;
using System.ComponentModel;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
{
private static Config _config;
private WindowNotificationManager? _manager;
public MainWindow()
{
InitializeComponent();
_config = LazyConfig.Instance.Config;
_manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.BottomRight };
//ThreadPool.RegisterWaitForSingleObject(App.ProgramStarted, OnProgramStarted, null, -1, false);
this.Closing += MainWindow_Closing;
this.KeyDown += MainWindow_KeyDown;
menuSettingsSetUWP.Click += menuSettingsSetUWP_Click;
menuPromotion.Click += menuPromotion_Click;
menuClose.Click += menuClose_Click;
MessageBus.Current.Listen<string>(Global.CommandSendSnackMsg).Subscribe(x => DelegateSnackMsg(x));
ViewModel = new MainWindowViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel));
//WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
this.WhenActivated(disposables =>
{
//servers
this.BindCommand(ViewModel, vm => vm.AddVmessServerCmd, v => v.menuAddVmessServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddVlessServerCmd, v => v.menuAddVlessServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddShadowsocksServerCmd, v => v.menuAddShadowsocksServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSocksServerCmd, v => v.menuAddSocksServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddHttpServerCmd, v => v.menuAddHttpServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTrojanServerCmd, v => v.menuAddTrojanServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
//sub
this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubUpdateCmd, v => v.menuSubUpdate).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubUpdateViaProxyCmd, v => v.menuSubUpdateViaProxy).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubGroupUpdateCmd, v => v.menuSubGroupUpdate).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubGroupUpdateViaProxyCmd, v => v.menuSubGroupUpdateViaProxy).DisposeWith(disposables);
//setting
this.BindCommand(ViewModel, vm => vm.OptionSettingCmd, v => v.menuOptionSetting).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingSettingCmd, v => v.menuRoutingSetting).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.DNSSettingCmd, v => v.menuDNSSetting).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GlobalHotkeySettingCmd, v => v.menuGlobalHotkeySetting).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.OpenTheFileLocationCmd, v => v.menuOpenTheFileLocation).DisposeWith(disposables);
//check update
this.BindCommand(ViewModel, vm => vm.CheckUpdateNCmd, v => v.menuCheckUpdateN).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateXrayCoreCmd, v => v.menuCheckUpdateXrayCore).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateClashMetaCoreCmd, v => v.menuCheckUpdateMihomoCore).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateSingBoxCoreCmd, v => v.menuCheckUpdateSingBoxCore).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateGeoCmd, v => v.menuCheckUpdateGeo).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
//status bar
this.OneWayBind(ViewModel, vm => vm.InboundDisplay, v => v.txtInboundDisplay.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.InboundLanDisplay, v => v.txtInboundLanDisplay.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.RunningServerDisplay, v => v.txtRunningServerDisplay.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.RunningInfoDisplay, v => v.txtRunningInfoDisplay.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.SpeedProxyDisplay, v => v.txtSpeedProxyDisplay.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.SpeedDirectDisplay, v => v.txtSpeedDirectDisplay.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableTun, v => v.togEnableTun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SystemProxySelected, v => v.cmbSystemProxy.SelectedIndex).DisposeWith(disposables);
//this.OneWayBind(ViewModel, vm => vm.RoutingItems, v => v.cmbRoutings2.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting, v => v.cmbRoutings2.SelectedItem).DisposeWith(disposables);
if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Horizontal)
{
gridMain.IsVisible = true;
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies.IsVisible).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections.IsVisible).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain.SelectedIndex).DisposeWith(disposables);
}
else if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Vertical)
{
gridMain1.IsVisible = true;
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies1.IsVisible).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections1.IsVisible).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain1.SelectedIndex).DisposeWith(disposables);
}
else
{
gridMain2.IsVisible = true;
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies2.IsVisible).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections2.IsVisible).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(disposables);
}
});
if (Utils.IsWindows())
{
var IsAdministrator = false;//WindowsUtils.IsAdministrator();
ViewModel.IsAdministrator = IsAdministrator;
this.Title = $"{Utils.GetVersion()} - {(IsAdministrator ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
if (_config.tunModeItem.enableTun)
{
if (IsAdministrator)
{
ViewModel.EnableTun = true;
}
else
{
_config.tunModeItem.enableTun = ViewModel.EnableTun = false;
}
}
}
else
{
ViewModel.IsAdministrator = true;
this.Title = $"{Utils.GetVersion()}";
menuRebootAsAdmin.IsVisible = false;
menuSettingsSetUWP.IsVisible = false;
menuGlobalHotkeySetting.IsVisible = false;
menuOpenTheFileLocation.IsVisible = false;
cmbSystemProxy.IsVisible = false;
if (_config.tunModeItem.enableTun)
{
ViewModel.EnableTun = true;
}
}
if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Horizontal)
{
tabProfiles.Content ??= new ProfilesView(this);
tabMsgView.Content ??= new MsgView();
tabClashProxies.Content ??= new ClashProxiesView();
tabClashConnections.Content ??= new ClashConnectionsView();
}
else if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Vertical)
{
tabProfiles1.Content ??= new ProfilesView(this);
tabMsgView1.Content ??= new MsgView();
tabClashProxies1.Content ??= new ClashProxiesView();
tabClashConnections1.Content ??= new ClashConnectionsView();
}
else
{
tabProfiles2.Content ??= new ProfilesView(this);
tabMsgView2.Content ??= new MsgView();
tabClashProxies2.Content ??= new ClashProxiesView();
tabClashConnections2.Content ??= new ClashConnectionsView();
}
conTheme.Content ??= new ThemeSettingView();
RestoreUI();
AddHelpMenuItem();
}
#region Event
private void OnProgramStarted(object state, bool timeout)
{
ShowHideWindow(true);
}
private void DelegateSnackMsg(string content)
{
Dispatcher.UIThread.Post(() =>
_manager?.Show(new Notification(null, content, NotificationType.Information)),
DispatcherPriority.Normal);
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.AddServerWindow:
if (obj is null) return false;
return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.AddServer2Window:
if (obj is null) return false;
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.DNSSettingWindow:
return await new DNSSettingWindow().ShowDialog<bool>(this);
case EViewAction.RoutingSettingWindow:
return await new RoutingSettingWindow().ShowDialog<bool>(this);
case EViewAction.OptionSettingWindow:
return await new OptionSettingWindow().ShowDialog<bool>(this);
case EViewAction.GlobalHotkeySettingWindow:
return await new GlobalHotkeySettingWindow().ShowDialog<bool>(this);
case EViewAction.SubSettingWindow:
return await new SubSettingWindow().ShowDialog<bool>(this);
case EViewAction.ShowHideWindow:
Dispatcher.UIThread.Post(() =>
ShowHideWindow((bool?)obj),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherStatistics:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.SetStatisticsResult((ServerSpeedItem)obj),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherServerAvailability:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.TestServerAvailabilityResult((string)obj),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherReload:
Dispatcher.UIThread.Post(() =>
ViewModel?.ReloadResult(),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherRefreshServersBiz:
Dispatcher.UIThread.Post(() =>
ViewModel?.RefreshServersBiz(),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherRefreshIcon:
Dispatcher.UIThread.Post(() =>
{
this.Icon = AvaUtils.GetAppIcon(_config.systemProxyItem.sysProxyType);
},
DispatcherPriority.Default);
break;
case EViewAction.Shutdown:
StorageUI();
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.Shutdown();
}
break;
case EViewAction.ScanScreenTask:
ScanScreenTaskAsync().ContinueWith(_ => { });
break;
case EViewAction.UpdateSysProxy:
if (obj is null) return false;
// await SysProxyHandler.UpdateSysProxy(_config, (bool)obj);
break;
case EViewAction.AddServerViaClipboard:
var clipboardData = await AvaUtils.GetClipboardData(this);
ViewModel?.AddServerViaClipboardAsync(clipboardData);
break;
}
return await Task.FromResult(true);
}
private void OnHotkeyHandler(EGlobalHotkey e)
{
switch (e)
{
case EGlobalHotkey.ShowForm:
ShowHideWindow(null);
break;
case EGlobalHotkey.SystemProxyClear:
ViewModel?.SetListenerType(ESysProxyType.ForcedClear);
break;
case EGlobalHotkey.SystemProxySet:
ViewModel?.SetListenerType(ESysProxyType.ForcedChange);
break;
case EGlobalHotkey.SystemProxyUnchanged:
ViewModel?.SetListenerType(ESysProxyType.Unchanged);
break;
case EGlobalHotkey.SystemProxyPac:
ViewModel?.SetListenerType(ESysProxyType.Pac);
break;
}
}
private void MainWindow_Closing(object? sender, CancelEventArgs e)
{
e.Cancel = true;
ShowHideWindow(false);
}
private async void MainWindow_KeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers == KeyModifiers.Control)
{
switch (e.Key)
{
case Key.V:
var clipboardData = await AvaUtils.GetClipboardData(this);
ViewModel?.AddServerViaClipboardAsync(clipboardData);
break;
case Key.S:
ScanScreenTaskAsync().ContinueWith(_ => { });
break;
}
}
else
{
if (e.Key == Key.F5)
{
ViewModel?.Reload();
}
}
}
private void menuClose_Click(object? sender, RoutedEventArgs e)
{
StorageUI();
ShowHideWindow(false);
}
private void menuPromotion_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart($"{Utils.Base64Decode(Global.PromotionUrl)}?t={DateTime.Now.Ticks}");
}
private void TxtRunningServerDisplay_Tapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
ViewModel?.TestServerAvailability();
}
private void menuSettingsSetUWP_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
}
public async Task ScanScreenTaskAsync()
{
ShowHideWindow(false);
//var dpiXY = QRCodeHelper.GetDpiXY(Application.Current.MainWindow);
//string result = await Task.Run(() =>
//{
// return QRCodeHelper.ScanScreen(dpiXY.Item1, dpiXY.Item2);
//});
ShowHideWindow(true);
//ViewModel?.ScanScreenTaskAsync(result);
}
#endregion Event
#region UI
public void ShowHideWindow(bool? blShow)
{
var bl = blShow ?? !_config.uiItem.showInTaskbar;
if (bl)
{
this.Show();
if (this.WindowState == WindowState.Minimized)
{
this.WindowState = WindowState.Normal;
}
this.Activate();
this.Focus();
}
else
{
this.Hide();
}
_config.uiItem.showInTaskbar = bl;
}
private void RestoreUI()
{
if (_config.uiItem.mainWidth > 0 && _config.uiItem.mainHeight > 0)
{
Width = _config.uiItem.mainWidth;
Height = _config.uiItem.mainHeight;
}
if (_config.uiItem.mainGirdHeight1 > 0 && _config.uiItem.mainGirdHeight2 > 0)
{
if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Horizontal)
{
gridMain.ColumnDefinitions[0].Width = new GridLength(_config.uiItem.mainGirdHeight1, GridUnitType.Star);
gridMain.ColumnDefinitions[2].Width = new GridLength(_config.uiItem.mainGirdHeight2, GridUnitType.Star);
}
else if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Vertical)
{
gridMain1.RowDefinitions[0].Height = new GridLength(_config.uiItem.mainGirdHeight1, GridUnitType.Star);
gridMain1.RowDefinitions[2].Height = new GridLength(_config.uiItem.mainGirdHeight2, GridUnitType.Star);
}
}
}
private void StorageUI()
{
_config.uiItem.mainWidth = Utils.ToInt(this.Width);
_config.uiItem.mainHeight = Utils.ToInt(this.Height);
if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Horizontal)
{
_config.uiItem.mainGirdHeight1 = Math.Ceiling(gridMain.ColumnDefinitions[0].ActualWidth + 0.1);
_config.uiItem.mainGirdHeight2 = Math.Ceiling(gridMain.ColumnDefinitions[2].ActualWidth + 0.1);
}
else if (_config.uiItem.mainGirdOrientation == EGirdOrientation.Vertical)
{
_config.uiItem.mainGirdHeight1 = Math.Ceiling(gridMain1.RowDefinitions[0].ActualHeight + 0.1);
_config.uiItem.mainGirdHeight2 = Math.Ceiling(gridMain1.RowDefinitions[2].ActualHeight + 0.1);
}
ConfigHandler.SaveConfig(_config);
}
private void AddHelpMenuItem()
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
foreach (var it in coreInfo
.Where(t => t.coreType != ECoreType.v2fly
&& t.coreType != ECoreType.clash
&& t.coreType != ECoreType.clash_meta
&& t.coreType != ECoreType.hysteria))
{
var item = new MenuItem()
{
Tag = it.coreUrl.Replace(@"/releases", ""),
Header = string.Format(ResUI.menuWebsiteItem, it.coreType.ToString().Replace("_", " ")).UpperFirstChar()
};
item.Click += MenuItem_Click;
menuHelp.Items.Add(item);
}
}
private void MenuItem_Click(object? sender, RoutedEventArgs e)
{
if (sender is MenuItem item)
{
Utils.ProcessStart(item.Tag.ToString());
}
}
#endregion UI
}
}

View file

@ -0,0 +1,107 @@
<UserControl
x:Class="v2rayN.Desktop.Views.MsgView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<DockPanel Margin="2">
<WrapPanel
VerticalAlignment="Center"
Classes="Margin8"
DockPanel.Dock="Top"
Orientation="Horizontal">
<TextBox
x:Name="cmbMsgFilter"
Width="200"
Margin="8,0"
VerticalContentAlignment="Center"
ToolTip.Tip="{x:Static resx:ResUI.MsgFilterTitle}" />
<Button
x:Name="btnCopy"
Width="24"
Height="24"
Margin="8,0"
Classes="Success"
Click="menuMsgViewCopyAll_Click"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuMsgViewCopyAll}">
<Button.Content>
<Image
Width="16"
Height="16"
Source="/Assets/copy.png" />
</Button.Content>
</Button>
<Button
x:Name="btnClear"
Width="24"
Height="24"
Margin="8,0"
Classes="Success"
Click="menuMsgViewClear_Click"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuMsgViewClear}">
<Button.Content>
<Image
Width="16"
Height="16"
Source="/Assets/delete.png" />
</Button.Content>
</Button>
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAutoRefresh}" />
<ToggleSwitch
x:Name="togAutoRefresh"
Margin="8,0"
HorizontalAlignment="Left"
IsChecked="True" />
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
IsVisible="False"
Text="{x:Static resx:ResUI.TbAutoScrollToEnd}" />
<ToggleSwitch
x:Name="togScrollToEnd"
Margin="8,0"
HorizontalAlignment="Left"
IsChecked="True"
IsVisible="False" />
</WrapPanel>
<TextBox
Name="txtMsg"
BorderThickness="0"
Classes="TextArea"
IsReadOnly="True"
TextAlignment="Left"
TextWrapping="Wrap">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem
x:Name="menuMsgViewSelectAll"
Click="menuMsgViewSelectAll_Click"
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
<MenuItem
x:Name="menuMsgViewCopy"
Click="menuMsgViewCopy_Click"
Header="{x:Static resx:ResUI.menuMsgViewCopy}" />
<MenuItem
x:Name="menuMsgViewCopyAll"
Click="menuMsgViewCopyAll_Click"
Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" />
<MenuItem
x:Name="menuMsgViewClear"
Click="menuMsgViewClear_Click"
Header="{x:Static resx:ResUI.menuMsgViewClear}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,127 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using ReactiveUI;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class MsgView : UserControl
{
private static Config? _config;
private string lastMsgFilter = string.Empty;
private bool lastMsgFilterNotAvailable;
private ConcurrentBag<string> _lstMsg = [];
public MsgView()
{
InitializeComponent();
_config = LazyConfig.Instance.Config;
MessageBus.Current.Listen<string>(Global.CommandSendMsgView).Subscribe(x => DelegateAppendText(x));
//Global.PresetMsgFilters.ForEach(it =>
//{
// cmbMsgFilter.Items.Add(it);
//});
if (!_config.uiItem.mainMsgFilter.IsNullOrEmpty())
{
cmbMsgFilter.Text = _config.uiItem.mainMsgFilter;
}
cmbMsgFilter.TextChanged += (s, e) =>
{
_config.uiItem.mainMsgFilter = cmbMsgFilter.Text?.ToString();
};
}
private void DelegateAppendText(string msg)
{
Dispatcher.UIThread.Post(() => AppendText(msg), DispatcherPriority.ApplicationIdle);
}
public void AppendText(string msg)
{
if (msg == Global.CommandClearMsg)
{
ClearMsg();
return;
}
if (togAutoRefresh.IsChecked == false)
{
return;
}
var MsgFilter = cmbMsgFilter.Text?.ToString();
if (MsgFilter != lastMsgFilter) lastMsgFilterNotAvailable = false;
if (!Utils.IsNullOrEmpty(MsgFilter) && !lastMsgFilterNotAvailable)
{
try
{
if (!Regex.IsMatch(msg, MsgFilter))
{
return;
}
}
catch (Exception)
{
lastMsgFilterNotAvailable = true;
}
}
lastMsgFilter = MsgFilter;
ShowMsg(msg);
if (togScrollToEnd.IsChecked ?? true)
{
}
}
private void ShowMsg(string msg)
{
if (_lstMsg.Count > 999)
{
ClearMsg();
}
if (!msg.EndsWith(Environment.NewLine))
{
_lstMsg.Add(Environment.NewLine);
}
_lstMsg.Add(msg);
// if (!msg.EndsWith(Environment.NewLine))
// {
// _lstMsg.Add(Environment.NewLine);
// }
this.txtMsg.Text = string.Join("", _lstMsg);
}
public void ClearMsg()
{
_lstMsg.Clear();
txtMsg.Clear();
}
private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e)
{
txtMsg.Focus();
txtMsg.SelectAll();
}
private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e)
{
var data = txtMsg.SelectedText.TrimEx();
await AvaUtils.SetClipboardData(this, data);
}
private async void menuMsgViewCopyAll_Click(object? sender, RoutedEventArgs e)
{
var data = txtMsg.Text.TrimEx();
await AvaUtils.SetClipboardData(this, data);
}
private void menuMsgViewClear_Click(object? sender, RoutedEventArgs e)
{
ClearMsg();
}
}
}

View file

@ -0,0 +1,857 @@
<Window
x:Class="v2rayN.Desktop.Views.OptionSettingWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSetting}"
Width="1000"
Height="700"
x:DataType="vms:OptionSettingViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<TabControl HorizontalContentAlignment="Left">
<TabItem Header="{x:Static resx:ResUI.TbSettingsCore}">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid Classes="Margin8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSocksPort}" />
<TextBox
x:Name="txtlocalPort"
Grid.Row="0"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSocksPortTip}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsUdpEnabled}" />
<ToggleSwitch
x:Name="togudpEnabled"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSniffingEnabled}" />
<StackPanel
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="2"
Orientation="Horizontal">
<ToggleSwitch
x:Name="togsniffingEnabled"
HorizontalAlignment="Left"
Classes="Margin8" />
<ListBox
x:Name="clbdestOverride"
HorizontalAlignment="Left"
Classes="Margin4"
SelectionMode="Multiple"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
</StackPanel>
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsRouteOnly}" />
<ToggleSwitch
x:Name="togrouteOnly"
Grid.Row="3"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsAllowLAN}" />
<ToggleSwitch
x:Name="togAllowLANConn"
Grid.Row="4"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsNewPort4LAN}" />
<ToggleSwitch
x:Name="togNewPort4LAN"
Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsUser}" />
<TextBox
x:Name="txtuser"
Grid.Row="6"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsPass}" />
<TextBox
x:Name="txtpass"
Grid.Row="7"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
<ToggleSwitch
x:Name="togmuxEnabled"
Grid.Row="8"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsLogEnabledToFile}" />
<ToggleSwitch
x:Name="toglogEnabled"
Grid.Row="9"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsLogLevel}" />
<ComboBox
x:Name="cmbloglevel"
Grid.Row="10"
Grid.Column="1"
Width="200"
Classes="Margin8"
ToolTip.Tip="Level" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDefAllowInsecure}" />
<ToggleSwitch
x:Name="togdefAllowInsecure"
Grid.Row="11"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="12"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDefFingerprint}" />
<ComboBox
x:Name="cmbdefFingerprint"
Grid.Row="12"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="13"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDefUserAgent}" />
<ComboBox
x:Name="cmbdefUserAgent"
Grid.Row="13"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="13"
Grid.Column="3"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDefUserAgentTips}" />
<TextBlock
Grid.Row="14"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsMux4SboxProtocol}" />
<ComboBox
x:Name="cmbmux4SboxProtocol"
Grid.Row="14"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="15"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableCacheFile4Sbox}" />
<ToggleSwitch
x:Name="togenableCacheFile4Sbox"
Grid.Row="15"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="16"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsHysteriaBandwidth}" />
<StackPanel
Grid.Row="16"
Grid.Column="1"
Orientation="Horizontal">
<TextBox
x:Name="txtUpMbps"
Width="90"
Classes="Margin8"
ToolTip.Tip="Up" />
<TextBox
x:Name="txtDownMbps"
Width="90"
Classes="Margin8"
ToolTip.Tip="Down" />
</StackPanel>
<TextBlock
Grid.Row="17"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableFragment}" />
<ToggleSwitch
x:Name="togenableFragment"
Grid.Row="17"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="17"
Grid.Column="3"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableFragmentTips}" />
</Grid>
</ScrollViewer>
</TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsN}">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid Grid.Row="2" Classes="Margin8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsStartBoot}" />
<ToggleSwitch
x:Name="togAutoRun"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsStartBootTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsStatistics}" />
<ToggleSwitch
x:Name="togEnableStatistics"
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsKeepOlderDedupl}" />
<ToggleSwitch
x:Name="togKeepOlderDedupl"
Grid.Row="3"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsIgnoreGeoUpdateCore}" />
<ToggleSwitch
x:Name="togIgnoreGeoUpdateCore"
Grid.Row="4"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableAutoAdjustMainLvColWidth}" />
<ToggleSwitch
x:Name="togEnableAutoAdjustMainLvColWidth"
Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableUpdateSubOnlyRemarksExist}" />
<ToggleSwitch
x:Name="togEnableUpdateSubOnlyRemarksExist"
Grid.Row="6"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsTLS13}" />
<ToggleSwitch
x:Name="togEnableSecurityProtocolTls13"
Grid.Row="7"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsAutoHideStartup}" />
<ToggleSwitch
x:Name="togAutoHideStartup"
Grid.Row="8"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
<ToggleSwitch
x:Name="togEnableCheckPreReleaseUpdate"
Grid.Row="9"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableDragDropSort}" />
<ToggleSwitch
x:Name="togEnableDragDropSort"
Grid.Row="10"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsDoubleClick2Activate}" />
<ToggleSwitch
x:Name="togDoubleClick2Activate"
Grid.Row="11"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="14"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsAutoUpdateInterval}" />
<TextBox
x:Name="txtautoUpdateInterval"
Grid.Row="14"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="16"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
IsVisible="False"
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamily}" />
<ComboBox
x:Name="cmbcurrentFontFamily"
Grid.Row="16"
Grid.Column="1"
Width="200"
Classes="Margin8"
IsVisible="False"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="16"
Grid.Column="2"
VerticalAlignment="Center"
Classes="Margin8"
IsVisible="False"
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamilyTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="17"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSpeedTestTimeout}" />
<ComboBox
x:Name="cmbSpeedTestTimeout"
Grid.Row="17"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="18"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSpeedTestUrl}" />
<ComboBox
x:Name="cmbSpeedTestUrl"
Grid.Row="18"
Grid.Column="1"
Width="300"
Classes="Margin8" />
<TextBlock
Grid.Row="19"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSpeedPingTestUrl}" />
<ComboBox
x:Name="cmbSpeedPingTestUrl"
Grid.Row="19"
Grid.Column="1"
Width="300"
Classes="Margin8" />
<TextBlock
Grid.Row="20"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
<ComboBox
x:Name="cmbSubConvertUrl"
Grid.Row="20"
Grid.Column="1"
Width="300"
Classes="Margin8"
ToolTip.Tip="Convert Url" />
<TextBlock
Grid.Row="21"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsMainGirdOrientation}" />
<ComboBox
x:Name="cmbMainGirdOrientation"
Grid.Row="21"
Grid.Column="1"
Width="300"
Classes="Margin8" />
</Grid>
</ScrollViewer>
</TabItem>
<TabItem Name="tabSystemproxy" Header="{x:Static resx:ResUI.TbSettingsSystemproxy}">
<DockPanel Classes="Margin8">
<StackPanel DockPanel.Dock="Bottom" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsNotProxyLocalAddress}" />
<ToggleSwitch
x:Name="tognotProxyLocalAddress"
HorizontalAlignment="Left"
Classes="Margin8" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsAdvancedProtocol}" />
<ComboBox
x:Name="cmbsystemProxyAdvancedProtocol"
MinWidth="400"
Classes="Margin8"
ToolTip.Tip="Protocol" />
</StackPanel>
</StackPanel>
<TextBlock
Grid.Row="1"
VerticalAlignment="Center"
Classes="Margin8"
DockPanel.Dock="Top"
Text="{x:Static resx:ResUI.TbSettingsExceptionTip}" />
<TextBox
x:Name="txtsystemProxyExceptions"
VerticalAlignment="Stretch"
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap" />
</DockPanel>
</TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsTunMode}">
<DockPanel Classes="Margin8">
<Grid Classes="Margin8" DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Strict Route" />
<ToggleSwitch
x:Name="togStrictRoute"
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Stack" />
<ComboBox
x:Name="cmbStack"
Grid.Row="3"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Mtu" />
<ComboBox
x:Name="cmbMtu"
Grid.Row="4"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableExInbound}" />
<ToggleSwitch
x:Name="togEnableExInbound"
Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableIPv6Address}" />
<ToggleSwitch
x:Name="togEnableIPv6Address"
Grid.Row="6"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
</Grid>
</DockPanel>
</TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}">
<Grid Classes="Margin8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="VMess" />
<ComboBox
x:Name="cmbCoreType1"
Grid.Row="1"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Custom" />
<ComboBox
x:Name="cmbCoreType2"
Grid.Row="2"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreType3"
Grid.Row="3"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Socks" />
<ComboBox
x:Name="cmbCoreType4"
Grid.Row="4"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreType5"
Grid.Row="5"
Grid.Column="1"
Width="200"
Classes="Margin8" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreType6"
Grid.Row="6"
Grid.Column="1"
Width="200"
Classes="Margin8" />
</Grid>
</TabItem>
</TabControl>
</DockPanel>
</Window>

View file

@ -0,0 +1,232 @@
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel>
{
private static Config _config;
public OptionSettingWindow()
{
InitializeComponent();
btnCancel.Click += (s, e) => this.Close();
_config = LazyConfig.Instance.Config;
// var lstFonts = GetFonts(Utils.GetFontsPath());
ViewModel = new OptionSettingViewModel(UpdateViewHandler);
clbdestOverride.SelectionChanged += ClbdestOverride_SelectionChanged;
Global.destOverrideProtocols.ForEach(it =>
{
clbdestOverride.Items.Add(it);
});
_config.inbound[0].destOverride?.ForEach(it =>
{
clbdestOverride.SelectedItems.Add(it);
});
Global.IEProxyProtocols.ForEach(it =>
{
cmbsystemProxyAdvancedProtocol.Items.Add(it);
});
Global.LogLevels.ForEach(it =>
{
cmbloglevel.Items.Add(it);
});
Global.Fingerprints.ForEach(it =>
{
cmbdefFingerprint.Items.Add(it);
});
Global.UserAgent.ForEach(it =>
{
cmbdefUserAgent.Items.Add(it);
});
Global.SingboxMuxs.ForEach(it =>
{
cmbmux4SboxProtocol.Items.Add(it);
});
Global.TunMtus.ForEach(it =>
{
cmbMtu.Items.Add(it);
});
Global.TunStacks.ForEach(it =>
{
cmbStack.Items.Add(it);
});
Global.CoreTypes.ForEach(it =>
{
cmbCoreType1.Items.Add(it);
cmbCoreType2.Items.Add(it);
cmbCoreType3.Items.Add(it);
cmbCoreType4.Items.Add(it);
cmbCoreType5.Items.Add(it);
cmbCoreType6.Items.Add(it);
});
for (int i = 2; i <= 6; i++)
{
cmbSpeedTestTimeout.Items.Add(i * 5);
}
Global.SpeedTestUrls.ForEach(it =>
{
cmbSpeedTestUrl.Items.Add(it);
});
Global.SpeedPingTestUrls.ForEach(it =>
{
cmbSpeedPingTestUrl.Items.Add(it);
});
Global.SubConvertUrls.ForEach(it =>
{
cmbSubConvertUrl.Items.Add(it);
});
foreach (EGirdOrientation it in Enum.GetValues(typeof(EGirdOrientation)))
{
cmbMainGirdOrientation.Items.Add(it.ToString());
}
//lstFonts.ForEach(it => { cmbcurrentFontFamily.Items.Add(it); });
//cmbcurrentFontFamily.Items.Add(string.Empty);
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.localPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.udpEnabled, v => v.togudpEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.routeOnly, v => v.togrouteOnly.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.allowLANConn, v => v.togAllowLANConn.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtuser.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.user, v => v.txtuser.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.muxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.logEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.loglevel, v => v.cmbloglevel.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.KeepOlderDedupl, v => v.togKeepOlderDedupl.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IgnoreGeoUpdateCore, v => v.togIgnoreGeoUpdateCore.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableAutoAdjustMainLvColWidth, v => v.togEnableAutoAdjustMainLvColWidth.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableUpdateSubOnlyRemarksExist, v => v.togEnableUpdateSubOnlyRemarksExist.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableSecurityProtocolTls13, v => v.togEnableSecurityProtocolTls13.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoHideStartup, v => v.togAutoHideStartup.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCheckPreReleaseUpdate, v => v.togEnableCheckPreReleaseUpdate.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableDragDropSort, v => v.togEnableDragDropSort.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DoubleClick2Activate, v => v.togDoubleClick2Activate.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoUpdateInterval, v => v.txtautoUpdateInterval.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CurrentFontFamily, v => v.cmbcurrentFontFamily.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SpeedTestTimeout, v => v.cmbSpeedTestTimeout.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SpeedTestUrl, v => v.cmbSpeedTestUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SpeedPingTestUrl, v => v.cmbSpeedPingTestUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SubConvertUrl, v => v.cmbSubConvertUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.MainGirdOrientation, v => v.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableExInbound, v => v.togEnableExInbound.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType3, v => v.cmbCoreType3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType4, v => v.cmbCoreType4.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType5, v => v.cmbCoreType5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
//if (Utils.IsWindows())
//{
//}
//else
//{
tabSystemproxy.IsVisible = false;
//}
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
// WindowsUtils.SetAutoRun(Global.AutoRunRegPath, Global.AutoRunName, togAutoRun.IsChecked ?? false);
this.Close(true);
break;
}
return await Task.FromResult(true);
}
//private List<string> GetFonts(string path)
//{
// var lstFonts = new List<string>();
// try
// {
// string[] searchPatterns = { "*.ttf", "*.ttc" };
// var files = new List<string>();
// foreach (var pattern in searchPatterns)
// {
// files.AddRange(Directory.GetFiles(path, pattern));
// }
// var culture = _config.uiItem.currentLanguage == Global.Languages[0] ? "zh-cn" : "en-us";
// var culture2 = "en-us";
// foreach (var ttf in files)
// {
// var families = Fonts.GetFontFamilies(Utils.GetFontsPath(ttf));
// foreach (FontFamily family in families)
// {
// var typefaces = family.GetTypefaces();
// foreach (Typeface typeface in typefaces)
// {
// typeface.TryGetGlyphTypeface(out GlyphTypeface glyph);
// //var fontFace = glyph.Win32FaceNames[new CultureInfo("en-us")];
// //if (!fontFace.Equals("Regular") && !fontFace.Equals("Normal"))
// //{
// // continue;
// //}
// var fontFamily = glyph.Win32FamilyNames[new CultureInfo(culture)];
// if (Utils.IsNullOrEmpty(fontFamily))
// {
// fontFamily = glyph.Win32FamilyNames[new CultureInfo(culture2)];
// if (Utils.IsNullOrEmpty(fontFamily))
// {
// continue;
// }
// }
// lstFonts.Add(fontFamily);
// break;
// }
// }
// }
// }
// catch (Exception ex)
// {
// Logging.SaveLog("fill fonts error", ex);
// }
// return lstFonts;
//}
private void ClbdestOverride_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
}
}
}

View file

@ -0,0 +1,214 @@
<UserControl
x:Class="v2rayN.Desktop.Views.ProfilesView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="using:v2rayN.Desktop.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:ProfilesViewModel"
mc:Ignorable="d">
<UserControl.Resources>
<conv:DelayColorConverter x:Key="DelayColorConverter" />
</UserControl.Resources>
<Grid>
<DockPanel>
<WrapPanel Margin="2" DockPanel.Dock="Top">
<ListBox
x:Name="lstGroup"
MaxHeight="200"
DisplayMemberBinding="{Binding remarks}"
ItemsSource="{Binding SubItems}"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
<Button
x:Name="btnEditSub"
Width="30"
Height="30"
Margin="4,0"
Classes="Success"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuSubEdit}">
<Button.Content>
<Image
Width="20"
Height="20"
Source="/Assets/edit.png" />
</Button.Content>
</Button>
<Button
x:Name="btnAddSub"
Width="30"
Height="30"
Margin="4,0"
Classes="Success"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuSubAdd}">
<Button.Content>
<Image
Width="20"
Height="20"
Source="/Assets/add.png" />
</Button.Content>
</Button>
<Button
x:Name="btnAutofitColumnWidth"
Width="30"
Height="30"
Margin="20,0"
Classes="Success"
Theme="{DynamicResource SolidButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
<Button.Content>
<Image
Width="20"
Height="20"
Source="/Assets/fit.png" />
</Button.Content>
</Button>
<TextBox
x:Name="txtServerFilter"
Width="200"
Margin="4,0"
VerticalContentAlignment="Center"
ToolTip.Tip="{x:Static resx:ResUI.MsgServerTitle}" />
</WrapPanel>
<DataGrid
x:Name="lstProfiles"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserResizeColumns="True"
CanUserSortColumns="False"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding ProfileItems}">
<DataGrid.KeyBindings>
<KeyBinding Command="{Binding Export2ShareUrlCmd}" Gesture="Ctrl+C" />
<KeyBinding Command="{Binding SetDefaultServerCmd}" Gesture="Enter" />
</DataGrid.KeyBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuEditServer" Header="{x:Static resx:ResUI.menuEditServer}" />
<MenuItem x:Name="menuSetDefaultServer" Header="{x:Static resx:ResUI.menuSetDefaultServer}" />
<MenuItem x:Name="menuRemoveServer" Header="{x:Static resx:ResUI.menuRemoveServer}" />
<MenuItem x:Name="menuRemoveDuplicateServer" Header="{x:Static resx:ResUI.menuRemoveDuplicateServer}" />
<MenuItem x:Name="menuCopyServer" Header="{x:Static resx:ResUI.menuCopyServer}" />
<MenuItem x:Name="menuShareServer" Header="{x:Static resx:ResUI.menuShareServer}" />
<Separator />
<MenuItem x:Name="menuSetDefaultMultipleServer" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServer}" />
<MenuItem x:Name="menuSetDefaultLoadBalanceServer" Header="{x:Static resx:ResUI.menuSetDefaultLoadBalanceServer}" />
<Separator />
<MenuItem x:Name="menuMixedTestServer" Header="{x:Static resx:ResUI.menuMixedTestServer}" />
<MenuItem x:Name="menuTcpingServer" Header="{x:Static resx:ResUI.menuTcpingServer}" />
<MenuItem x:Name="menuRealPingServer" Header="{x:Static resx:ResUI.menuRealPingServer}" />
<MenuItem x:Name="menuSpeedServer" Header="{x:Static resx:ResUI.menuSpeedServer}" />
<MenuItem x:Name="menuSortServerResult" Header="{x:Static resx:ResUI.menuSortServerResult}" />
<Separator />
<MenuItem x:Name="menuMoveToGroup" Header="{x:Static resx:ResUI.menuMoveToGroup}">
<MenuItem>
<MenuItem.Header>
<DockPanel>
<ComboBox
x:Name="cmbMoveToGroup"
Width="200"
ToolTip.Tip="{x:Static resx:ResUI.menuSubscription}" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
</MenuItem>
<MenuItem Header="{x:Static resx:ResUI.menuMoveTo}">
<MenuItem x:Name="menuMoveTop" Header="{x:Static resx:ResUI.menuMoveTop}" />
<MenuItem x:Name="menuMoveUp" Header="{x:Static resx:ResUI.menuMoveUp}" />
<MenuItem x:Name="menuMoveDown" Header="{x:Static resx:ResUI.menuMoveDown}" />
<MenuItem x:Name="menuMoveBottom" Header="{x:Static resx:ResUI.menuMoveBottom}" />
</MenuItem>
<MenuItem x:Name="menuSelectAll" Header="{x:Static resx:ResUI.menuSelectAll}" />
<Separator />
<MenuItem Header="{x:Static resx:ResUI.menuExport2ClientConfig}">
<MenuItem x:Name="menuExport2ClientConfig" Header="{x:Static resx:ResUI.menuExport2ClientConfig}" />
<MenuItem x:Name="menuExport2ClientConfigClipboard" Header="{x:Static resx:ResUI.menuExport2ClientConfigClipboard}" />
</MenuItem>
<MenuItem Header="{x:Static resx:ResUI.menuExport2ShareUrl}">
<MenuItem x:Name="menuExport2ShareUrl" Header="{x:Static resx:ResUI.menuExport2ShareUrl}" />
<MenuItem x:Name="menuExport2ShareUrlBase64" Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="40" Binding="{Binding isActive}" />
<DataGridTextColumn
Width="80"
Binding="{Binding configType}"
Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn
Width="150"
Binding="{Binding remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="120"
Binding="{Binding address}"
Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn
Width="60"
Binding="{Binding port}"
Header="{x:Static resx:ResUI.LvPort}" />
<DataGridTextColumn
Width="100"
Binding="{Binding network}"
Header="{x:Static resx:ResUI.LvTransportProtocol}" />
<DataGridTextColumn
Width="100"
Binding="{Binding streamSecurity}"
Header="{x:Static resx:ResUI.LvTLS}" />
<DataGridTextColumn
Width="100"
Binding="{Binding subRemarks}"
Header="{x:Static resx:ResUI.LvSubscription}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<TextBlock Text="{x:Static resx:ResUI.LvTestDelay}" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Margin="8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{Binding delay, Converter={StaticResource DelayColorConverter}}"
Text="{Binding Path=delayVal, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="100"
Binding="{Binding speedVal}"
Header="{x:Static resx:ResUI.LvTestSpeed}" />
<DataGridTextColumn
Width="100"
Binding="{Binding todayUp}"
Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" />
<DataGridTextColumn
Width="100"
Binding="{Binding todayDown}"
Header="{x:Static resx:ResUI.LvTodayDownloadDataAmount}" />
<DataGridTextColumn
Width="100"
Binding="{Binding totalUp}"
Header="{x:Static resx:ResUI.LvTotalUploadDataAmount}" />
<DataGridTextColumn
Width="100"
Binding="{Binding totalDown}"
Header="{x:Static resx:ResUI.LvTotalDownloadDataAmount}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
</UserControl>

View file

@ -0,0 +1,468 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using Splat;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{
private static Config _config;
private Window _window;
public ProfilesView(Window window)
{
InitializeComponent();
_config = LazyConfig.Instance.Config;
_window = window;
menuSelectAll.Click += menuSelectAll_Click;
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
txtServerFilter.KeyDown += TxtServerFilter_KeyDown;
lstProfiles.KeyDown += LstProfiles_KeyDown;
lstProfiles.SelectionChanged += lstProfiles_SelectionChanged;
lstProfiles.DoubleTapped += LstProfiles_DoubleTapped;
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
//if (_config.uiItem.enableDragDropSort)
//{
// lstProfiles.AllowDrop = true;
// lstProfiles.PreviewMouseLeftButtonDown += LstProfiles_PreviewMouseLeftButtonDown;
// lstProfiles.MouseMove += LstProfiles_MouseMove;
// lstProfiles.DragEnter += LstProfiles_DragEnter;
// lstProfiles.Drop += LstProfiles_Drop;
//}
ViewModel = new ProfilesViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ProfilesViewModel));
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.ProfileItems, v => v.lstProfiles.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedProfile, v => v.lstProfiles.SelectedItem).DisposeWith(disposables);
// this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.lstGroup.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.btnAddSub).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.btnEditSub).DisposeWith(disposables);
//servers delete
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.menuEditServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveServerCmd, v => v.menuRemoveServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveDuplicateServerCmd, v => v.menuRemoveDuplicateServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultLoadBalanceServerCmd, v => v.menuSetDefaultLoadBalanceServer).DisposeWith(disposables);
//servers move
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
//servers ping
this.BindCommand(ViewModel, vm => vm.MixedTestServerCmd, v => v.menuMixedTestServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.TcpingServerCmd, v => v.menuTcpingServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RealPingServerCmd, v => v.menuRealPingServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables);
//servers export
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigCmd, v => v.menuExport2ClientConfig).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
});
//RestoreUI();
ViewModel?.RefreshServers();
}
//#region Event
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.SetClipboardData:
if (obj is null) return false;
await AvaUtils.SetClipboardData(this, (string)obj);
break;
case EViewAction.AdjustMainLvColWidth:
Dispatcher.UIThread.Post(() =>
AutofitColumnWidth(),
DispatcherPriority.Default);
break;
case EViewAction.ProfilesFocus:
lstProfiles.Focus();
break;
case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(_window, ResUI.RemoveServer) == ButtonResult.No)
{
return false;
}
break;
case EViewAction.SaveFileDialog:
if (obj is null) return false;
var fileName = await UI.SaveFileDialog(_window, "");
if (fileName.IsNullOrEmpty())
{
return false;
}
ViewModel?.Export2ClientConfigResult(fileName, (ProfileItem)obj);
break;
case EViewAction.AddServerWindow:
if (obj is null) return false;
return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.AddServer2Window:
if (obj is null) return false;
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.ShareServer:
if (obj is null) return false;
await ShareServer((string)obj);
break;
case EViewAction.SubEditWindow:
if (obj is null) return false;
return await new SubEditWindow((SubItem)obj).ShowDialog<bool>(_window);
case EViewAction.DispatcherSpeedTest:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.SetSpeedTestResult((SpeedTestResult)obj),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherRefreshServersBiz:
Dispatcher.UIThread.Post(() =>
ViewModel?.RefreshServersBiz(),
DispatcherPriority.Default);
break;
}
return await Task.FromResult(true);
}
public async Task ShareServer(string url)
{
if (Utils.IsNullOrEmpty(url))
{
return;
}
var dialog = new QrcodeView(url);
await dialog.ShowDialog(_window);
}
private void lstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
List<ProfileItemModel> lst = [];
foreach (var item in lstProfiles.SelectedItems)
{
lst.Add((ProfileItemModel)item);
}
ViewModel.SelectedProfiles = lst;
}
private void LstProfiles_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
if (_config.uiItem.doubleClick2Activate)
{
ViewModel?.SetDefaultServer();
}
else
{
ViewModel?.EditServerAsync(EConfigType.Custom);
}
}
private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e)
{
e.Row.Header = $" {e.Row.GetIndex() + 1}";
}
//private void LstProfiles_ColumnHeader_Click(object? sender, RoutedEventArgs e)
//{
// var colHeader = sender as DataGridColumnHeader;
// if (colHeader == null || colHeader.TabIndex < 0 || colHeader.Column == null)
// {
// return;
// }
// var colName = ((MyDGTextColumn)colHeader.Column).ExName;
// ViewModel?.SortServer(colName);
//}
private void menuSelectAll_Click(object? sender, RoutedEventArgs e)
{
lstProfiles.SelectAll();
}
private void LstProfiles_KeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers == KeyModifiers.Control)
{
switch (e.Key)
{
case Key.A:
menuSelectAll_Click(null, null);
break;
case Key.C:
ViewModel?.Export2ShareUrlAsync(false);
break;
case Key.D:
ViewModel?.EditServerAsync(EConfigType.Custom);
break;
case Key.F:
ViewModel?.ShareServerAsync();
break;
case Key.O:
ViewModel?.ServerSpeedtest(ESpeedActionType.Tcping);
break;
case Key.R:
ViewModel?.ServerSpeedtest(ESpeedActionType.Realping);
break;
case Key.T:
ViewModel?.ServerSpeedtest(ESpeedActionType.Speedtest);
break;
case Key.E:
ViewModel?.ServerSpeedtest(ESpeedActionType.Mixedtest);
break;
}
}
else
{
if (e.Key is Key.Enter or Key.Return)
{
ViewModel?.SetDefaultServer();
}
else if (e.Key == Key.Delete)
{
ViewModel?.RemoveServerAsync();
}
else if (e.Key == Key.T)
{
ViewModel?.MoveServer(EMove.Top);
}
else if (e.Key == Key.U)
{
ViewModel?.MoveServer(EMove.Up);
}
else if (e.Key == Key.D)
{
ViewModel?.MoveServer(EMove.Down);
}
else if (e.Key == Key.B)
{
ViewModel?.MoveServer(EMove.Bottom);
}
else if (e.Key == Key.Escape)
{
ViewModel?.ServerSpeedtestStop();
}
}
}
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
{
AutofitColumnWidth();
}
private void AutofitColumnWidth()
{
foreach (var it in lstProfiles.Columns)
{
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
}
}
private void TxtServerFilter_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key is Key.Enter or Key.Return)
{
ViewModel?.RefreshServers();
}
}
//#endregion Event
//#region UI
//private void RestoreUI()
//{
// var lvColumnItem = _config.uiItem.mainColumnItem.OrderBy(t => t.Index).ToList();
// var displayIndex = 0;
// foreach (var item in lvColumnItem)
// {
// foreach (MyDGTextColumn item2 in lstProfiles.Columns)
// {
// if (item2.ExName == item.Name)
// {
// if (item.Width < 0)
// {
// item2.IsVisible = false;
// }
// else
// {
// item2.Width = item.Width;
// item2.DisplayIndex = displayIndex++;
// }
// }
// }
// }
// if (!_config.guiItem.enableStatistics)
// {
// colTodayUp.IsVisible =
// colTodayDown.IsVisible =
// colTotalUp.IsVisible =
// colTotalDown.IsVisible = false;
// }
// else
// {
// colTodayUp.IsVisible =
// colTodayDown.IsVisible =
// colTotalUp.IsVisible =
// colTotalDown.IsVisible = true;
// }
//}
//private void StorageUI()
//{
// List<ColumnItem> lvColumnItem = new();
// for (int k = 0; k < lstProfiles.Columns.Count; k++)
// {
// var item2 = (MyDGTextColumn)lstProfiles.Columns[k];
// lvColumnItem.Add(new()
// {
// Name = item2.ExName,
// Width = item2.IsVisible == true ? Utils.ToInt(item2.ActualWidth) : -1,
// Index = item2.DisplayIndex
// });
// }
// _config.uiItem.mainColumnItem = lvColumnItem;
// ConfigHandler.SaveConfig(_config);
//}
//#endregion UI
//#region Drag and Drop
//private Point startPoint = new();
//private int startIndex = -1;
//private string formatData = "ProfileItemModel";
///// <summary>
///// Helper to search up the VisualTree
///// </summary>
///// <typeparam name="T"></typeparam>
///// <param name="current"></param>
///// <returns></returns>
//private static T? FindAncestor<T>(DependencyObject current) where T : DependencyObject
//{
// do
// {
// if (current is T)
// {
// return (T)current;
// }
// current = VisualTreeHelper.GetParent(current);
// }
// while (current != null);
// return null;
//}
//private void LstProfiles_PreviewMouseLeftButtonDown(object? sender, MouseButtonEventArgs e)
//{
// // Get current mouse position
// startPoint = e.GetPosition(null);
//}
//private void LstProfiles_MouseMove(object? sender, MouseEventArgs e)
//{
// // Get the current mouse position
// Point mousePos = e.GetPosition(null);
// Vector diff = startPoint - mousePos;
// if (e.LeftButton == MouseButtonState.Pressed &&
// (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
// Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
// {
// // Get the dragged Item
// if (sender is not DataGrid listView) return;
// var listViewItem = FindAncestor<DataGridRow>((DependencyObject)e.OriginalSource);
// if (listViewItem == null) return; // Abort
// // Find the data behind the ListViewItem
// ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);
// if (item == null) return; // Abort
// // Initialize the drag & drop operation
// startIndex = lstProfiles.SelectedIndex;
// DataObject dragData = new(formatData, item);
// DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Copy | DragDropEffects.Move);
// }
//}
//private void LstProfiles_DragEnter(object? sender, DragEventArgs e)
//{
// if (!e.Data.GetDataPresent(formatData) || sender != e.Source)
// {
// e.Effects = DragDropEffects.None;
// }
//}
//private void LstProfiles_Drop(object? sender, DragEventArgs e)
//{
// if (e.Data.GetDataPresent(formatData) && sender == e.Source)
// {
// // Get the drop Item destination
// if (sender is not DataGrid listView) return;
// var listViewItem = FindAncestor<DataGridRow>((DependencyObject)e.OriginalSource);
// if (listViewItem == null)
// {
// // Abort
// e.Effects = DragDropEffects.None;
// return;
// }
// // Find the data behind the Item
// ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);
// if (item == null) return;
// // Move item into observable collection
// // (this will be automatically reflected to lstView.ItemsSource)
// e.Effects = DragDropEffects.Move;
// ViewModel?.MoveServerTo(startIndex, item);
// startIndex = -1;
// }
//}
//#endregion Drag and Drop
}
}

View file

@ -0,0 +1,48 @@
<Window
x:Class="v2rayN.Desktop.Views.QrcodeView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
Title=""
Width="400"
Height="480"
d:DesignHeight="480"
d:DesignWidth="400"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaToDecorationsHint="True"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid Margin="30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<Image
Name="imgQrcode"
Width="300"
Height="300"
Source="/Assets/close.png" />
<TextBox
x:Name="txtContent"
Grid.Row="1"
Width="300"
Margin="0,8"
VerticalAlignment="Center"
IsReadOnly="True"
MaxLines="1" />
<Button
x:Name="btnCancel"
Grid.Row="2"
Width="100"
HorizontalAlignment="Right"
Classes="Margin8"
Content="{x:Static resx:ResUI.TbConfirm}" />
</Grid>
</Window>

View file

@ -0,0 +1,26 @@
using Avalonia.Controls;
using Avalonia.Media.Imaging;
namespace v2rayN.Desktop.Views
{
public partial class QrcodeView : Window
{
public QrcodeView(string? url)
{
InitializeComponent();
txtContent.Text = url;
imgQrcode.Source = GetQRCode(url);
btnCancel.Click += (s, e) => this.Close();
}
private Bitmap? GetQRCode(string? url)
{
var qrCodeImage = QRCodeHelper.GenQRCode(url);
if (qrCodeImage is null) return null;
var ms = new MemoryStream(qrCodeImage);
return new Bitmap(ms);
}
}
}

View file

@ -0,0 +1,197 @@
<Window
x:Class="v2rayN.Desktop.Views.RoutingRuleDetailsWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuRoutingRuleDetailsSetting}"
Width="900"
Height="700"
x:DataType="vms:RoutingRuleDetailsViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel>
<Grid Classes="Margin8" DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="outboundTag" />
<ComboBox
x:Name="cmbOutboundTag"
Grid.Row="0"
Grid.Column="1"
Width="200"
Classes="Margin4"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
HorizontalAlignment="Left"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbRuleMatchingTips}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="port" />
<TextBox
x:Name="txtPort"
Grid.Row="1"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin4" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
Classes="Margin4">
<Button Click="linkRuleobjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbRuleobjectDoc}" />
</Button>
</TextBlock>
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="protocol" />
<ListBox
x:Name="clbProtocol"
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin4"
SelectionMode="Multiple"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="inboundTag" />
<ListBox
x:Name="clbInboundTag"
Grid.Row="3"
Grid.Column="1"
Classes="Margin4"
SelectionMode="Multiple"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="network" />
<ComboBox
x:Name="cmbNetwork"
Grid.Row="4"
Grid.Column="1"
Width="200"
Classes="Margin4"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="enabled" />
<ToggleSwitch
x:Name="togEnabled"
Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin4" />
<TextBlock
Grid.Row="5"
Grid.Column="2"
HorizontalAlignment="Left"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbRoutingTips}" />
</Grid>
<StackPanel
HorizontalAlignment="Right"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<StackPanel
Width="600"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<CheckBox x:Name="chkAutoSort">
<TextBlock Text="{x:Static resx:ResUI.TbAutoSort}" />
</CheckBox>
</StackPanel>
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<Grid Classes="Margin8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<HeaderedContentControl Grid.Column="0" Header="{x:Static resx:ResUI.TbRoutingRuleDomain}">
<TextBox
Name="txtDomain"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
<HeaderedContentControl Grid.Column="2" Header="{x:Static resx:ResUI.TbRoutingRuleIP}">
<TextBox
Name="txtIP"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
<GridSplitter Grid.Column="3" HorizontalAlignment="Stretch" />
<HeaderedContentControl Grid.Column="4" Header="{x:Static resx:ResUI.TbRoutingRuleProcess}">
<TextBox
Name="txtProcess"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
</Grid>
</DockPanel>
</Window>

View file

@ -0,0 +1,100 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class RoutingRuleDetailsWindow : ReactiveWindow<RoutingRuleDetailsViewModel>
{
public RoutingRuleDetailsWindow()
{
InitializeComponent();
}
public RoutingRuleDetailsWindow(RulesItem rulesItem)
{
InitializeComponent();
this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close();
clbProtocol.SelectionChanged += ClbProtocol_SelectionChanged;
clbInboundTag.SelectionChanged += ClbInboundTag_SelectionChanged;
ViewModel = new RoutingRuleDetailsViewModel(rulesItem, UpdateViewHandler);
cmbOutboundTag.Items.Add(Global.ProxyTag);
cmbOutboundTag.Items.Add(Global.DirectTag);
cmbOutboundTag.Items.Add(Global.BlockTag);
Global.RuleProtocols.ForEach(it =>
{
clbProtocol.Items.Add(it);
});
Global.InboundTags.ForEach(it =>
{
clbInboundTag.Items.Add(it);
});
Global.RuleNetworks.ForEach(it =>
{
cmbNetwork.Items.Add(it);
});
if (!rulesItem.id.IsNullOrEmpty())
{
rulesItem.protocol?.ForEach(it =>
{
clbProtocol.SelectedItems.Add(it);
});
rulesItem.inboundTag?.ForEach(it =>
{
clbInboundTag.SelectedItems.Add(it);
});
}
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.SelectedSource.outboundTag, v => v.cmbOutboundTag.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.port, v => v.txtPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Domain, v => v.txtDomain.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IP, v => v.txtIP.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoSort, v => v.chkAutoSort.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
}
return await Task.FromResult(true);
}
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
cmbOutboundTag.Focus();
}
private void ClbProtocol_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.ProtocolItems = clbProtocol.SelectedItems.Cast<string>().ToList();
}
private void ClbInboundTag_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.InboundTagItems = clbInboundTag.SelectedItems.Cast<string>().ToList();
}
private void linkRuleobjectDoc_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
}
}
}

View file

@ -0,0 +1,245 @@
<Window
x:Class="v2rayN.Desktop.Views.RoutingRuleSettingWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuRoutingRuleSetting}"
Width="960"
Height="700"
x:DataType="vms:RoutingRuleSettingViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel>
<StackPanel
Classes="Margin8"
DockPanel.Dock="Top"
Orientation="Horizontal">
<Menu>
<MenuItem x:Name="menuRuleAdd" Header="{x:Static resx:ResUI.menuRuleAdd}" />
<MenuItem x:Name="menuImportRulesFromFile" Header="{x:Static resx:ResUI.menuImportRulesFromFile}" />
<MenuItem x:Name="menuImportRulesFromClipboard" Header="{x:Static resx:ResUI.menuImportRulesFromClipboard}" />
<MenuItem x:Name="menuImportRulesFromUrl" Header="{x:Static resx:ResUI.menuImportRulesFromUrl}" />
</Menu>
</StackPanel>
<StackPanel
HorizontalAlignment="Right"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<Grid Classes="Margin8" DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvRemarks}" />
<StackPanel
Grid.Row="0"
Grid.Column="1"
Orientation="Horizontal">
<TextBox
x:Name="txtRemarks"
Grid.Row="0"
Grid.Column="1"
Width="300"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap" />
<TextBlock
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvSort}" />
<TextBox
x:Name="txtSort"
Width="100"
HorizontalAlignment="Left"
Classes="Margin4" />
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbdomainStrategy}" />
<StackPanel
Grid.Row="1"
Grid.Column="1"
Orientation="Horizontal">
<ComboBox
x:Name="cmbdomainStrategy"
Width="200"
Classes="Margin4" />
<TextBlock
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
<ComboBox
x:Name="cmbdomainStrategy4Singbox"
Width="200"
Classes="Margin4" />
</StackPanel>
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvUrl}" />
<TextBox
x:Name="txtUrl"
Grid.Row="2"
Grid.Column="1"
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvCustomIcon}" />
<TextBox
x:Name="txtCustomIcon"
Grid.Row="3"
Grid.Column="1"
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap" />
<Button
x:Name="btnBrowseCustomIcon"
Grid.Row="3"
Grid.Column="2"
Classes="Margin4"
Content="{x:Static resx:ResUI.TbBrowse}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4">
<Button Click="linkCustomRulesetPath4Singbox">
<TextBlock Text="{x:Static resx:ResUI.LvCustomRulesetPath4Singbox}" />
</Button>
</TextBlock>
<TextBox
x:Name="txtCustomRulesetPath4Singbox"
Grid.Row="4"
Grid.Column="1"
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap" />
<Button
x:Name="btnBrowseCustomRulesetPath4Singbox"
Grid.Row="4"
Grid.Column="2"
Classes="Margin4"
Content="{x:Static resx:ResUI.TbBrowse}" />
</Grid>
<TabControl x:Name="tabAdvanced">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuRuleList}">
<DataGrid
x:Name="lstRules"
AutoGenerateColumns="False"
BorderThickness="1"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding RulesItems}">
<DataGrid.KeyBindings>
<KeyBinding Command="{Binding RuleExportSelectedCmd}" Gesture="Ctrl+C" />
</DataGrid.KeyBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuRuleAdd2" Header="{x:Static resx:ResUI.menuRuleAdd}" />
<MenuItem x:Name="menuRuleRemove" Header="{x:Static resx:ResUI.menuRuleRemove}" />
<MenuItem x:Name="menuRuleSelectAll" Header="{x:Static resx:ResUI.menuSelectAll}" />
<MenuItem x:Name="menuRuleExportSelected" Header="{x:Static resx:ResUI.menuRuleExportSelected}" />
<Separator />
<MenuItem x:Name="menuMoveTop" Header="{x:Static resx:ResUI.menuMoveTop}" />
<MenuItem x:Name="menuMoveUp" Header="{x:Static resx:ResUI.menuMoveUp}" />
<MenuItem x:Name="menuMoveDown" Header="{x:Static resx:ResUI.menuMoveDown}" />
<MenuItem x:Name="menuMoveBottom" Header="{x:Static resx:ResUI.menuMoveBottom}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn
Width="110"
Binding="{Binding outboundTag}"
Header="outboundTag" />
<DataGridTextColumn
Width="80"
Binding="{Binding port}"
Header="port" />
<DataGridTextColumn
Width="80"
Binding="{Binding protocols}"
Header="protocol" />
<DataGridTextColumn
Width="110"
Binding="{Binding inboundTags}"
Header="inboundTag" />
<DataGridTextColumn
Width="90"
Binding="{Binding network}"
Header="network" />
<DataGridTextColumn
Width="190"
Binding="{Binding domains}"
Header="domain" />
<DataGridTextColumn
Width="190"
Binding="{Binding ips}"
Header="ip" />
<DataGridTextColumn
Width="80"
Binding="{Binding enabled}"
Header="enabled" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</DockPanel>
</Window>

View file

@ -0,0 +1,214 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.ReactiveUI;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class RoutingRuleSettingWindow : ReactiveWindow<RoutingRuleSettingViewModel>
{
public RoutingRuleSettingWindow()
{
InitializeComponent();
}
public RoutingRuleSettingWindow(RoutingItem routingItem)
{
InitializeComponent();
this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close();
this.KeyDown += RoutingRuleSettingWindow_KeyDown;
lstRules.SelectionChanged += lstRules_SelectionChanged;
lstRules.DoubleTapped += LstRules_DoubleTapped;
menuRuleSelectAll.Click += menuRuleSelectAll_Click;
btnBrowseCustomIcon.Click += btnBrowseCustomIcon_Click;
btnBrowseCustomRulesetPath4Singbox.Click += btnBrowseCustomRulesetPath4Singbox_ClickAsync;
ViewModel = new RoutingRuleSettingViewModel(routingItem, UpdateViewHandler);
Global.DomainStrategies.ForEach(it =>
{
cmbdomainStrategy.Items.Add(it);
});
cmbdomainStrategy.Items.Add(string.Empty);
Global.DomainStrategies4Singbox.ForEach(it =>
{
cmbdomainStrategy4Singbox.Items.Add(it);
});
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.RulesItems, v => v.lstRules.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstRules.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.domainStrategy, v => v.cmbdomainStrategy.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.domainStrategy4Singbox, v => v.cmbdomainStrategy4Singbox.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.url, v => v.txtUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.customIcon, v => v.txtCustomIcon.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.customRulesetPath4Singbox, v => v.txtCustomRulesetPath4Singbox.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedRouting.sort, v => v.txtSort.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RuleAddCmd, v => v.menuRuleAdd).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ImportRulesFromFileCmd, v => v.menuImportRulesFromFile).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ImportRulesFromClipboardCmd, v => v.menuImportRulesFromClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ImportRulesFromUrlCmd, v => v.menuImportRulesFromUrl).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RuleAddCmd, v => v.menuRuleAdd2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RuleRemoveCmd, v => v.menuRuleRemove).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RuleExportSelectedCmd, v => v.menuRuleExportSelected).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(this, ResUI.RemoveServer) == ButtonResult.No)
{
return false;
}
break;
case EViewAction.AddBatchRoutingRulesYesNo:
if (await UI.ShowYesNo(this, ResUI.AddBatchRoutingRulesYesNo) == ButtonResult.No)
{
return false;
}
break;
case EViewAction.RoutingRuleDetailsWindow:
if (obj is null) return false;
return await new RoutingRuleDetailsWindow((RulesItem)obj).ShowDialog<bool>(this);
case EViewAction.ImportRulesFromFile:
var fileName = await UI.OpenFileDialog(this, null);
if (fileName.IsNullOrEmpty())
{
return false;
}
ViewModel?.ImportRulesFromFileAsync(fileName);
break;
case EViewAction.SetClipboardData:
if (obj is null) return false;
await AvaUtils.SetClipboardData(this, (string)obj);
break;
case EViewAction.ImportRulesFromClipboard:
var clipboardData = await AvaUtils.GetClipboardData(this);
ViewModel?.ImportRulesFromClipboardAsync(clipboardData);
break;
}
return await Task.FromResult(true);
}
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
txtRemarks.Focus();
}
private void RoutingRuleSettingWindow_KeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers == KeyModifiers.Control)
{
if (e.Key == Key.A)
{
lstRules.SelectAll();
}
else if (e.Key == Key.C)
{
ViewModel?.RuleExportSelectedAsync();
}
}
else
{
if (e.Key == Key.T)
{
ViewModel?.MoveRule(EMove.Top);
}
else if (e.Key == Key.U)
{
ViewModel?.MoveRule(EMove.Up);
}
else if (e.Key == Key.D)
{
ViewModel?.MoveRule(EMove.Down);
}
else if (e.Key == Key.B)
{
ViewModel?.MoveRule(EMove.Bottom);
}
else if (e.Key == Key.Delete)
{
ViewModel?.RuleRemoveAsync();
}
}
}
private void lstRules_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
List<RulesItemModel> lst = [];
foreach (var item in lstRules.SelectedItems)
{
lst.Add((RulesItemModel)item);
}
ViewModel.SelectedSources = lst;
}
private void LstRules_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
ViewModel?.RuleEditAsync(false);
}
private void menuRuleSelectAll_Click(object? sender, RoutedEventArgs e)
{
lstRules.SelectAll();
}
private async void btnBrowseCustomIcon_Click(object? sender, RoutedEventArgs e)
{
var fileName = await UI.OpenFileDialog(this, FilePickerFileTypes.ImagePng);
if (fileName.IsNullOrEmpty())
{
return;
}
txtCustomIcon.Text = fileName;
}
private async void btnBrowseCustomRulesetPath4Singbox_ClickAsync(object? sender, RoutedEventArgs e)
{
var fileName = await UI.OpenFileDialog(this, null);
if (fileName.IsNullOrEmpty())
{
return;
}
txtCustomRulesetPath4Singbox.Text = fileName;
}
private void linkCustomRulesetPath4Singbox(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart("https://github.com/2dust/v2rayCustomRoutingList/blob/master/singbox_custom_ruleset_example.json");
}
}
}

View file

@ -0,0 +1,136 @@
<Window
x:Class="v2rayN.Desktop.Views.RoutingSettingWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuRoutingSetting}"
Width="990"
Height="700"
x:DataType="vms:RoutingSettingViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel>
<StackPanel
Classes="Margin8"
DockPanel.Dock="Top"
Orientation="Horizontal">
<Menu>
<MenuItem x:Name="menuRoutingAdvancedAdd2" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
<MenuItem x:Name="menuRoutingAdvancedImportRules2" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
</Menu>
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center">
<Button Click="linkdomainStrategy_Click">
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
</Button>
</TextBlock>
<ComboBox
x:Name="cmbdomainStrategy"
Width="110"
Margin="8,0,0,0" />
<Separator />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbdomainMatcher}" />
<ComboBox
x:Name="cmbdomainMatcher"
Width="60"
Margin="8,0,0,0" />
<Separator />
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center">
<Button Click="linkdomainStrategy4Singbox_Click">
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
</Button>
</TextBlock>
<ComboBox
x:Name="cmbdomainStrategy4Singbox"
Width="100"
Margin="8,0,0,0" />
</StackPanel>
<StackPanel
HorizontalAlignment="Right"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<StackPanel
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<TextBlock Text="{x:Static resx:ResUI.TbRoutingTips}" />
</StackPanel>
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<DockPanel>
<TabControl x:Name="tabAdvanced">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}">
<DataGrid
x:Name="lstRoutings"
AutoGenerateColumns="False"
BorderThickness="1"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding RoutingItems}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuRoutingAdvancedAdd" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
<MenuItem x:Name="menuRoutingAdvancedRemove" Header="{x:Static resx:ResUI.menuRoutingAdvancedRemove}" />
<MenuItem x:Name="menuRoutingAdvancedSelectAll" Header="{x:Static resx:ResUI.menuSelectAll}" />
<MenuItem x:Name="menuRoutingAdvancedSetDefault" Header="{x:Static resx:ResUI.menuRoutingAdvancedSetDefault}" />
<Separator />
<MenuItem x:Name="menuRoutingAdvancedImportRules" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="40" Binding="{Binding isActive}" />
<DataGridTextColumn
Width="250"
Binding="{Binding remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="60"
Binding="{Binding ruleNum}"
Header="{x:Static resx:ResUI.LvCount}" />
<DataGridTextColumn
Width="60"
Binding="{Binding sort}"
Header="{x:Static resx:ResUI.LvSort}" />
<DataGridTextColumn
Width="300"
Binding="{Binding url}"
Header="{x:Static resx:ResUI.LvUrl}" />
<DataGridTextColumn
Width="300"
Binding="{Binding customIcon}"
Header="{x:Static resx:ResUI.LvCustomIcon}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</DockPanel>
</DockPanel>
</Window>

View file

@ -0,0 +1,155 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class RoutingSettingWindow : ReactiveWindow<RoutingSettingViewModel>
{
private bool _manualClose = false;
public RoutingSettingWindow()
{
InitializeComponent();
this.Closing += RoutingSettingWindow_Closing;
btnCancel.Click += (s, e) => this.Close();
this.KeyDown += RoutingSettingWindow_KeyDown;
lstRoutings.SelectionChanged += lstRoutings_SelectionChanged;
lstRoutings.DoubleTapped += LstRoutings_DoubleTapped;
menuRoutingAdvancedSelectAll.Click += menuRoutingAdvancedSelectAll_Click;
ViewModel = new RoutingSettingViewModel(UpdateViewHandler);
Global.DomainStrategies.ForEach(it =>
{
cmbdomainStrategy.Items.Add(it);
});
Global.DomainMatchers.ForEach(it =>
{
cmbdomainMatcher.Items.Add(it);
});
Global.DomainStrategies4Singbox.ForEach(it =>
{
cmbdomainStrategy4Singbox.Items.Add(it);
});
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.RoutingItems, v => v.lstRoutings.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstRoutings.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainStrategy, v => v.cmbdomainStrategy.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainMatcher, v => v.cmbdomainMatcher.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainStrategy4Singbox, v => v.cmbdomainStrategy4Singbox.SelectedValue).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingAdvancedAddCmd, v => v.menuRoutingAdvancedAdd).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingAdvancedAddCmd, v => v.menuRoutingAdvancedAdd2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingAdvancedRemoveCmd, v => v.menuRoutingAdvancedRemove).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingAdvancedSetDefaultCmd, v => v.menuRoutingAdvancedSetDefault).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingAdvancedImportRulesCmd, v => v.menuRoutingAdvancedImportRules).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RoutingAdvancedImportRulesCmd, v => v.menuRoutingAdvancedImportRules2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(this, ResUI.RemoveRules) == ButtonResult.No)
{
return false;
}
break;
case EViewAction.RoutingRuleSettingWindow:
if (obj is null) return false;
return await new RoutingRuleSettingWindow((RoutingItem)obj).ShowDialog<bool>(this);
}
return await Task.FromResult(true);
}
private void RoutingSettingWindow_KeyDown(object? sender, KeyEventArgs e)
{
if (ViewModel?.enableRoutingBasic ?? false)
{
return;
}
if (e.KeyModifiers == KeyModifiers.Control)
{
if (e.Key == Key.A)
{
lstRoutings.SelectAll();
}
}
else if (e.Key is Key.Enter or Key.Return)
{
ViewModel?.RoutingAdvancedSetDefault();
}
else if (e.Key == Key.Delete)
{
ViewModel?.RoutingAdvancedRemoveAsync();
}
}
private void menuRoutingAdvancedSelectAll_Click(object? sender, RoutedEventArgs e)
{
lstRoutings.SelectAll();
}
private void lstRoutings_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
List<RoutingItemModel> lst = [];
foreach (var item in lstRoutings.SelectedItems)
{
lst.Add((RoutingItemModel)item);
}
ViewModel.SelectedSources = lst;
}
private void LstRoutings_DoubleTapped(object? sender, TappedEventArgs e)
{
ViewModel?.RoutingAdvancedEditAsync(false);
}
private void linkdomainStrategy_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart("https://xtls.github.io/config/routing.html");
}
private void linkdomainStrategy4Singbox_Click(object? sender, RoutedEventArgs e)
{
Utils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/shared/listen/#domain_strategy");
}
private void btnCancel_Click(object? sender, RoutedEventArgs e)
{
_manualClose = true;
this.Close(ViewModel?.IsModified);
}
private void RoutingSettingWindow_Closing(object? sender, WindowClosingEventArgs e)
{
if (ViewModel?.IsModified == true)
{
if (!_manualClose)
{
btnCancel_Click(null, null);
}
}
}
}
}

View file

@ -0,0 +1,234 @@
<Window
x:Class="v2rayN.Desktop.Views.SubEditWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSubSetting}"
Width="700"
Height="600"
d:DesignHeight="600"
d:DesignWidth="800"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand" />
<Button
x:Name="btnCancel"
Width="100"
Margin="8,0"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="400" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Text="{x:Static resx:ResUI.menuSubscription}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvUrl}" />
<TextBox
x:Name="txtUrl"
Grid.Row="2"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvEnabled}" />
<DockPanel
Grid.Row="3"
Grid.Column="1"
Classes="Margin4">
<ToggleSwitch
x:Name="togEnable"
HorizontalAlignment="Center"
Classes="Margin4"
DockPanel.Dock="Left" />
<TextBox
x:Name="txtAutoUpdateInterval"
Width="200"
VerticalAlignment="Center"
Classes="Margin4"
DockPanel.Dock="Right"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}" />
</DockPanel>
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvConvertTarget}" />
<ComboBox
x:Name="cmbConvertTarget"
Grid.Row="6"
Grid.Column="1"
Width="200"
VerticalAlignment=" "
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.LvConvertTargetTip}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvUserAgent}" />
<TextBox
x:Name="txtUserAgent"
Grid.Row="7"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvSort}" />
<TextBox
x:Name="txtSort"
Grid.Row="8"
Grid.Column="1"
Width="100"
HorizontalAlignment="Left"
Classes="Margin4" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvPrevProfile}" />
<TextBox
x:Name="txtPrevProfile"
Grid.Row="9"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvNextProfile}" />
<TextBox
x:Name="txtNextProfile"
Grid.Row="10"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
Grid.ColumnSpan="2"
VerticalAlignment="Center"
Classes="Margin4"
Text="{x:Static resx:ResUI.LvMoreUrl}" />
<TextBox
x:Name="txtMoreUrl"
Grid.Row="12"
Grid.Column="1"
MinHeight="100"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Classes="TextArea Margin4"
MinLines="4"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
</Grid>
</ScrollViewer>
</DockPanel>
</Window>

View file

@ -0,0 +1,64 @@
using Avalonia;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class SubEditWindow : ReactiveWindow<SubEditViewModel>
{
public SubEditWindow()
{
InitializeComponent();
}
public SubEditWindow(SubItem subItem)
{
InitializeComponent();
Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close();
ViewModel = new SubEditViewModel(subItem, UpdateViewHandler);
Global.SubConvertTargets.ForEach(it =>
{
cmbConvertTarget.Items.Add(it);
});
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.SelectedSource.remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.url, v => v.txtUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.moreUrl, v => v.txtMoreUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.enabled, v => v.togEnable.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.autoUpdateInterval, v => v.txtAutoUpdateInterval.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.userAgent, v => v.txtUserAgent.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.sort, v => v.txtSort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.convertTarget, v => v.cmbConvertTarget.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.prevProfile, v => v.txtPrevProfile.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.nextProfile, v => v.txtNextProfile.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
}
return await Task.FromResult(true);
}
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
txtRemarks.Focus();
}
}
}

View file

@ -0,0 +1,71 @@
<Window
x:Class="v2rayN.Desktop.Views.SubSettingWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSubSetting}"
Width="800"
Height="600"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:SubSettingViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
Classes="Margin8"
DockPanel.Dock="Top"
Orientation="Horizontal">
<Menu>
<MenuItem x:Name="menuSubAdd" Header="{x:Static resx:ResUI.menuSubAdd}" />
<MenuItem x:Name="menuSubDelete" Header="{x:Static resx:ResUI.menuSubDelete}" />
<MenuItem x:Name="menuSubEdit" Header="{x:Static resx:ResUI.menuSubEdit}" />
<MenuItem x:Name="menuSubShare" Header="{x:Static resx:ResUI.menuSubShare}" />
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuClose}" />
</Menu>
</StackPanel>
<DataGrid
x:Name="lstSubscription"
AutoGenerateColumns="False"
BorderThickness="1"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding SubItems}">
<DataGrid.KeyBindings>
<KeyBinding Command="{Binding SubDeleteCmd}" Gesture="Delete" />
</DataGrid.KeyBindings>
<DataGrid.Columns>
<DataGridTextColumn
Width="120"
Binding="{Binding remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="150"
Binding="{Binding url}"
Header="{x:Static resx:ResUI.LvUrl}" />
<DataGridTextColumn
Width="100"
Binding="{Binding enabled}"
Header="{x:Static resx:ResUI.LvEnabled}" />
<DataGridTextColumn
Width="150"
Binding="{Binding autoUpdateInterval}"
Header="{x:Static resx:ResUI.LvAutoUpdateInterval}" />
<DataGridTextColumn
Width="150"
Binding="{Binding userAgent}"
Header="{x:Static resx:ResUI.LvUserAgent}" />
<DataGridTextColumn
Width="80"
Binding="{Binding sort}"
Header="{x:Static resx:ResUI.LvSort}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>

View file

@ -0,0 +1,109 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using DynamicData;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class SubSettingWindow : ReactiveWindow<SubSettingViewModel>
{
private bool _manualClose = false;
public SubSettingWindow()
{
InitializeComponent();
menuClose.Click += menuClose_Click;
this.Closing += SubSettingWindow_Closing;
ViewModel = new SubSettingViewModel(UpdateViewHandler);
lstSubscription.DoubleTapped += LstSubscription_DoubleTapped;
lstSubscription.SelectionChanged += LstSubscription_SelectionChanged;
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.lstSubscription.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstSubscription.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close();
break;
case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(this, ResUI.RemoveServer) == ButtonResult.No)
{
return false;
}
break;
case EViewAction.SubEditWindow:
if (obj is null) return false;
var window = new SubEditWindow((SubItem)obj);
await window.ShowDialog(this);
break;
case EViewAction.ShareSub:
if (obj is null) return false;
await ShareSub((string)obj);
break;
}
return await Task.FromResult(true);
}
private async Task ShareSub(string url)
{
if (Utils.IsNullOrEmpty(url))
{
return;
}
var dialog = new QrcodeView(url);
await dialog.ShowDialog(this);
}
private void LstSubscription_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
ViewModel?.EditSubAsync(false);
}
private void LstSubscription_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
List<SubItem> lst = [];
foreach (var item in lstSubscription.SelectedItems)
{
lst.Add((SubItem)item);
}
ViewModel.SelectedSources = lst;
}
private void menuClose_Click(object? sender, RoutedEventArgs e)
{
_manualClose = true;
this.Close(ViewModel?.IsModified);
}
private void SubSettingWindow_Closing(object? sender, WindowClosingEventArgs e)
{
if (ViewModel?.IsModified == true)
{
if (!_manualClose)
{
menuClose_Click(null, null);
}
}
}
}
}

View file

@ -0,0 +1,72 @@
<UserControl
x:Class="v2rayN.Desktop.Views.ThemeSettingView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:v2rayN.Desktop.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:ThemeSettingViewModel"
mc:Ignorable="d">
<Button Width="30" Height="30">
<Button.Content>
<Image
Width="20"
Height="20"
Source="/Assets/more.png" />
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Width="100"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsColorMode}" />
<ToggleSwitch
x:Name="togDarkMode"
Grid.Row="0"
Grid.Column="1"
Classes="Margin8" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Width="100"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsFontSize}" />
<ComboBox
x:Name="cmbCurrentFontSize"
Grid.Row="1"
Grid.Column="1"
Width="100"
Classes="Margin8" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Grid.Row="2"
Grid.Column="0"
Width="100"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsLanguage}" />
<ComboBox
x:Name="cmbCurrentLanguage"
Grid.Row="2"
Grid.Column="1"
Width="100"
Classes="Margin8" />
</StackPanel>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</UserControl>

View file

@ -0,0 +1,37 @@
using Avalonia;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables;
using v2rayN.Desktop.ViewModels;
namespace v2rayN.Desktop.Views
{
/// <summary>
/// ThemeSettingView.xaml
/// </summary>
public partial class ThemeSettingView : ReactiveUserControl<ThemeSettingViewModel>
{
public ThemeSettingView()
{
InitializeComponent();
ViewModel = new ThemeSettingViewModel();
for (int i = Global.MinFontSize; i <= Global.MinFontSize + 10; i++)
{
cmbCurrentFontSize.Items.Add(i);
}
Global.Languages.ForEach(it =>
{
cmbCurrentLanguage.Items.Add(it);
});
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.ColorModeDark, v => v.togDarkMode.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CurrentFontSize, v => v.cmbCurrentFontSize.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CurrentLanguage, v => v.cmbCurrentLanguage.SelectedValue).DisposeWith(disposables);
});
}
}
}

View file

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<FileVersion>6.55</FileVersion>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<ProjectCapability Include="Avalonia" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.3" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.3" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.6" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0.2" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.1.0.2" />
<PackageReference Include="ReactiveUI" Version="20.1.1" />
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ServiceLib\ServiceLib.csproj" />
</ItemGroup>
</Project>

View file

@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtosLib", "ProtosLib\Prot
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PacLib", "PacLib\PacLib.csproj", "{EE4E6CD8-8353-446B-8F29-A841A02AE5EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayUpgrade", "v2rayUpgrade\v2rayUpgrade.csproj", "{3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceLib", "ServiceLib\ServiceLib.csproj", "{1B6456C4-FFAA-4298-80F6-7B689A6E9243}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib", "ServiceLib\ServiceLib.csproj", "{1B6456C4-FFAA-4298-80F6-7B689A6E9243}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayN.Desktop", "v2rayN.Desktop\v2rayN.Desktop.csproj", "{5D16541A-F971-4C17-9315-BB8955E3F984}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayUpgrade", "v2rayUpgrade\v2rayUpgrade.csproj", "{47D68B1C-601C-4C69-873B-FFF0DC13EC97}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -31,14 +33,18 @@ Global
{EE4E6CD8-8353-446B-8F29-A841A02AE5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE4E6CD8-8353-446B-8F29-A841A02AE5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE4E6CD8-8353-446B-8F29-A841A02AE5EC}.Release|Any CPU.Build.0 = Release|Any CPU
{3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Release|Any CPU.Build.0 = Release|Any CPU
{1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Release|Any CPU.Build.0 = Release|Any CPU
{5D16541A-F971-4C17-9315-BB8955E3F984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D16541A-F971-4C17-9315-BB8955E3F984}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D16541A-F971-4C17-9315-BB8955E3F984}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D16541A-F971-4C17-9315-BB8955E3F984}.Release|Any CPU.Build.0 = Release|Any CPU
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE