mirror of
https://github.com/2dust/v2rayN.git
synced 2026-09-29 02:07:58 +03:00
532 lines
19 KiB
C#
532 lines
19 KiB
C#
using Avalonia.VisualTree;
|
|
using DialogHostAvalonia;
|
|
using v2rayN.Desktop.Common;
|
|
|
|
namespace v2rayN.Desktop.Views;
|
|
|
|
public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|
{
|
|
private static Config _config;
|
|
private static readonly string _tag = "ProfilesView";
|
|
|
|
public ProfilesView()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_config = AppManager.Instance.Config;
|
|
|
|
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;
|
|
lstProfiles.Sorting += LstProfiles_Sorting;
|
|
if (_config.UiItem.EnableDragDropSort)
|
|
{
|
|
lstProfiles.SetValue(DragDrop.AllowDropProperty, true);
|
|
|
|
lstProfiles.AddHandler(PointerPressedEvent, LstProfiles_PointerPressed, RoutingStrategies.Bubble, true);
|
|
lstProfiles.AddHandler(DragDrop.DragOverEvent, LstProfiles_DragOver, RoutingStrategies.Bubble);
|
|
lstProfiles.AddHandler(DragDrop.DropEvent, LstProfiles_Drop, RoutingStrategies.Bubble);
|
|
}
|
|
|
|
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);
|
|
this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.menuSubAdd).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.DeleteSubCmd, v => v.menuSubDelete).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.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).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.UdpTestServerCmd, v => v.menuUdpTestServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.RemoveInvalidServerResultCmd, v => v.menuRemoveInvalidServerResult).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.FastRealPingCmd, v => v.btnFastRealPing).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);
|
|
this.BindCommand(ViewModel, vm => vm.Export2InnerUriCmd, v => v.menuExport2InnerUri).DisposeWith(disposables);
|
|
|
|
ViewModel.ShowYesNoInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
var message = interaction.Input;
|
|
var result = await UI.ShowYesNo(message);
|
|
interaction.SetOutput(result == ButtonResult.Yes);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.SaveFileDialogInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
var viewModel = ViewModel;
|
|
if (viewModel is null)
|
|
{
|
|
interaction.SetOutput(false);
|
|
return;
|
|
}
|
|
var profileItem = interaction.Input;
|
|
var fileName = await UI.SaveFileDialog("");
|
|
if (fileName.IsNullOrEmpty())
|
|
{
|
|
interaction.SetOutput(false);
|
|
return;
|
|
}
|
|
await viewModel.Export2ClientConfigResult(fileName, profileItem);
|
|
interaction.SetOutput(true);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.SetClipboardDataInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
var strData = interaction.Input;
|
|
await AvaUtils.SetClipboardData(this, strData);
|
|
interaction.SetOutput(RxVoid.Default);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.ProfilesFocusInteraction.RegisterHandler(interaction =>
|
|
{
|
|
lstProfiles.Focus();
|
|
interaction.SetOutput(RxVoid.Default);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.ShareServerInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
var url = interaction.Input;
|
|
if (url.IsNullOrEmpty())
|
|
{
|
|
interaction.SetOutput(RxVoid.Default);
|
|
return;
|
|
}
|
|
await ShareServer(url);
|
|
interaction.SetOutput(RxVoid.Default);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.DispatcherRefreshServersBizInteraction.RegisterHandler(interaction =>
|
|
{
|
|
Dispatcher.UIThread.Post(RefreshServersBiz, DispatcherPriority.Default);
|
|
interaction.SetOutput(RxVoid.Default);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.AdjustMainLvColWidthInteraction.RegisterHandler(interaction =>
|
|
{
|
|
//AutofitColumnWidth();
|
|
interaction.SetOutput(RxVoid.Default);
|
|
}).DisposeWith(disposables);
|
|
|
|
AppEvents.AppExitRequested
|
|
.AsObservable()
|
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
|
.Subscribe(_ => StorageUI())
|
|
.DisposeWith(disposables);
|
|
});
|
|
|
|
RestoreUI();
|
|
}
|
|
|
|
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
|
{
|
|
e.Handled = true;
|
|
|
|
if (ViewModel != null && e.Column?.Tag?.ToString() != null)
|
|
{
|
|
await ViewModel.SortServer(e.Column.Tag.ToString());
|
|
}
|
|
|
|
e.Handled = false;
|
|
}
|
|
|
|
#region Event
|
|
|
|
public async Task ShareServer(string url)
|
|
{
|
|
if (url.IsNullOrEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var dialog = new QrcodeView(url);
|
|
await DialogHost.Show(dialog);
|
|
}
|
|
|
|
public void RefreshServersBiz()
|
|
{
|
|
if (lstProfiles.SelectedIndex >= 0)
|
|
{
|
|
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
|
|
}
|
|
}
|
|
|
|
private void lstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (ViewModel != null)
|
|
{
|
|
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
|
|
}
|
|
}
|
|
|
|
private void LstProfiles_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
|
|
{
|
|
var source = e.Source as Border;
|
|
if (source?.Name == "HeaderBackground")
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_config.UiItem.DoubleClick2Activate)
|
|
{
|
|
ViewModel?.SetDefaultServer();
|
|
}
|
|
else
|
|
{
|
|
ViewModel?.EditServerAsync();
|
|
}
|
|
}
|
|
|
|
private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e)
|
|
{
|
|
e.Row.Header = $" {e.Row.Index + 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 is KeyModifiers.Control or KeyModifiers.Meta)
|
|
{
|
|
switch (e.Key)
|
|
{
|
|
case Key.A:
|
|
menuSelectAll_Click(null, null);
|
|
break;
|
|
|
|
case Key.C:
|
|
ViewModel?.Export2ShareUrlAsync(false);
|
|
break;
|
|
|
|
case Key.D:
|
|
ViewModel?.EditServerAsync();
|
|
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
|
|
{
|
|
switch (e.Key)
|
|
{
|
|
case Key.Enter:
|
|
//case Key.Return:
|
|
ViewModel?.SetDefaultServer();
|
|
break;
|
|
|
|
case Key.Delete:
|
|
case Key.Back:
|
|
ViewModel?.RemoveServerAsync();
|
|
break;
|
|
|
|
case Key.T:
|
|
ViewModel?.MoveServer(EMove.Top);
|
|
break;
|
|
|
|
case Key.U:
|
|
ViewModel?.MoveServer(EMove.Up);
|
|
break;
|
|
|
|
case Key.D:
|
|
ViewModel?.MoveServer(EMove.Down);
|
|
break;
|
|
|
|
case Key.B:
|
|
ViewModel?.MoveServer(EMove.Bottom);
|
|
break;
|
|
|
|
case Key.Escape:
|
|
ViewModel?.ServerSpeedtestStop();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
AutofitColumnWidth();
|
|
}
|
|
|
|
private void AutofitColumnWidth()
|
|
{
|
|
try
|
|
{
|
|
//First scroll horizontally to the initial position to avoid the control crash bug
|
|
if (lstProfiles.SelectedIndex >= 0)
|
|
{
|
|
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, lstProfiles.Columns[0]);
|
|
}
|
|
else
|
|
{
|
|
var model = lstProfiles.ItemsSource.Cast<ProfileItemModel>();
|
|
if (model.Any())
|
|
{
|
|
lstProfiles.ScrollIntoView(model.First(), lstProfiles.Columns[0]);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
foreach (var it in lstProfiles.Columns)
|
|
{
|
|
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
try
|
|
{
|
|
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
|
|
var displayIndex = 0;
|
|
foreach (var item in lvColumnItem)
|
|
{
|
|
foreach (var item2 in lstProfiles.Columns)
|
|
{
|
|
if (item2.Tag == null)
|
|
{
|
|
continue;
|
|
}
|
|
if (item2.Tag.Equals(item.Name))
|
|
{
|
|
if (item.Width < 0)
|
|
{
|
|
item2.IsVisible = false;
|
|
}
|
|
else
|
|
{
|
|
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
|
|
item2.DisplayIndex = displayIndex++;
|
|
}
|
|
if (item.Name.StartsWith("to", StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
item2.IsVisible = _config.GuiItem.EnableStatistics;
|
|
}
|
|
if (item.Name.Equals("IpInfo", StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
item2.IsVisible = _config.SpeedTestItem.IPAPIUrl.IsNotEmpty() && !_config.UiItem.HideColumnIpInfo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
}
|
|
|
|
private void StorageUI()
|
|
{
|
|
try
|
|
{
|
|
List<ColumnItem> lvColumnItem = [];
|
|
foreach (var item2 in lstProfiles.Columns)
|
|
{
|
|
if (item2.Tag == null)
|
|
{
|
|
continue;
|
|
}
|
|
lvColumnItem.Add(new()
|
|
{
|
|
Name = (string)item2.Tag,
|
|
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
|
|
Index = item2.DisplayIndex
|
|
});
|
|
}
|
|
_config.UiItem.MainColumnItem = lvColumnItem;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
}
|
|
|
|
#endregion UI
|
|
|
|
#region Drag and Drop
|
|
|
|
private static readonly DataFormat<object> LstProfilesRowFormat =
|
|
DataFormat.CreateInProcessFormat<object>("LstProfilesRow");
|
|
|
|
private async void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (e.Source is not Visual visualSource)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var row = visualSource.FindAncestorOfType<DataGridRow>(true);
|
|
if (row?.DataContext == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.GetCurrentPoint(row).Properties.IsLeftButtonPressed)
|
|
{
|
|
var dragData = new DataTransfer();
|
|
var item = DataTransferItem.Create(LstProfilesRowFormat, row.DataContext);
|
|
dragData.Add(item);
|
|
await DragDrop.DoDragDropAsync(e, dragData, DragDropEffects.Move);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
private void LstProfiles_DragOver(object? sender, DragEventArgs e)
|
|
{
|
|
if (!e.DataTransfer.Contains(LstProfilesRowFormat))
|
|
{
|
|
e.DragEffects = DragDropEffects.None;
|
|
return;
|
|
}
|
|
e.DragEffects = DragDropEffects.Move;
|
|
}
|
|
|
|
private void LstProfiles_Drop(object? sender, DragEventArgs e)
|
|
{
|
|
if (!e.DataTransfer.Contains(LstProfilesRowFormat))
|
|
{
|
|
return;
|
|
}
|
|
ProfileItemModel? sourceItem = null;
|
|
foreach (var item in e.DataTransfer.Items)
|
|
{
|
|
if (!item.Formats.Contains(LstProfilesRowFormat))
|
|
{
|
|
continue;
|
|
}
|
|
if (item.TryGetRaw(LstProfilesRowFormat) is not ProfileItemModel model)
|
|
{
|
|
continue;
|
|
}
|
|
sourceItem = model;
|
|
break;
|
|
}
|
|
if (sourceItem == null)
|
|
{
|
|
return;
|
|
}
|
|
if (e.Source is not Visual visualTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var targetRow = visualTarget.FindAncestorOfType<DataGridRow>(true);
|
|
if (targetRow is not { DataContext: ProfileItemModel targetItem })
|
|
{
|
|
return;
|
|
}
|
|
if (ReferenceEquals(sourceItem, targetItem))
|
|
{
|
|
return;
|
|
}
|
|
if (lstProfiles.ItemsSource is not IList<ProfileItemModel> items)
|
|
{
|
|
return;
|
|
}
|
|
var oldIndex = items.IndexOf(sourceItem);
|
|
var newIndex = items.IndexOf(targetItem);
|
|
if (oldIndex >= 0 && newIndex >= 0)
|
|
{
|
|
ViewModel?.MoveServerTo(oldIndex, targetItem);
|
|
}
|
|
}
|
|
|
|
#endregion Drag and Drop
|
|
}
|