using Avalonia.VisualTree; using DialogHostAvalonia; using v2rayN.Desktop.Common; namespace v2rayN.Desktop.Views; public partial class ProfilesView : ReactiveUserControl { 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().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(); 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 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 LstProfilesRowFormat = DataFormat.CreateInProcessFormat("LstProfilesRow"); private async void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e) { try { if (e.Source is not Visual visualSource) { return; } var row = visualSource.FindAncestorOfType(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(true); if (targetRow is not { DataContext: ProfileItemModel targetItem }) { return; } if (ReferenceEquals(sourceItem, targetItem)) { return; } if (lstProfiles.ItemsSource is not IList 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 }