namespace ServiceLib.ViewModels; public class ProfilesSelectViewModel : MyReactiveObject, ICloseable { public event EventHandler? RequestClose; public Interaction ProfilesFocusInteraction { get; } = new(); #region private prop private string _serverFilter = string.Empty; private readonly Dictionary _dicHeaderSort = new(); private string _subIndexId = string.Empty; // ConfigType filter state: default include-mode with all types selected #endregion private prop public ReactiveCommand SaveCmd { get; } #region ObservableCollection public IObservableCollection ProfileItems { get; } = new ObservableCollectionExtended(); public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); [Reactive] public ProfileItemModel SelectedProfile { get; set; } public IList SelectedProfiles { get; set; } [Reactive] public SubItem SelectedSub { get; set; } [Reactive] public string ServerFilter { get; set; } // Include/Exclude filter for ConfigType [Reactive] public List FilterConfigTypes { get; set; } [Reactive] public bool FilterExclude { get; set; } [Reactive] public bool MultiSelect { get; set; } #endregion ObservableCollection #region Init public ProfilesSelectViewModel() { _config = AppManager.Instance.Config; _subIndexId = _config.SubIndexId ?? string.Empty; #region WhenAnyValue && ReactiveCommand SaveCmd = ReactiveCommand.Create(() => { SelectFinish(); }); 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().ToList(); } catch { FilterConfigTypes = []; } await RefreshSubscriptions(); await RefreshServers(); } #endregion Init #region Actions public bool CanOk() { return SelectedProfile != null && !SelectedProfile.IndexId.IsNullOrEmpty(); } public bool SelectFinish() { if (!CanOk()) { return false; } RequestClose?.Invoke(this, EventArgs.Empty); return true; } #endregion Actions #region Servers && Groups private async Task SubSelectedChangedAsync(bool c) { if (!c) { return; } _subIndexId = SelectedSub?.Id; await RefreshServers(); try { await ProfilesFocusInteraction.Handle(Unit.Default); } catch (UnhandledInteractionException) { } } 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); ProfileItems.Clear(); ProfileItems.AddRange(lstModel); if (lstModel.Count > 0) { var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); SelectedProfile = selected ?? lstModel.First(); } } private async Task RefreshSubscriptions() { var subItems = await AppManager.Instance.SubItems(); subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers }); SubItems.Clear(); SubItems.AddRange(subItems); SelectedSub = (_config.SubIndexId.IsNotEmpty() ? subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) : null) ?? subItems.FirstOrDefault(); } private async Task?> GetProfileItemsEx(string subid, string filter) { var lstModel = await AppManager.Instance.ProfileModels(_subIndexId, filter); 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 is { 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 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?> GetProfileItems() { if (SelectedProfiles == null || SelectedProfiles.Count == 0) { return null; } var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId)); 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.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 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 types, bool exclude = false) { FilterConfigTypes = types?.Distinct().ToList() ?? []; FilterExclude = exclude; } #endregion Public API }