mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-01 19:12:08 +03:00
Profiles Select Window (#7891)
* Profiles Select Window * Sort * wpf * avalonia * Allow single select * Fix * Add Config Type Filter * Remove unnecessary
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Splat;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
public class ProfilesSelectViewModel : MyReactiveObject
|
||||
{
|
||||
#region private prop
|
||||
|
||||
private List<ProfileItem> _lstProfile;
|
||||
private string _serverFilter = string.Empty;
|
||||
private Dictionary<string, bool> _dicHeaderSort = new();
|
||||
private string _subIndexId = string.Empty;
|
||||
// ConfigType filter state: default include-mode with all types selected
|
||||
private List<EConfigType> _filterConfigTypes = new();
|
||||
private bool _filterExclude = false;
|
||||
|
||||
#endregion private prop
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
|
||||
[Reactive]
|
||||
public ProfileItemModel SelectedProfile { get; set; }
|
||||
|
||||
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedSub { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ServerFilter { get; set; }
|
||||
|
||||
// Include/Exclude filter for ConfigType
|
||||
public List<EConfigType> 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
|
||||
|
||||
public ProfilesSelectViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
_subIndexId = _config.SubIndexId ?? string.Empty;
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.SelectedSub,
|
||||
y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id)
|
||||
.Subscribe(async c => await SubSelectedChangedAsync(c));
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.ServerFilter,
|
||||
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
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
SelectedProfile = new();
|
||||
SelectedSub = new();
|
||||
|
||||
// Default: include mode with all ConfigTypes selected
|
||||
try
|
||||
{
|
||||
FilterExclude = false;
|
||||
FilterConfigTypes = Enum.GetValues(typeof(EConfigType)).Cast<EConfigType>().ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
FilterConfigTypes = new();
|
||||
}
|
||||
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Actions
|
||||
|
||||
public bool CanOk()
|
||||
{
|
||||
return SelectedProfile != null && !SelectedProfile.IndexId.IsNullOrEmpty();
|
||||
}
|
||||
|
||||
public bool SelectFinish()
|
||||
{
|
||||
if (!CanOk())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
return true;
|
||||
}
|
||||
#endregion Actions
|
||||
|
||||
#region Servers && Groups
|
||||
|
||||
private async Task SubSelectedChangedAsync(bool c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_subIndexId = SelectedSub?.Id;
|
||||
|
||||
await RefreshServers();
|
||||
|
||||
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||
}
|
||||
|
||||
private async Task ServerFilterChanged(bool c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_serverFilter = ServerFilter;
|
||||
if (_serverFilter.IsNullOrEmpty())
|
||||
{
|
||||
await RefreshServers();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshServers()
|
||||
{
|
||||
await RefreshServersBiz();
|
||||
}
|
||||
|
||||
private async Task RefreshServersBiz()
|
||||
{
|
||||
var lstModel = await GetProfileItemsEx(_subIndexId, _serverFilter);
|
||||
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
||||
|
||||
ProfileItems.Clear();
|
||||
ProfileItems.AddRange(lstModel);
|
||||
if (lstModel.Count > 0)
|
||||
{
|
||||
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||
if (selected != null)
|
||||
{
|
||||
SelectedProfile = selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedProfile = lstModel.First();
|
||||
}
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||
}
|
||||
|
||||
public async Task RefreshSubscriptions()
|
||||
{
|
||||
SubItems.Clear();
|
||||
|
||||
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
foreach (var item in await AppManager.Instance.SubItems())
|
||||
{
|
||||
SubItems.Add(item);
|
||||
}
|
||||
if (_subIndexId != null && SubItems.FirstOrDefault(t => t.Id == _subIndexId) != null)
|
||||
{
|
||||
SelectedSub = SubItems.FirstOrDefault(t => t.Id == _subIndexId);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedSub = SubItems.First();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(_subIndexId, filter);
|
||||
|
||||
//await ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||
|
||||
lstModel = (from t in lstModel
|
||||
select new ProfileItemModel
|
||||
{
|
||||
IndexId = t.IndexId,
|
||||
ConfigType = t.ConfigType,
|
||||
Remarks = t.Remarks,
|
||||
Address = t.Address,
|
||||
Port = t.Port,
|
||||
Security = t.Security,
|
||||
Network = t.Network,
|
||||
StreamSecurity = t.StreamSecurity,
|
||||
Subid = t.Subid,
|
||||
SubRemarks = t.SubRemarks,
|
||||
IsActive = t.IndexId == _config.IndexId,
|
||||
}).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;
|
||||
}
|
||||
|
||||
public async Task<ProfileItem?> GetProfileItem()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedProfile?.IndexId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var indexId = SelectedProfile.IndexId;
|
||||
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return null;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public async Task<List<ProfileItem>?> GetProfileItems()
|
||||
{
|
||||
if (SelectedProfiles == null || SelectedProfiles.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var lst = new List<ProfileItem>();
|
||||
foreach (var sp in SelectedProfiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sp?.IndexId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
|
||||
if (item != null)
|
||||
{
|
||||
lst.Add(item);
|
||||
}
|
||||
}
|
||||
if (lst.Count == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return null;
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
public void SortServer(string colName)
|
||||
{
|
||||
if (colName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prop = typeof(ProfileItemModel).GetProperty(colName);
|
||||
if (prop == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dicHeaderSort.TryAdd(colName, true);
|
||||
var asc = _dicHeaderSort[colName];
|
||||
|
||||
var comparer = Comparer<object?>.Create((a, b) =>
|
||||
{
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (a is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (b is null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (a.GetType() == b.GetType() && a is IComparable ca)
|
||||
{
|
||||
return ca.CompareTo(b);
|
||||
}
|
||||
return string.Compare(a.ToString(), b.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
object? KeySelector(ProfileItemModel x)
|
||||
{
|
||||
return prop.GetValue(x);
|
||||
}
|
||||
|
||||
IEnumerable<ProfileItemModel> sorted = asc
|
||||
? ProfileItems.OrderBy(KeySelector, comparer)
|
||||
: ProfileItems.OrderByDescending(KeySelector, comparer);
|
||||
|
||||
var list = sorted.ToList();
|
||||
ProfileItems.Clear();
|
||||
ProfileItems.AddRange(list);
|
||||
|
||||
_dicHeaderSort[colName] = !asc;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion Servers && Groups
|
||||
|
||||
#region Public API
|
||||
|
||||
// External setter for ConfigType filter
|
||||
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
|
||||
{
|
||||
FilterConfigTypes = types?.Distinct().ToList() ?? new List<EConfigType>();
|
||||
FilterExclude = exclude;
|
||||
}
|
||||
|
||||
#endregion Public API
|
||||
}
|
||||
Reference in New Issue
Block a user