From b8e832160325e7e26c48de6c30576853b55a678d Mon Sep 17 00:00:00 2001 From: DHR60 <192860629+DHR60@users.noreply.github.com> Date: Fri, 4 Sep 2026 06:46:41 +0000 Subject: [PATCH] Fix ViewModel and UI (#10093) * Fix avalonia ScrollToEnd * Refactor ViewModel --- .../Base/BulkObservableCollection.cs | 12 ++-- v2rayN/ServiceLib/Manager/TaskManager.cs | 6 +- .../Statistics/StatisticsSingboxService.cs | 6 +- .../Statistics/StatisticsXrayService.cs | 6 +- .../ViewModels/AddGroupServerViewModel.cs | 3 +- .../ViewModels/CheckUpdateViewModel.cs | 13 ++--- .../ViewModels/ClashConnectionsViewModel.cs | 8 +-- .../ViewModels/MainWindowViewModel.cs | 18 +++--- v2rayN/ServiceLib/ViewModels/MsgViewModel.cs | 20 +++---- .../ViewModels/ProfilesSelectViewModel.cs | 39 +++++-------- .../ViewModels/ProfilesViewModel.cs | 52 ++++++----------- .../ViewModels/RoutingRuleSettingViewModel.cs | 4 +- .../ViewModels/RoutingSettingViewModel.cs | 7 +-- .../ViewModels/StatusBarViewModel.cs | 58 ++++++------------- .../ViewModels/SubSettingViewModel.cs | 3 +- v2rayN/v2rayN.Desktop/GlobalUsings.cs | 1 + .../ViewModels/ThemeSettingViewModel.cs | 23 ++++---- v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs | 3 + v2rayN/v2rayN/GlobalUsings.cs | 1 + .../ViewModels/ThemeSettingViewModel.cs | 36 +++++------- 20 files changed, 130 insertions(+), 189 deletions(-) diff --git a/v2rayN/ServiceLib/Base/BulkObservableCollection.cs b/v2rayN/ServiceLib/Base/BulkObservableCollection.cs index de8b33ae..90881a6c 100644 --- a/v2rayN/ServiceLib/Base/BulkObservableCollection.cs +++ b/v2rayN/ServiceLib/Base/BulkObservableCollection.cs @@ -73,18 +73,16 @@ public class BulkObservableCollection : ObservableCollection public void ReplaceRange(IEnumerable? collection) { - if (collection == null) - { - return; - } - _suppressNotification = true; try { Items.Clear(); - foreach (var item in collection) + if (collection != null) { - Items.Add(item); + foreach (var item in collection) + { + Items.Add(item); + } } } finally diff --git a/v2rayN/ServiceLib/Manager/TaskManager.cs b/v2rayN/ServiceLib/Manager/TaskManager.cs index 39b2a43e..0e19182b 100644 --- a/v2rayN/ServiceLib/Manager/TaskManager.cs +++ b/v2rayN/ServiceLib/Manager/TaskManager.cs @@ -12,7 +12,11 @@ public class TaskManager _config = config; _updateFunc = updateFunc; - Task.Run(ScheduledTasks); + _ = Task.Factory.StartNew( + ScheduledTasks, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); } private async Task ScheduledTasks() diff --git a/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs b/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs index 7d663322..4458529e 100644 --- a/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs +++ b/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs @@ -17,7 +17,11 @@ public class StatisticsSingboxService _updateFunc = updateFunc; _exitFlag = false; - _ = Task.Run(Run); + _ = Task.Factory.StartNew( + Run, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); } private async Task Init() diff --git a/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs b/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs index a64fed00..b7b67e88 100644 --- a/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs +++ b/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs @@ -15,7 +15,11 @@ public class StatisticsXrayService _updateFunc = updateFunc; _exitFlag = false; - _ = Task.Run(Run); + _ = Task.Factory.StartNew( + Run, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); } public void Close() diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs index e29bc16b..970bd8cc 100644 --- a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -218,8 +218,7 @@ public partial class AddGroupServerViewModel : MyReactiveObject, ICloseable public async Task UpdatePreviewList() { - AllProfilePreviewItemsObs.Clear(); - AllProfilePreviewItemsObs.AddRange(await GroupProfileManager.GetChildProfileItemsByProtocolExtra(GetUpdatedProtocolExtra())); + AllProfilePreviewItemsObs.ReplaceRange(await GroupProfileManager.GetChildProfileItemsByProtocolExtra(GetUpdatedProtocolExtra())); } private async Task SaveServerAsync() diff --git a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs index dc0646c1..abba48cb 100644 --- a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs @@ -36,14 +36,10 @@ public partial class CheckUpdateViewModel : MyReactiveObject EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate; EnableUpdateViaProxy = _config.CheckUpdateItem.UpdateViaProxy; - this.WhenAnyValue( - x => x.EnableCheckPreReleaseUpdate, - y => y == true) - .Subscribe(c => _ = OnCheckPreReleaseUpdateChanged()); + this.WhenAnyValue(x => x.EnableCheckPreReleaseUpdate) + .SubscribeAsync(async _ => await OnCheckPreReleaseUpdateChanged()); - this.WhenAnyValue( - x => x.EnableUpdateViaProxy, - y => y == true) + this.WhenAnyValue(x => x.EnableUpdateViaProxy) .Subscribe(c => _ = OnUpdateViaProxyChanged()); RefreshCheckUpdateItems(); @@ -57,8 +53,7 @@ public partial class CheckUpdateViewModel : MyReactiveObject models.Add(GetGeoFileCheckUpdateModel()); - CheckUpdateModels.Clear(); - CheckUpdateModels.AddRange(models); + CheckUpdateModels.ReplaceRange(models); } private CheckUpdateModel GetCheckUpdateModel(ECoreType coreType) diff --git a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs index 749565dd..c8842bff 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs @@ -59,8 +59,6 @@ public partial class ClashConnectionsViewModel : MyReactiveObject public async Task RefreshConnections(List? connections) { - ConnectionItems.Clear(); - var dtNow = DateTime.Now; var lstModel = new List(); foreach (var item in connections ?? []) @@ -85,12 +83,8 @@ public partial class ClashConnectionsViewModel : MyReactiveObject lstModel.Add(model); } - if (lstModel.Count <= 0) - { - return; - } - ConnectionItems.AddRange(lstModel); + ConnectionItems.ReplaceRange(lstModel); await Task.CompletedTask; } diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index e2a3d7ef..98fbfd79 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -257,19 +257,19 @@ public partial class MainWindowViewModel : MyReactiveObject AppEvents.AddServerViaClipboardRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async _ => await AddServerViaClipboardAsync(null)); + .SubscribeAsync(async _ => await AddServerViaClipboardAsync(null)); AppEvents.HasUpdateNotified .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async bl => BlNewUpdate = bl); + .Subscribe(bl => BlNewUpdate = bl); #endregion AppEvents ProfilesViewModel.RefreshServersRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async _ => await RefreshServers()); + .SubscribeAsync(async _ => await RefreshServers()); var vmReloadRequestedList = new List> { @@ -282,23 +282,23 @@ public partial class MainWindowViewModel : MyReactiveObject { reloadRequested .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async _ => await Reload()); + .SubscribeAsync(async _ => await Reload()); } StatusBarViewModel.AddServerViaScanRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async _ => await AddServerViaScanAsync()); + .SubscribeAsync(async _ => await AddServerViaScanAsync()); StatusBarViewModel.AddServerViaClipboardRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async _ => await AddServerViaClipboardAsync(null)); + .SubscribeAsync(async _ => await AddServerViaClipboardAsync(null)); StatusBarViewModel.ShowHideWindowRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async blShow => + .SubscribeAsync(async blShow => { await ShowHideWindowInteraction.HandleSafe(blShow); }); @@ -306,12 +306,12 @@ public partial class MainWindowViewModel : MyReactiveObject StatusBarViewModel.SetDefaultServerRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async indexId => await ProfilesViewModel.SetDefaultServer(indexId)); + .SubscribeAsync(async indexId => await ProfilesViewModel.SetDefaultServer(indexId)); StatusBarViewModel.SubscriptionsUpdateRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); + .SubscribeAsync(async blProxy => await UpdateSubscriptionProcess("", blProxy)); _ = Init(); } diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index eaa336dc..21c3049b 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -22,18 +22,16 @@ public partial class MsgViewModel : MyReactiveObject AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; this.WhenAnyValue( - x => x.MsgFilter) - .Subscribe(c => DoMsgFilter()); + x => x.MsgFilter) + .Subscribe(c => DoMsgFilter()); - this.WhenAnyValue( - x => x.AutoRefresh, - y => y == true) - .Subscribe(c => _config.MsgUIItem.AutoRefresh = AutoRefresh); + this.WhenAnyValue(x => x.AutoRefresh) + .Subscribe(_ => _config.MsgUIItem.AutoRefresh = AutoRefresh); AppEvents.SendMsgViewRequested - .AsObservable() - //.ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(content => _ = AppendQueueMsg(content)); + .AsObservable() + //.ObserveOn(RxSchedulers.MainThreadScheduler) + .Subscribe(content => _ = AppendQueueMsg(content)); } public void FlushQueueMsg() @@ -74,9 +72,9 @@ public partial class MsgViewModel : MyReactiveObject { try { - await DispatcherShowMsgInteraction.HandleSafe(sb.ToString()); + await DispatcherShowMsgInteraction.Handle(sb.ToString()); } - catch (Exception) + catch { _queueMsg.Enqueue(sb.ToString()); } diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs index 4d7e7bb6..99eea332 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs @@ -61,24 +61,22 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable 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.SelectedSub) + .Where(y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id) + .SubscribeAsync(async _ => await SubSelectedChangedAsync()); - this.WhenAnyValue( - x => x.ServerFilter, - y => y != null && _serverFilter != y) - .Subscribe(async c => await ServerFilterChanged(c)); + this.WhenAnyValue(x => x.ServerFilter) + .Where(y => y != null && _serverFilter != y) + .SubscribeAsync(async _ => await ServerFilterChanged()); // React to ConfigType filter changes this.WhenAnyValue(x => x.FilterExclude) .Skip(1) - .Subscribe(async _ => await RefreshServersBiz()); + .SubscribeAsync(async _ => await RefreshServersBiz()); this.WhenAnyValue(x => x.FilterConfigTypes) .Skip(1) - .Subscribe(async _ => await RefreshServersBiz()); + .SubscribeAsync(async _ => await RefreshServersBiz()); #endregion WhenAnyValue && ReactiveCommand @@ -128,12 +126,8 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable #region Servers && Groups - private async Task SubSelectedChangedAsync(bool c) + private async Task SubSelectedChangedAsync() { - if (!c) - { - return; - } _subIndexId = SelectedSub?.Id; await RefreshServers(); @@ -141,12 +135,8 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable await ProfilesFocusInteraction.HandleSafe(RxVoid.Default); } - private async Task ServerFilterChanged(bool c) + private async Task ServerFilterChanged() { - if (!c) - { - return; - } _serverFilter = ServerFilter; if (_serverFilter.IsNullOrEmpty()) { @@ -163,8 +153,7 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable { var lstModel = await GetProfileItemsEx(_subIndexId, _serverFilter); - ProfileItems.Clear(); - ProfileItems.AddRange(lstModel); + ProfileItems.ReplaceRange(lstModel); if (lstModel.Count > 0) { var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); @@ -177,8 +166,7 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable var subItems = await AppManager.Instance.SubItems(); subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers }); - SubItems.Clear(); - SubItems.AddRange(subItems); + SubItems.ReplaceRange(subItems); SelectedSub = (_config.SubIndexId.IsNotEmpty() ? subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) @@ -298,8 +286,7 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable : ProfileItems.OrderByDescending(KeySelector, comparer); var list = sorted.ToList(); - ProfileItems.Clear(); - ProfileItems.AddRange(list); + ProfileItems.ReplaceRange(list); _dicHeaderSort[colName] = !asc; diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index a273aafd..f173f58f 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -103,19 +103,16 @@ public partial class ProfilesViewModel : MyReactiveObject x => x.SelectedProfile, selectedSource => selectedSource != null && !selectedSource.IndexId.IsNullOrEmpty()); - this.WhenAnyValue( - x => x.SelectedSub, - y => y != null && !y.Remarks.IsNullOrEmpty() && _config.SubIndexId != y.Id) - .Subscribe(async c => await SubSelectedChangedAsync(c)); - this.WhenAnyValue( - x => x.SelectedMoveToGroup, - y => y != null && !y.Remarks.IsNullOrEmpty()) - .Subscribe(async c => await MoveToGroup(c)); + this.WhenAnyValue(x => x.SelectedSub) + .Where(y => y != null && !y.Remarks.IsNullOrEmpty() && _config.SubIndexId != y.Id) + .SubscribeAsync(async _ => await SubSelectedChangedAsync()); + this.WhenAnyValue(x => x.SelectedMoveToGroup) + .Where(y => y != null && !y.Remarks.IsNullOrEmpty()) + .SubscribeAsync(async _ => await MoveToGroup()); - this.WhenAnyValue( - x => x.ServerFilter, - y => y != null && _serverFilter != y) - .Subscribe(async c => await ServerFilterChanged(c)); + this.WhenAnyValue(x => x.ServerFilter) + .Where(y => y != null && _serverFilter != y) + .SubscribeAsync(async _ => await ServerFilterChanged()); //servers delete EditServerCmd = ReactiveCommand.CreateFromTask(async () => @@ -249,7 +246,7 @@ public partial class ProfilesViewModel : MyReactiveObject AppEvents.DispatcherStatisticsRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async result => await UpdateStatistics(result)); + .SubscribeAsync(async result => await UpdateStatistics(result)); #endregion AppEvents @@ -335,12 +332,8 @@ public partial class ProfilesViewModel : MyReactiveObject #region Servers && Groups - private async Task SubSelectedChangedAsync(bool c) + private async Task SubSelectedChangedAsync() { - if (!c) - { - return; - } _config.SubIndexId = SelectedSub?.Id; await RefreshServers(); @@ -348,12 +341,8 @@ public partial class ProfilesViewModel : MyReactiveObject await ProfilesFocusInteraction.HandleSafe(RxVoid.Default); } - private async Task ServerFilterChanged(bool c) + private async Task ServerFilterChanged() { - if (!c) - { - return; - } _serverFilter = ServerFilter; if (_serverFilter.IsNullOrEmpty()) { @@ -375,8 +364,7 @@ public partial class ProfilesViewModel : MyReactiveObject var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter); _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)) ?? []; - ProfileItems.Clear(); - ProfileItems.AddRange(lstModel ?? []); + ProfileItems.ReplaceRange(lstModel ?? []); if (lstModel?.Count > 0) { ProfileItemModel? selected = null; @@ -397,8 +385,7 @@ public partial class ProfilesViewModel : MyReactiveObject var subItems = await AppManager.Instance.SubItems(); subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers }); - SubItems.Clear(); - SubItems.AddRange(subItems); + SubItems.ReplaceRange(subItems); SelectedSub = (_config.SubIndexId.IsNotEmpty() ? subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) @@ -672,13 +659,8 @@ public partial class ProfilesViewModel : MyReactiveObject } //move server - private async Task MoveToGroup(bool c) + private async Task MoveToGroup() { - if (!c) - { - return; - } - var lstSelected = await GetProfileItems(true); if (lstSelected == null) { @@ -901,7 +883,7 @@ public partial class ProfilesViewModel : MyReactiveObject if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subEditViewModel) == true) { await RefreshSubscriptions(); - await SubSelectedChangedAsync(true); + await SubSelectedChangedAsync(); } } @@ -920,7 +902,7 @@ public partial class ProfilesViewModel : MyReactiveObject await ConfigHandler.DeleteSubItem(_config, item.Id); await RefreshSubscriptions(); - await SubSelectedChangedAsync(true); + await SubSelectedChangedAsync(); } #endregion Subscription diff --git a/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs index 3b4f1104..706c9253 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs @@ -100,8 +100,6 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable public void RefreshRulesItems() { - RulesItems.Clear(); - var models = new List(); foreach (var item in _rules) { @@ -120,7 +118,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable }; models.Add(it); } - RulesItems.AddRange(models); + RulesItems.ReplaceRange(models); } public async Task RuleEditAsync(bool blNew) diff --git a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs index f7ad92a8..709e3cb6 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs @@ -61,10 +61,10 @@ public partial class RoutingSettingViewModel : MyReactiveObject x => x.DomainStrategy4Singbox) .Skip(1) .DistinctUntilChanged() - .Subscribe(x => + .SubscribeAsync(async x => { IsModified = true; - _ = SaveSettingsAsync(); + await SaveSettingsAsync(); }); } @@ -83,7 +83,6 @@ public partial class RoutingSettingViewModel : MyReactiveObject public async Task RefreshRoutingItems() { - RoutingItems.Clear(); var models = new List(); var routings = await AppManager.Instance.RoutingItems(); @@ -102,7 +101,7 @@ public partial class RoutingSettingViewModel : MyReactiveObject }; models.Add(it); } - RoutingItems.AddRange(models); + RoutingItems.ReplaceRange(models); } /// diff --git a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs index c4983ff7..5cfe417f 100644 --- a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs @@ -123,26 +123,21 @@ public partial class StatusBarViewModel : MyReactiveObject #region WhenAnyValue && ReactiveCommand - this.WhenAnyValue( - x => x.SelectedRouting, - y => y != null && !y.Remarks.IsNullOrEmpty()) - .Subscribe(async c => await RoutingSelectedChangedAsync(c)); + this.WhenAnyValue(x => x.SelectedRouting) + .Where(y => y != null && !y.Remarks.IsNullOrEmpty()) + .SubscribeAsync(async _ => await RoutingSelectedChangedAsync()); - this.WhenAnyValue( - x => x.SelectedServer, - y => y != null && !y.Text.IsNullOrEmpty()) - .Subscribe(ServerSelectedChanged); + this.WhenAnyValue(x => x.SelectedServer) + .Where(y => y != null && !y.Text.IsNullOrEmpty()) + .Subscribe(_ => ServerSelectedChanged()); SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; - this.WhenAnyValue( - x => x.SystemProxySelected, - y => y >= 0) - .Subscribe(async c => await DoSystemProxySelected(c)); + this.WhenAnyValue(x => x.SystemProxySelected) + .Where(y => y >= 0) + .SubscribeAsync(async _ => await DoSystemProxySelected()); - this.WhenAnyValue( - x => x.EnableTun, - y => y == true) - .Subscribe(async c => await DoEnableTun(c)); + this.WhenAnyValue(x => x.EnableTun) + .SubscribeAsync(async _ => await DoEnableTun()); CopyProxyCmdToClipboardCmd = ReactiveCommand.CreateFromTask(async () => { @@ -207,12 +202,12 @@ public partial class StatusBarViewModel : MyReactiveObject AppEvents.DispatcherStatisticsRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async result => await UpdateStatistics(result)); + .SubscribeAsync(async result => await UpdateStatistics(result)); AppEvents.SysProxyChangeRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(async result => await SetListenerType(result)); + .SubscribeAsync(async result => await SetListenerType(result)); #endregion AppEvents @@ -300,19 +295,14 @@ public partial class StatusBarViewModel : MyReactiveObject var models = lstModel.Select(it => new ComboItem { ID = it.IndexId, Text = it.GetSummary() }).ToList(); BlServers = true; - Servers.Clear(); - Servers.AddRange(models); + Servers.ReplaceRange(models); // Update the ItemsSource before SelectedItem so a collection reset does not clear the tray selection. SelectedServer = models.FirstOrDefault(it => it.ID == _config.IndexId) ?? new(); } - private void ServerSelectedChanged(bool c) + private void ServerSelectedChanged() { - if (!c) - { - return; - } if (SelectedServer == null) { return; @@ -402,19 +392,13 @@ public partial class StatusBarViewModel : MyReactiveObject { var routings = await AppManager.Instance.RoutingItems(); - RoutingItems.Clear(); - RoutingItems.AddRange(routings); + RoutingItems.ReplaceRange(routings); SelectedRouting = routings.FirstOrDefault(t => t.IsActive == true); } - private async Task RoutingSelectedChangedAsync(bool c) + private async Task RoutingSelectedChangedAsync() { - if (!c) - { - return; - } - if (SelectedRouting == null) { return; @@ -434,12 +418,8 @@ public partial class StatusBarViewModel : MyReactiveObject } } - private async Task DoSystemProxySelected(bool c) + private async Task DoSystemProxySelected() { - if (!c) - { - return; - } if (_config.SystemProxyItem.SysProxyType == (ESysProxyType)SystemProxySelected) { return; @@ -447,7 +427,7 @@ public partial class StatusBarViewModel : MyReactiveObject await SetListenerType((ESysProxyType)SystemProxySelected); } - private async Task DoEnableTun(bool c) + private async Task DoEnableTun() { if (_config.TunModeItem.EnableTun == EnableTun) { diff --git a/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs index 8ba8f00f..d3a75a34 100644 --- a/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs @@ -55,8 +55,7 @@ public partial class SubSettingViewModel : MyReactiveObject public async Task RefreshSubItems() { - SubItems.Clear(); - SubItems.AddRange(await AppManager.Instance.SubItems()); + SubItems.ReplaceRange(await AppManager.Instance.SubItems()); } public async Task EditSubAsync(bool blNew) diff --git a/v2rayN/v2rayN.Desktop/GlobalUsings.cs b/v2rayN/v2rayN.Desktop/GlobalUsings.cs index 95fbd967..f15be259 100644 --- a/v2rayN/v2rayN.Desktop/GlobalUsings.cs +++ b/v2rayN/v2rayN.Desktop/GlobalUsings.cs @@ -22,6 +22,7 @@ global using ReactiveUI; global using ReactiveUI.Avalonia; global using ReactiveUI.Primitives; global using ReactiveUI.Primitives.Disposables; +global using ReactiveUI.Primitives.Extensions; global using ReactiveUI.SourceGenerators; global using ServiceLib; global using ServiceLib.Base; diff --git a/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs b/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs index 8728cfbe..5fd7f5b2 100644 --- a/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs +++ b/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs @@ -35,39 +35,38 @@ public partial class ThemeSettingViewModel : MyReactiveObject CurrentLanguage = _config.UiItem.CurrentLanguage; this.WhenAnyValue(x => x.CurrentTheme) - .Subscribe(c => + .Where(y => y.IsNotEmpty()) + .SubscribeAsync(async _ => { if (_config.UiItem.CurrentTheme != CurrentTheme) { _config.UiItem.CurrentTheme = CurrentTheme; ModifyTheme(); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); } }); - this.WhenAnyValue( - x => x.CurrentFontSize, - y => y > 0) - .Subscribe(c => + this.WhenAnyValue(x => x.CurrentFontSize) + .Where(y => y > 0) + .SubscribeAsync(async _ => { if (_config.UiItem.CurrentFontSize != CurrentFontSize && CurrentFontSize >= Global.MinFontSize) { _config.UiItem.CurrentFontSize = CurrentFontSize; ModifyFontSize(); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); } }); - this.WhenAnyValue( - x => x.CurrentLanguage, - y => y != null && !y.IsNullOrEmpty()) - .Subscribe(c => + this.WhenAnyValue(x => x.CurrentLanguage) + .Where(y => !y.IsNullOrEmpty()) + .SubscribeAsync(async _ => { if (CurrentLanguage.IsNotEmpty() && _config.UiItem.CurrentLanguage != CurrentLanguage) { _config.UiItem.CurrentLanguage = CurrentLanguage; Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips); } }); diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs index 76ae59d0..d7bc486c 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -50,6 +50,9 @@ public partial class MsgView : ReactiveUserControl if (togScrollToEnd.IsChecked ?? true) { txtMsg.ScrollToEnd(); + Dispatcher.UIThread.Invoke(() => + txtMsg.ScrollTo(txtMsg.LineCount, 0, AvaloniaEdit.Rendering.VisualYPosition.TextBottom, txtMsg.Bounds.Height, 0), + DispatcherPriority.Background); } } diff --git a/v2rayN/v2rayN/GlobalUsings.cs b/v2rayN/v2rayN/GlobalUsings.cs index 2948b5fa..14823ad3 100644 --- a/v2rayN/v2rayN/GlobalUsings.cs +++ b/v2rayN/v2rayN/GlobalUsings.cs @@ -19,6 +19,7 @@ global using ReactiveUI; global using ReactiveUI.Builder; global using ReactiveUI.Primitives; global using ReactiveUI.Primitives.Disposables; +global using ReactiveUI.Primitives.Extensions; global using ReactiveUI.SourceGenerators; global using ServiceLib; global using ServiceLib.Base; diff --git a/v2rayN/v2rayN/ViewModels/ThemeSettingViewModel.cs b/v2rayN/v2rayN/ViewModels/ThemeSettingViewModel.cs index 125ccf31..42fda62c 100644 --- a/v2rayN/v2rayN/ViewModels/ThemeSettingViewModel.cs +++ b/v2rayN/v2rayN/ViewModels/ThemeSettingViewModel.cs @@ -55,23 +55,21 @@ public partial class ThemeSettingViewModel : MyReactiveObject CurrentFontSize = _config.UiItem.CurrentFontSize; CurrentLanguage = _config.UiItem.CurrentLanguage; - this.WhenAnyValue( - x => x.CurrentTheme, - y => y != null && !y.IsNullOrEmpty()) - .Subscribe(c => + this.WhenAnyValue(x => x.CurrentTheme) + .Where(y => y != null && !y.IsNullOrEmpty()) + .SubscribeAsync(async _ => { if (_config.UiItem.CurrentTheme != CurrentTheme) { _config.UiItem.CurrentTheme = CurrentTheme; ModifyTheme(); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); } }); - this.WhenAnyValue( - x => x.SelectedSwatch, - y => y != null && !y.Name.IsNullOrEmpty()) - .Subscribe(c => + this.WhenAnyValue(x => x.SelectedSwatch) + .Where(y => y != null && !y.Name.IsNullOrEmpty()) + .SubscribeAsync(async _ => { if (SelectedSwatch == null || SelectedSwatch.Name.IsNullOrEmpty() @@ -84,33 +82,31 @@ public partial class ThemeSettingViewModel : MyReactiveObject { _config.UiItem.ColorPrimaryName = SelectedSwatch?.Name; ChangePrimaryColor(SelectedSwatch.ExemplarHue.Color); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); } }); - this.WhenAnyValue( - x => x.CurrentFontSize, - y => y > 0) - .Subscribe(c => + this.WhenAnyValue(x => x.CurrentFontSize) + .Where(y => y > 0) + .SubscribeAsync(async _ => { if (_config.UiItem.CurrentFontSize != CurrentFontSize) { _config.UiItem.CurrentFontSize = CurrentFontSize; ModifyFontSize(); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); } }); - this.WhenAnyValue( - x => x.CurrentLanguage, - y => y != null && !y.IsNullOrEmpty()) - .Subscribe(c => + this.WhenAnyValue(x => x.CurrentLanguage) + .Where(y => y != null && !y.IsNullOrEmpty()) + .SubscribeAsync(async _ => { if (CurrentLanguage.IsNotEmpty() && _config.UiItem.CurrentLanguage != CurrentLanguage) { _config.UiItem.CurrentLanguage = CurrentLanguage; Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); - _ = ConfigHandler.SaveConfig(_config); + await ConfigHandler.SaveConfig(_config); NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips); } });