From c9ec34b836ba70670da8cd9f57fe0f53a723ec7f Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 7 Sep 2025 15:51:10 +0800 Subject: [PATCH] Add Config Type Filter --- .../ViewModels/ProfilesSelectViewModel.cs | 61 +++++++++++++++++++ .../Views/ProfilesSelectWindow.axaml.cs | 4 ++ .../Views/RoutingRuleDetailsWindow.axaml.cs | 1 + .../Views/SubEditWindow.axaml.cs | 2 + .../v2rayN/Views/ProfilesSelectWindow.xaml.cs | 4 ++ .../Views/RoutingRuleDetailsWindow.xaml.cs | 1 + v2rayN/v2rayN/Views/SubEditWindow.xaml.cs | 2 + 7 files changed, 75 insertions(+) diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs index 8e2ecdbc..219a9ff8 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs @@ -20,6 +20,9 @@ public class ProfilesSelectViewModel : MyReactiveObject private string _serverFilter = string.Empty; private Dictionary _dicHeaderSort = new(); private string _subIndexId = string.Empty; + // ConfigType filter state: default include-mode with all types selected + private List _filterConfigTypes = new(); + private bool _filterExclude = false; #endregion private prop @@ -40,6 +43,20 @@ public class ProfilesSelectViewModel : MyReactiveObject [Reactive] public string ServerFilter { get; set; } + // Include/Exclude filter for ConfigType + public List FilterConfigTypes + { + get => _filterConfigTypes; + set => this.RaiseAndSetIfChanged(ref _filterConfigTypes, value); + } + + [Reactive] + public bool FilterExclude + { + get => _filterExclude; + set => this.RaiseAndSetIfChanged(ref _filterExclude, value); + } + #endregion ObservableCollection #region Init @@ -61,6 +78,15 @@ public class ProfilesSelectViewModel : MyReactiveObject y => y != null && _serverFilter != y) .Subscribe(async c => await ServerFilterChanged(c)); + // React to ConfigType filter changes + this.WhenAnyValue(x => x.FilterExclude) + .Skip(1) + .Subscribe(async _ => await RefreshServersBiz()); + + this.WhenAnyValue(x => x.FilterConfigTypes) + .Skip(1) + .Subscribe(async _ => await RefreshServersBiz()); + #endregion WhenAnyValue && ReactiveCommand #region AppEvents @@ -85,6 +111,17 @@ public class ProfilesSelectViewModel : MyReactiveObject SelectedProfile = new(); SelectedSub = new(); + // Default: include mode with all ConfigTypes selected + try + { + FilterExclude = false; + FilterConfigTypes = Enum.GetValues(typeof(EConfigType)).Cast().ToList(); + } + catch + { + FilterConfigTypes = new(); + } + await RefreshSubscriptions(); await RefreshServers(); } @@ -258,6 +295,19 @@ public class ProfilesSelectViewModel : MyReactiveObject TotalUp = t22 == null ? "" : Utils.HumanFy(t22.TotalUp) }).OrderBy(t => t.Sort).ToList(); + // Apply ConfigType filter (include or exclude) + if (FilterConfigTypes != null && FilterConfigTypes.Count > 0) + { + if (FilterExclude) + { + lstModel = lstModel.Where(t => !FilterConfigTypes.Contains(t.ConfigType)).ToList(); + } + else + { + lstModel = lstModel.Where(t => FilterConfigTypes.Contains(t.ConfigType)).ToList(); + } + } + return lstModel; } @@ -360,4 +410,15 @@ public class ProfilesSelectViewModel : MyReactiveObject } #endregion Servers && Groups + + #region Public API + + // External setter for ConfigType filter + public void SetConfigTypeFilter(IEnumerable types, bool exclude = false) + { + FilterConfigTypes = types?.Distinct().ToList() ?? new List(); + FilterExclude = exclude; + } + + #endregion Public API } diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs index 41030e43..5df0251a 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs @@ -72,6 +72,10 @@ public partial class ProfilesSelectWindow : ReactiveWindow types, bool exclude = false) + => ViewModel?.SetConfigTypeFilter(types, exclude); + private async Task UpdateViewHandler(EViewAction action, object? obj) { switch (action) diff --git a/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml.cs index 62a2fae9..04b1219c 100644 --- a/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml.cs @@ -98,6 +98,7 @@ public partial class RoutingRuleDetailsWindow : WindowBase(this); if (result == true) { diff --git a/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs index 6712fb3b..486be1dd 100644 --- a/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs @@ -64,6 +64,7 @@ public partial class SubEditWindow : WindowBase private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); + selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); var result = await selectWindow.ShowDialog(this); if (result == true) { @@ -78,6 +79,7 @@ public partial class SubEditWindow : WindowBase private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); + selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); var result = await selectWindow.ShowDialog(this); if (result == true) { diff --git a/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs b/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs index 49690d6e..e9e33ec5 100644 --- a/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs @@ -66,6 +66,10 @@ public partial class ProfilesSelectWindow } } + // Expose ConfigType filter controls to callers + public void SetConfigTypeFilter(IEnumerable types, bool exclude = false) + => ViewModel?.SetConfigTypeFilter(types, exclude); + #region Event private async Task UpdateViewHandler(EViewAction action, object? obj) diff --git a/v2rayN/v2rayN/Views/RoutingRuleDetailsWindow.xaml.cs b/v2rayN/v2rayN/Views/RoutingRuleDetailsWindow.xaml.cs index 8d37921f..806dde65 100644 --- a/v2rayN/v2rayN/Views/RoutingRuleDetailsWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/RoutingRuleDetailsWindow.xaml.cs @@ -93,6 +93,7 @@ public partial class RoutingRuleDetailsWindow private async void BtnSelectProfile_Click(object sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); + selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); if (selectWindow.ShowDialog() == true) { var profile = await selectWindow.ProfileItem; diff --git a/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs b/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs index 79df19d6..e37076e7 100644 --- a/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs @@ -58,6 +58,7 @@ public partial class SubEditWindow private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); + selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); if (selectWindow.ShowDialog() == true) { var profile = await selectWindow.ProfileItem; @@ -71,6 +72,7 @@ public partial class SubEditWindow private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); + selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); if (selectWindow.ShowDialog() == true) { var profile = await selectWindow.ProfileItem;