Files
2dust_v2rayN/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs
T
DHR60 d924c6f557 ReactiveUI 24 (#9678)
* ReactiveUI.SourceGenerators

* Primitives

* Try fix
2026-08-01 10:08:43 +08:00

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
}