diff --git a/v2rayN/v2rayN/Global.cs b/v2rayN/v2rayN/Global.cs index 09569b03..1bbd9fef 100644 --- a/v2rayN/v2rayN/Global.cs +++ b/v2rayN/v2rayN/Global.cs @@ -74,6 +74,7 @@ namespace v2rayN public const string CommandClearMsg = "CommandClearMsg"; public const string CommandSendMsgView = "CommandSendMsgView"; public const string CommandStopSpeedTest = "CommandStopSpeedTest"; + public const string CommandRefreshProfiles = "CommandRefreshProfiles"; public const string DelayUnit = ""; public const string SpeedUnit = ""; public const int MinFontSize = 10; diff --git a/v2rayN/v2rayN/Handler/Statistics/StatisticsHandler.cs b/v2rayN/v2rayN/Handler/Statistics/StatisticsHandler.cs index a14a6dd8..3b789c75 100644 --- a/v2rayN/v2rayN/Handler/Statistics/StatisticsHandler.cs +++ b/v2rayN/v2rayN/Handler/Statistics/StatisticsHandler.cs @@ -4,6 +4,9 @@ namespace v2rayN.Handler.Statistics { internal class StatisticsHandler { + private static readonly Lazy instance = new(() => new()); + public static StatisticsHandler Instance => instance.Value; + private Config _config; private ServerStatItem? _serverStatItem; private List _lstServerStat; @@ -12,20 +15,17 @@ namespace v2rayN.Handler.Statistics private StatisticsSingbox? _statisticsSingbox; public List ServerStat => _lstServerStat; - public bool Enable { get; set; } - public StatisticsHandler(Config config, Action update) + public void Init(Config config, Action update) { _config = config; - Enable = config.guiItem.enableStatistics; - if (!Enable) + _updateFunc = update; + if (!config.guiItem.enableStatistics) { return; } - _updateFunc = update; - - Init(); + InitData(); _statisticsV2Ray = new StatisticsV2ray(config, UpdateServerStat); _statisticsSingbox = new StatisticsSingbox(config, UpdateServerStat); @@ -55,7 +55,10 @@ namespace v2rayN.Handler.Statistics { try { - SQLiteHelper.Instance.UpdateAll(_lstServerStat); + if (_lstServerStat != null) + { + SQLiteHelper.Instance.UpdateAll(_lstServerStat); + } } catch (Exception ex) { @@ -63,7 +66,7 @@ namespace v2rayN.Handler.Statistics } } - private void Init() + private void InitData() { SQLiteHelper.Instance.Execute($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )"); diff --git a/v2rayN/v2rayN/Models/ServerSpeedItem.cs b/v2rayN/v2rayN/Models/ServerSpeedItem.cs index 274fa493..d5411d32 100644 --- a/v2rayN/v2rayN/Models/ServerSpeedItem.cs +++ b/v2rayN/v2rayN/Models/ServerSpeedItem.cs @@ -1,7 +1,7 @@ namespace v2rayN.Models { [Serializable] - internal class ServerSpeedItem : ServerStatItem + public class ServerSpeedItem : ServerStatItem { public long proxyUp { diff --git a/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs b/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs index 86396b46..7eae4459 100644 --- a/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs @@ -16,7 +16,6 @@ using System.Windows; using System.Windows.Media; using v2rayN.Enums; using v2rayN.Handler; -using v2rayN.Handler.Fmt; using v2rayN.Handler.Statistics; using v2rayN.Models; using v2rayN.Resx; @@ -29,14 +28,9 @@ namespace v2rayN.ViewModels #region private prop private CoreHandler _coreHandler; - private StatisticsHandler _statistics; - private List _lstProfile; - private string _subId = string.Empty; - private string _serverFilter = string.Empty; private static Config _config; private NoticeHandler? _noticeHandler; private readonly PaletteHelper _paletteHelper = new(); - private Dictionary _dicHeaderSort = new(); private Action _updateView; private bool _showInTaskbar; @@ -44,38 +38,18 @@ namespace v2rayN.ViewModels #region ObservableCollection - private IObservableCollection _profileItems = new ObservableCollectionExtended(); - public IObservableCollection ProfileItems => _profileItems; - - private IObservableCollection _subItems = new ObservableCollectionExtended(); - public IObservableCollection SubItems => _subItems; - private IObservableCollection _routingItems = new ObservableCollectionExtended(); public IObservableCollection RoutingItems => _routingItems; private IObservableCollection _servers = new ObservableCollectionExtended(); public IObservableCollection Servers => _servers; - [Reactive] - public ProfileItemModel SelectedProfile { get; set; } - - public IList SelectedProfiles { get; set; } - - [Reactive] - public SubItem SelectedSub { get; set; } - - [Reactive] - public SubItem SelectedMoveToGroup { get; set; } - [Reactive] public RoutingItem SelectedRouting { get; set; } [Reactive] public ComboItem SelectedServer { get; set; } - [Reactive] - public string ServerFilter { get; set; } - [Reactive] public bool BlServers { get; set; } @@ -98,41 +72,9 @@ namespace v2rayN.ViewModels public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } - //servers delete - public ReactiveCommand EditServerCmd { get; } - - public ReactiveCommand RemoveServerCmd { get; } - public ReactiveCommand RemoveDuplicateServerCmd { get; } - public ReactiveCommand CopyServerCmd { get; } - public ReactiveCommand SetDefaultServerCmd { get; } - public ReactiveCommand ShareServerCmd { get; } - public ReactiveCommand SetDefaultMultipleServerCmd { get; } - - //servers move - public ReactiveCommand MoveTopCmd { get; } - - public ReactiveCommand MoveUpCmd { get; } - public ReactiveCommand MoveDownCmd { get; } - public ReactiveCommand MoveBottomCmd { get; } - - //servers ping - public ReactiveCommand MixedTestServerCmd { get; } - - public ReactiveCommand TcpingServerCmd { get; } - public ReactiveCommand RealPingServerCmd { get; } - public ReactiveCommand SpeedServerCmd { get; } - public ReactiveCommand SortServerResultCmd { get; } - - //servers export - public ReactiveCommand Export2ClientConfigCmd { get; } - - public ReactiveCommand Export2ShareUrlCmd { get; } - //Subscription public ReactiveCommand SubSettingCmd { get; } - public ReactiveCommand AddSubCmd { get; } - public ReactiveCommand EditSubCmd { get; } public ReactiveCommand SubUpdateCmd { get; } public ReactiveCommand SubUpdateViaProxyCmd { get; } public ReactiveCommand SubGroupUpdateCmd { get; } @@ -147,7 +89,6 @@ namespace v2rayN.ViewModels public ReactiveCommand RebootAsAdminCmd { get; } public ReactiveCommand ClearServerStatisticsCmd { get; } public ReactiveCommand OpenTheFileLocationCmd { get; } - //public ReactiveCommand ImportOldGuiConfigCmd { get; } //CheckUpdate public ReactiveCommand CheckUpdateNCmd { get; } @@ -169,9 +110,6 @@ namespace v2rayN.ViewModels [Reactive] public ImageSource AppIcon { get; set; } - //[Reactive] - //public bool BlShowTrayTip { get; set; } - #endregion Menu #region System Proxy @@ -246,7 +184,7 @@ namespace v2rayN.ViewModels public string CurrentLanguage { get; set; } [Reactive] - public bool ShowCalshUI { get; set; } + public bool ShowClashUI { get; set; } #endregion UI @@ -257,13 +195,12 @@ namespace v2rayN.ViewModels _updateView = updateView; ThreadPool.RegisterWaitForSingleObject(App.ProgramStarted, OnProgramStarted, null, -1, false); - Locator.CurrentMutable.RegisterLazySingleton(() => new NoticeHandler(snackbarMessageQueue), typeof(NoticeHandler)); - _noticeHandler = Locator.Current.GetService(); + _noticeHandler = new NoticeHandler(snackbarMessageQueue); + Locator.CurrentMutable.RegisterLazySingleton(() => _noticeHandler, typeof(NoticeHandler)); _config = LazyConfig.Instance.GetConfig(); - SelectedProfile = new(); - SelectedSub = new(); - SelectedMoveToGroup = new(); + MessageBus.Current.Listen(Global.CommandRefreshProfiles).Subscribe(x => RefreshServersBiz()); + SelectedRouting = new(); SelectedServer = new(); if (_config.tunModeItem.enableTun) @@ -277,7 +214,6 @@ namespace v2rayN.ViewModels _config.tunModeItem.enableTun = EnableTun = false; } } - _subId = _config.subIndexId; Init(); BindingUI(); @@ -285,19 +221,6 @@ namespace v2rayN.ViewModels #region WhenAnyValue && ReactiveCommand - var canEditRemove = this.WhenAnyValue( - x => x.SelectedProfile, - selectedSource => selectedSource != null && !selectedSource.indexId.IsNullOrEmpty()); - - this.WhenAnyValue( - x => x.SelectedSub, - y => y != null && !y.remarks.IsNullOrEmpty() && _subId != y.id) - .Subscribe(c => SubSelectedChanged(c)); - this.WhenAnyValue( - x => x.SelectedMoveToGroup, - y => y != null && !y.remarks.IsNullOrEmpty()) - .Subscribe(c => MoveToGroup(c)); - this.WhenAnyValue( x => x.SelectedRouting, y => y != null && !y.remarks.IsNullOrEmpty()) @@ -308,11 +231,6 @@ namespace v2rayN.ViewModels y => y != null && !y.Text.IsNullOrEmpty()) .Subscribe(c => ServerSelectedChanged(c)); - this.WhenAnyValue( - x => x.ServerFilter, - y => y != null && _serverFilter != y) - .Subscribe(c => ServerFilterChanged(c)); - SystemProxySelected = (int)_config.sysProxyType; this.WhenAnyValue( x => x.SystemProxySelected, @@ -327,43 +245,43 @@ namespace v2rayN.ViewModels //servers AddVmessServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.VMess); + AddServer(true, EConfigType.VMess); }); AddVlessServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.VLESS); + AddServer(true, EConfigType.VLESS); }); AddShadowsocksServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Shadowsocks); + AddServer(true, EConfigType.Shadowsocks); }); AddSocksServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Socks); + AddServer(true, EConfigType.Socks); }); AddHttpServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Http); + AddServer(true, EConfigType.Http); }); AddTrojanServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Trojan); + AddServer(true, EConfigType.Trojan); }); AddHysteria2ServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Hysteria2); + AddServer(true, EConfigType.Hysteria2); }); AddTuicServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Tuic); + AddServer(true, EConfigType.Tuic); }); AddWireguardServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Wireguard); + AddServer(true, EConfigType.Wireguard); }); AddCustomServerCmd = ReactiveCommand.Create(() => { - EditServer(true, EConfigType.Custom); + AddServer(true, EConfigType.Custom); }); AddServerViaClipboardCmd = ReactiveCommand.Create(() => { @@ -373,97 +291,13 @@ namespace v2rayN.ViewModels { return ScanScreenTaskAsync(); }); - //servers delete - EditServerCmd = ReactiveCommand.Create(() => - { - EditServer(false, EConfigType.Custom); - }, canEditRemove); - RemoveServerCmd = ReactiveCommand.Create(() => - { - RemoveServer(); - }, canEditRemove); - RemoveDuplicateServerCmd = ReactiveCommand.Create(() => - { - RemoveDuplicateServer(); - }); - CopyServerCmd = ReactiveCommand.Create(() => - { - CopyServer(); - }, canEditRemove); - SetDefaultServerCmd = ReactiveCommand.Create(() => - { - SetDefaultServer(); - }, canEditRemove); - ShareServerCmd = ReactiveCommand.Create(() => - { - ShareServer(); - }, canEditRemove); - SetDefaultMultipleServerCmd = ReactiveCommand.Create(() => - { - SetDefaultMultipleServer(); - }, canEditRemove); - //servers move - MoveTopCmd = ReactiveCommand.Create(() => - { - MoveServer(EMove.Top); - }, canEditRemove); - MoveUpCmd = ReactiveCommand.Create(() => - { - MoveServer(EMove.Up); - }, canEditRemove); - MoveDownCmd = ReactiveCommand.Create(() => - { - MoveServer(EMove.Down); - }, canEditRemove); - MoveBottomCmd = ReactiveCommand.Create(() => - { - MoveServer(EMove.Bottom); - }, canEditRemove); - - //servers ping - MixedTestServerCmd = ReactiveCommand.Create(() => - { - ServerSpeedtest(ESpeedActionType.Mixedtest); - }); - TcpingServerCmd = ReactiveCommand.Create(() => - { - ServerSpeedtest(ESpeedActionType.Tcping); - }, canEditRemove); - RealPingServerCmd = ReactiveCommand.Create(() => - { - ServerSpeedtest(ESpeedActionType.Realping); - }, canEditRemove); - SpeedServerCmd = ReactiveCommand.Create(() => - { - ServerSpeedtest(ESpeedActionType.Speedtest); - }, canEditRemove); - SortServerResultCmd = ReactiveCommand.Create(() => - { - SortServer(EServerColName.delayVal.ToString()); - }); - //servers export - Export2ClientConfigCmd = ReactiveCommand.Create(() => - { - Export2ClientConfig(); - }, canEditRemove); - Export2ShareUrlCmd = ReactiveCommand.Create(() => - { - Export2ShareUrl(); - }, canEditRemove); //Subscription SubSettingCmd = ReactiveCommand.Create(() => { SubSetting(); }); - AddSubCmd = ReactiveCommand.Create(() => - { - EditSub(true); - }); - EditSubCmd = ReactiveCommand.Create(() => - { - EditSub(false); - }); + SubUpdateCmd = ReactiveCommand.Create(() => { UpdateSubscriptionProcess("", false); @@ -474,11 +308,11 @@ namespace v2rayN.ViewModels }); SubGroupUpdateCmd = ReactiveCommand.Create(() => { - UpdateSubscriptionProcess(_subId, false); + UpdateSubscriptionProcess(_config.subIndexId, false); }); SubGroupUpdateViaProxyCmd = ReactiveCommand.Create(() => { - UpdateSubscriptionProcess(_subId, true); + UpdateSubscriptionProcess(_config.subIndexId, true); }); //Setting @@ -507,17 +341,13 @@ namespace v2rayN.ViewModels }); ClearServerStatisticsCmd = ReactiveCommand.Create(() => { - _statistics?.ClearAllServerStatistics(); + StatisticsHandler.Instance.ClearAllServerStatistics(); RefreshServers(); }); OpenTheFileLocationCmd = ReactiveCommand.Create(() => { Utils.ProcessStart("Explorer", $"/select,{Utils.GetConfigPath()}"); }); - //ImportOldGuiConfigCmd = ReactiveCommand.Create(() => - //{ - // ImportOldGuiConfig(); - //}); //CheckUpdate CheckUpdateNCmd = ReactiveCommand.Create(() => @@ -586,15 +416,14 @@ namespace v2rayN.ViewModels if (_config.guiItem.enableStatistics) { - _statistics = new StatisticsHandler(_config, UpdateStatisticsHandler); + StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler); } MainFormHandler.Instance.UpdateTask(_config, UpdateTaskHandler); MainFormHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, UpdateTaskHandler); - InitSubscriptionView(); RefreshRoutingsMenu(); - RefreshServers(); + //RefreshServers(); Reload(); ChangeSystemProxyStatus(_config.sysProxyType, true); @@ -647,41 +476,19 @@ namespace v2rayN.ViewModels { try { + if (!_showInTaskbar) + { + return; + } + Application.Current?.Dispatcher.Invoke((Action)(() => { - if (!_showInTaskbar) - { - return; - } - SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.proxyUp), Utils.HumanFy(update.proxyDown)); SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.directUp), Utils.HumanFy(update.directDown)); - if (update.proxyUp + update.proxyDown > 0) + if ((update.proxyUp + update.proxyDown) > 0 && DateTime.Now.Second % 3 == 0) { - var second = DateTime.Now.Second; - if (second % 3 == 0) - { - var item = _profileItems.Where(it => it.indexId == update.indexId).FirstOrDefault(); - if (item != null) - { - item.todayDown = Utils.HumanFy(update.todayDown); - item.todayUp = Utils.HumanFy(update.todayUp); - item.totalDown = Utils.HumanFy(update.totalDown); - item.totalUp = Utils.HumanFy(update.totalUp); - - if (SelectedProfile?.indexId == item.indexId) - { - var temp = JsonUtils.DeepCopy(item); - _profileItems.Replace(item, temp); - SelectedProfile = temp; - } - else - { - _profileItems.Replace(item, JsonUtils.DeepCopy(item)); - } - } - } + Locator.Current.GetService()?.UpdateStatistics(update); } })); } @@ -691,39 +498,6 @@ namespace v2rayN.ViewModels } } - private void UpdateSpeedtestHandler(string indexId, string delay, string speed) - { - Application.Current?.Dispatcher.Invoke((Action)(() => - { - SetTestResult(indexId, delay, speed); - })); - } - - private void SetTestResult(string indexId, string delay, string speed) - { - if (Utils.IsNullOrEmpty(indexId)) - { - _noticeHandler?.SendMessage(delay, true); - _noticeHandler?.Enqueue(delay); - return; - } - var item = _profileItems.Where(it => it.indexId == indexId).FirstOrDefault(); - if (item != null) - { - if (!Utils.IsNullOrEmpty(delay)) - { - int.TryParse(delay, out int temp); - item.delay = temp; - item.delayVal = $"{delay} {Global.DelayUnit}"; - } - if (!Utils.IsNullOrEmpty(speed)) - { - item.speedVal = $"{speed} {Global.SpeedUnit}"; - } - _profileItems.Replace(item, JsonUtils.DeepCopy(item)); - } - } - private void OnHotkeyHandler(EGlobalHotkey e) { switch (e) @@ -758,7 +532,6 @@ namespace v2rayN.ViewModels ConfigHandler.SaveConfig(_config); - //HttpProxyHandle.CloseHttpAgent(config); if (blWindowsShutDown) { SysProxyHandle.ResetIEProxy4WindowsShutDown(); @@ -770,8 +543,8 @@ namespace v2rayN.ViewModels ProfileExHandler.Instance.SaveTo(); - _statistics?.SaveTo(); - _statistics?.Close(); + StatisticsHandler.Instance.SaveTo(); + StatisticsHandler.Instance.Close(); _coreHandler.CoreStop(); Logging.SaveLog("MyAppExit End"); @@ -787,91 +560,15 @@ namespace v2rayN.ViewModels #region Servers && Groups - private void SubSelectedChanged(bool c) + private void RefreshServers() { - if (!c) - { - return; - } - _subId = SelectedSub?.id; - _config.subIndexId = _subId; - - RefreshServers(); - - _updateView(EViewAction.ProfilesFocus); + MessageBus.Current.SendMessage("", Global.CommandRefreshProfiles); } - private void ServerFilterChanged(bool c) + private void RefreshServersBiz() { - if (!c) - { - return; - } - _serverFilter = ServerFilter; - if (Utils.IsNullOrEmpty(_serverFilter)) - { - RefreshServers(); - } - } - - public void RefreshServers() - { - List lstModel = LazyConfig.Instance.ProfileItems(_subId, _serverFilter); - - ConfigHandler.SetDefaultServer(_config, lstModel); - - List lstServerStat = new(); - if (_statistics != null && _statistics.Enable) - { - lstServerStat = _statistics.ServerStat; - } - var lstProfileExs = ProfileExHandler.Instance.ProfileExs; - lstModel = (from t in lstModel - join t2 in lstServerStat on t.indexId equals t2.indexId into t2b - from t22 in t2b.DefaultIfEmpty() - join t3 in lstProfileExs on t.indexId equals t3.indexId into t3b - from t33 in t3b.DefaultIfEmpty() - 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, - sort = t33 == null ? 0 : t33.sort, - delay = t33 == null ? 0 : t33.delay, - delayVal = t33?.delay != 0 ? $"{t33?.delay} {Global.DelayUnit}" : string.Empty, - speedVal = t33?.speed != 0 ? $"{t33?.speed} {Global.SpeedUnit}" : string.Empty, - todayDown = t22 == null ? "" : Utils.HumanFy(t22.todayDown), - todayUp = t22 == null ? "" : Utils.HumanFy(t22.todayUp), - totalDown = t22 == null ? "" : Utils.HumanFy(t22.totalDown), - totalUp = t22 == null ? "" : Utils.HumanFy(t22.totalUp) - }).OrderBy(t => t.sort).ToList(); - _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)); - Application.Current?.Dispatcher.Invoke((Action)(() => { - _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[0]; - } - } - RefreshServersMenu(); //display running server @@ -891,17 +588,19 @@ namespace v2rayN.ViewModels private void RefreshServersMenu() { + var lstModel = LazyConfig.Instance.ProfileItems(_config.subIndexId, ""); + _servers.Clear(); - if (_lstProfile.Count > _config.guiItem.trayMenuServersLimit) + if (lstModel.Count > _config.guiItem.trayMenuServersLimit) { BlServers = false; return; } BlServers = true; - for (int k = 0; k < _lstProfile.Count; k++) + for (int k = 0; k < lstModel.Count; k++) { - ProfileItem it = _lstProfile[k]; + ProfileItem it = lstModel[k]; string name = it.GetSummary(); var item = new ComboItem() { ID = it.indexId, Text = name }; @@ -913,83 +612,24 @@ namespace v2rayN.ViewModels } } - private void InitSubscriptionView() + private void RefreshSubscriptions() { - _subItems.Clear(); - - _subItems.Add(new SubItem { remarks = ResUI.AllGroupServers }); - foreach (var item in LazyConfig.Instance.SubItems().OrderBy(t => t.sort)) - { - _subItems.Add(item); - } - if (_subId != null && _subItems.FirstOrDefault(t => t.id == _subId) != null) - { - SelectedSub = _subItems.FirstOrDefault(t => t.id == _subId); - } - else - { - SelectedSub = _subItems[0]; - } + Locator.Current.GetService()?.RefreshSubscriptions(); } #endregion Servers && Groups #region Add Servers - private int GetProfileItems(out List lstSelecteds, bool latest) + public void AddServer(bool blNew, EConfigType eConfigType) { - lstSelecteds = new List(); - if (SelectedProfiles == null || SelectedProfiles.Count <= 0) + ProfileItem item = new() { - return -1; - } + subid = _config.subIndexId, + configType = eConfigType, + isSub = false, + }; - var orderProfiles = SelectedProfiles?.OrderBy(t => t.sort); - if (latest) - { - foreach (var profile in orderProfiles) - { - var item = LazyConfig.Instance.GetProfileItem(profile.indexId); - if (item is not null) - { - lstSelecteds.Add(item); - } - } - } - else - { - lstSelecteds = JsonUtils.Deserialize>(JsonUtils.Serialize(orderProfiles)); - } - - return 0; - } - - public void EditServer(bool blNew, EConfigType eConfigType) - { - ProfileItem item; - if (blNew) - { - item = new() - { - subid = _subId, - configType = eConfigType, - isSub = false, - }; - } - else - { - if (Utils.IsNullOrEmpty(SelectedProfile?.indexId)) - { - return; - } - item = LazyConfig.Instance.GetProfileItem(SelectedProfile.indexId); - if (item is null) - { - _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); - return; - } - eConfigType = item.configType; - } bool? ret = false; if (eConfigType == EConfigType.Custom) { @@ -1012,10 +652,10 @@ namespace v2rayN.ViewModels public void AddServerViaClipboard() { var clipboardData = Utils.GetClipboardData(); - int ret = ConfigHandler.AddBatchServers(_config, clipboardData!, _subId, false); + int ret = ConfigHandler.AddBatchServers(_config, clipboardData!, _config.subIndexId, false); if (ret > 0) { - InitSubscriptionView(); + RefreshSubscriptions(); RefreshServers(); _noticeHandler?.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret)); } @@ -1039,69 +679,16 @@ namespace v2rayN.ViewModels } else { - int ret = ConfigHandler.AddBatchServers(_config, result, _subId, false); + int ret = ConfigHandler.AddBatchServers(_config, result, _config.subIndexId, false); if (ret > 0) { - InitSubscriptionView(); + RefreshSubscriptions(); RefreshServers(); _noticeHandler?.Enqueue(ResUI.SuccessfullyImportedServerViaScan); } } } - public void RemoveServer() - { - if (GetProfileItems(out List lstSelecteds, true) < 0) - { - return; - } - - if (UI.ShowYesNo(ResUI.RemoveServer) == MessageBoxResult.No) - { - return; - } - var exists = lstSelecteds.Exists(t => t.indexId == _config.indexId); - - ConfigHandler.RemoveServer(_config, lstSelecteds); - _noticeHandler?.Enqueue(ResUI.OperationSuccess); - - RefreshServers(); - if (exists) - { - Reload(); - } - } - - private void RemoveDuplicateServer() - { - var tuple = ConfigHandler.DedupServerList(_config, _subId); - RefreshServers(); - Reload(); - _noticeHandler?.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2)); - } - - private void CopyServer() - { - if (GetProfileItems(out List lstSelecteds, false) < 0) - { - return; - } - if (ConfigHandler.CopyServer(_config, lstSelecteds) == 0) - { - RefreshServers(); - _noticeHandler?.Enqueue(ResUI.OperationSuccess); - } - } - - public void SetDefaultServer() - { - if (Utils.IsNullOrEmpty(SelectedProfile?.indexId)) - { - return; - } - SetDefaultServer(SelectedProfile.indexId); - } - private void SetDefaultServer(string indexId) { if (Utils.IsNullOrEmpty(indexId)) @@ -1143,68 +730,6 @@ namespace v2rayN.ViewModels SetDefaultServer(SelectedServer.ID); } - public async void ShareServer() - { - var item = LazyConfig.Instance.GetProfileItem(SelectedProfile.indexId); - if (item is null) - { - _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); - return; - } - var url = FmtHandler.GetShareUri(item); - if (Utils.IsNullOrEmpty(url)) - { - return; - } - var img = QRCodeHelper.GetQRCode(url); - var dialog = new QrcodeView() - { - imgQrcode = { Source = img }, - txtContent = { Text = url }, - }; - - await DialogHost.Show(dialog, "RootDialog"); - } - - private void SetDefaultMultipleServer() - { - if (GetProfileItems(out List lstSelecteds, true) < 0) - { - return; - } - - if (ConfigHandler.AddCustomServer4Multiple(_config, lstSelecteds, out string indexId) != 0) - { - _noticeHandler?.Enqueue(ResUI.OperationFailed); - return; - } - if (indexId == _config.indexId) - { - Reload(); - } - else - { - SetDefaultServer(indexId); - } - } - - public void SortServer(string colName) - { - if (Utils.IsNullOrEmpty(colName)) - { - return; - } - - _dicHeaderSort.TryAdd(colName, true); - _dicHeaderSort.TryGetValue(colName, out bool asc); - if (ConfigHandler.SortServers(_config, _subId, colName, asc) != 0) - { - return; - } - _dicHeaderSort[colName] = !asc; - RefreshServers(); - } - public void TestServerAvailability() { var item = ConfigHandler.GetDefaultServer(_config); @@ -1226,109 +751,6 @@ namespace v2rayN.ViewModels }); } - //move server - private void MoveToGroup(bool c) - { - if (!c) - { - return; - } - - if (GetProfileItems(out List lstSelecteds, true) < 0) - { - return; - } - - ConfigHandler.MoveToGroup(_config, lstSelecteds, SelectedMoveToGroup.id); - _noticeHandler?.Enqueue(ResUI.OperationSuccess); - - RefreshServers(); - SelectedMoveToGroup = new(); - //Reload(); - } - - public void MoveServer(EMove eMove) - { - var item = _lstProfile.FirstOrDefault(t => t.indexId == SelectedProfile.indexId); - if (item is null) - { - _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); - return; - } - - int index = _lstProfile.IndexOf(item); - if (index < 0) - { - return; - } - if (ConfigHandler.MoveServer(_config, ref _lstProfile, index, eMove) == 0) - { - RefreshServers(); - } - } - - public void MoveServerTo(int startIndex, ProfileItemModel targetItem) - { - var targetIndex = _profileItems.IndexOf(targetItem); - if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex) - { - if (ConfigHandler.MoveServer(_config, ref _lstProfile, startIndex, EMove.Position, targetIndex) == 0) - { - RefreshServers(); - } - } - } - - public void ServerSpeedtest(ESpeedActionType actionType) - { - if (actionType == ESpeedActionType.Mixedtest) - { - SelectedProfiles = _profileItems; - } - if (GetProfileItems(out List lstSelecteds, false) < 0) - { - return; - } - //ClearTestResult(); - new SpeedtestHandler(_config, _coreHandler, lstSelecteds, actionType, UpdateSpeedtestHandler); - } - - private void Export2ClientConfig() - { - var item = LazyConfig.Instance.GetProfileItem(SelectedProfile.indexId); - if (item is null) - { - _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); - return; - } - MainFormHandler.Instance.Export2ClientConfig(item, _config); - } - - public void Export2ShareUrl() - { - if (GetProfileItems(out List lstSelecteds, true) < 0) - { - return; - } - - StringBuilder sb = new(); - foreach (var it in lstSelecteds) - { - var url = FmtHandler.GetShareUri(it); - if (Utils.IsNullOrEmpty(url)) - { - continue; - } - sb.Append(url); - sb.AppendLine(); - } - if (sb.Length > 0) - { - Utils.SetClipboardData(sb.ToString()); - _noticeHandler?.SendMessage(ResUI.BatchExportURLSuccessfully); - } - } - #endregion Add Servers #region Subscription @@ -1337,31 +759,7 @@ namespace v2rayN.ViewModels { if ((new SubSettingWindow()).ShowDialog() == true) { - InitSubscriptionView(); - SubSelectedChanged(true); - } - } - - private void EditSub(bool blNew) - { - SubItem item; - if (blNew) - { - item = new(); - } - else - { - item = LazyConfig.Instance.GetSubItem(_subId); - if (item is null) - { - return; - } - } - var ret = (new SubEditWindow(item)).ShowDialog(); - if (ret == true) - { - InitSubscriptionView(); - SubSelectedChanged(true); + RefreshSubscriptions(); } } @@ -1516,8 +914,8 @@ namespace v2rayN.ViewModels Application.Current?.Dispatcher.Invoke((Action)(() => { BlReloadEnabled = true; - ShowCalshUI = (_config.runningCoreType is ECoreType.sing_box or ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo); - if (ShowCalshUI) + ShowClashUI = (_config.runningCoreType is ECoreType.sing_box or ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo); + if (ShowClashUI) { Locator.Current.GetService()?.ProxiesReload(); } diff --git a/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs b/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs new file mode 100644 index 00000000..3b0ec00a --- /dev/null +++ b/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs @@ -0,0 +1,791 @@ +using DynamicData; +using DynamicData.Binding; +using MaterialDesignThemes.Wpf; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using Splat; +using System.Reactive; +using System.Reactive.Linq; +using System.Text; +using System.Windows; +using v2rayN.Enums; +using v2rayN.Handler; +using v2rayN.Handler.Fmt; +using v2rayN.Handler.Statistics; +using v2rayN.Models; +using v2rayN.Resx; +using v2rayN.Views; + +namespace v2rayN.ViewModels +{ + public class ProfilesViewModel : ReactiveObject + { + #region private prop + + private List _lstProfile; + private string _serverFilter = string.Empty; + private static Config _config; + private NoticeHandler? _noticeHandler; + private Dictionary _dicHeaderSort = new(); + private Action _updateView; + + #endregion private prop + + #region ObservableCollection + + private IObservableCollection _profileItems = new ObservableCollectionExtended(); + public IObservableCollection ProfileItems => _profileItems; + + private IObservableCollection _subItems = new ObservableCollectionExtended(); + public IObservableCollection SubItems => _subItems; + + private IObservableCollection _servers = new ObservableCollectionExtended(); + + [Reactive] + public ProfileItemModel SelectedProfile { get; set; } + + public IList SelectedProfiles { get; set; } + + [Reactive] + public SubItem SelectedSub { get; set; } + + [Reactive] + public SubItem SelectedMoveToGroup { get; set; } + + [Reactive] + public ComboItem SelectedServer { get; set; } + + [Reactive] + public string ServerFilter { get; set; } + + [Reactive] + public bool BlServers { get; set; } + + #endregion ObservableCollection + + #region Menu + + //servers delete + public ReactiveCommand EditServerCmd { get; } + + public ReactiveCommand RemoveServerCmd { get; } + public ReactiveCommand RemoveDuplicateServerCmd { get; } + public ReactiveCommand CopyServerCmd { get; } + public ReactiveCommand SetDefaultServerCmd { get; } + public ReactiveCommand ShareServerCmd { get; } + public ReactiveCommand SetDefaultMultipleServerCmd { get; } + + //servers move + public ReactiveCommand MoveTopCmd { get; } + + public ReactiveCommand MoveUpCmd { get; } + public ReactiveCommand MoveDownCmd { get; } + public ReactiveCommand MoveBottomCmd { get; } + + //servers ping + public ReactiveCommand MixedTestServerCmd { get; } + + public ReactiveCommand TcpingServerCmd { get; } + public ReactiveCommand RealPingServerCmd { get; } + public ReactiveCommand SpeedServerCmd { get; } + public ReactiveCommand SortServerResultCmd { get; } + + //servers export + public ReactiveCommand Export2ClientConfigCmd { get; } + + public ReactiveCommand Export2ShareUrlCmd { get; } + + public ReactiveCommand AddSubCmd { get; } + public ReactiveCommand EditSubCmd { get; } + + #endregion Menu + + #region Init + + public ProfilesViewModel(Action updateView) + { + _updateView = updateView; + + _noticeHandler = Locator.Current.GetService(); + _config = LazyConfig.Instance.GetConfig(); + MessageBus.Current.Listen(Global.CommandRefreshProfiles).Subscribe(x => RefreshServersBiz()); + + SelectedProfile = new(); + SelectedSub = new(); + SelectedMoveToGroup = new(); + SelectedServer = new(); + + RefreshSubscriptions(); + RefreshServers(); + + #region WhenAnyValue && ReactiveCommand + + var canEditRemove = this.WhenAnyValue( + 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(c => SubSelectedChanged(c)); + this.WhenAnyValue( + x => x.SelectedMoveToGroup, + y => y != null && !y.remarks.IsNullOrEmpty()) + .Subscribe(c => MoveToGroup(c)); + + this.WhenAnyValue( + x => x.SelectedServer, + y => y != null && !y.Text.IsNullOrEmpty()) + .Subscribe(c => ServerSelectedChanged(c)); + + this.WhenAnyValue( + x => x.ServerFilter, + y => y != null && _serverFilter != y) + .Subscribe(c => ServerFilterChanged(c)); + + //servers delete + EditServerCmd = ReactiveCommand.Create(() => + { + EditServer(false, EConfigType.Custom); + }, canEditRemove); + RemoveServerCmd = ReactiveCommand.Create(() => + { + RemoveServer(); + }, canEditRemove); + RemoveDuplicateServerCmd = ReactiveCommand.Create(() => + { + RemoveDuplicateServer(); + }); + CopyServerCmd = ReactiveCommand.Create(() => + { + CopyServer(); + }, canEditRemove); + SetDefaultServerCmd = ReactiveCommand.Create(() => + { + SetDefaultServer(); + }, canEditRemove); + ShareServerCmd = ReactiveCommand.Create(() => + { + ShareServer(); + }, canEditRemove); + SetDefaultMultipleServerCmd = ReactiveCommand.Create(() => + { + SetDefaultMultipleServer(); + }, canEditRemove); + //servers move + MoveTopCmd = ReactiveCommand.Create(() => + { + MoveServer(EMove.Top); + }, canEditRemove); + MoveUpCmd = ReactiveCommand.Create(() => + { + MoveServer(EMove.Up); + }, canEditRemove); + MoveDownCmd = ReactiveCommand.Create(() => + { + MoveServer(EMove.Down); + }, canEditRemove); + MoveBottomCmd = ReactiveCommand.Create(() => + { + MoveServer(EMove.Bottom); + }, canEditRemove); + + //servers ping + MixedTestServerCmd = ReactiveCommand.Create(() => + { + ServerSpeedtest(ESpeedActionType.Mixedtest); + }); + TcpingServerCmd = ReactiveCommand.Create(() => + { + ServerSpeedtest(ESpeedActionType.Tcping); + }, canEditRemove); + RealPingServerCmd = ReactiveCommand.Create(() => + { + ServerSpeedtest(ESpeedActionType.Realping); + }, canEditRemove); + SpeedServerCmd = ReactiveCommand.Create(() => + { + ServerSpeedtest(ESpeedActionType.Speedtest); + }, canEditRemove); + SortServerResultCmd = ReactiveCommand.Create(() => + { + SortServer(EServerColName.delayVal.ToString()); + }); + //servers export + Export2ClientConfigCmd = ReactiveCommand.Create(() => + { + Export2ClientConfig(); + }, canEditRemove); + Export2ShareUrlCmd = ReactiveCommand.Create(() => + { + Export2ShareUrl(); + }, canEditRemove); + + //Subscription + + AddSubCmd = ReactiveCommand.Create(() => + { + EditSub(true); + }); + EditSubCmd = ReactiveCommand.Create(() => + { + EditSub(false); + }); + + #endregion WhenAnyValue && ReactiveCommand + } + + #endregion Init + + #region Actions + + private void Reload() + { + Locator.Current.GetService()?.Reload(); + } + + private void UpdateSpeedtestHandler(string indexId, string delay, string speed) + { + Application.Current?.Dispatcher.Invoke((Action)(() => + { + SetTestResult(indexId, delay, speed); + })); + } + + private void SetTestResult(string indexId, string delay, string speed) + { + if (Utils.IsNullOrEmpty(indexId)) + { + _noticeHandler?.SendMessage(delay, true); + _noticeHandler?.Enqueue(delay); + return; + } + var item = _profileItems.Where(it => it.indexId == indexId).FirstOrDefault(); + if (item != null) + { + if (!Utils.IsNullOrEmpty(delay)) + { + int.TryParse(delay, out int temp); + item.delay = temp; + item.delayVal = $"{delay} {Global.DelayUnit}"; + } + if (!Utils.IsNullOrEmpty(speed)) + { + item.speedVal = $"{speed} {Global.SpeedUnit}"; + } + _profileItems.Replace(item, JsonUtils.DeepCopy(item)); + } + } + + public void UpdateStatistics(ServerSpeedItem update) + { + try + { + Application.Current?.Dispatcher.Invoke((Action)(() => + { + var item = _profileItems.Where(it => it.indexId == update.indexId).FirstOrDefault(); + if (item != null) + { + item.todayDown = Utils.HumanFy(update.todayDown); + item.todayUp = Utils.HumanFy(update.todayUp); + item.totalDown = Utils.HumanFy(update.totalDown); + item.totalUp = Utils.HumanFy(update.totalUp); + + if (SelectedProfile?.indexId == item.indexId) + { + var temp = JsonUtils.DeepCopy(item); + _profileItems.Replace(item, temp); + SelectedProfile = temp; + } + else + { + _profileItems.Replace(item, JsonUtils.DeepCopy(item)); + } + } + })); + } + catch + { + } + } + + #endregion Actions + + #region Servers && Groups + + private void SubSelectedChanged(bool c) + { + if (!c) + { + return; + } + _config.subIndexId = SelectedSub?.id; + + RefreshServers(); + + _updateView(EViewAction.ProfilesFocus); + } + + private void ServerFilterChanged(bool c) + { + if (!c) + { + return; + } + _serverFilter = ServerFilter; + if (Utils.IsNullOrEmpty(_serverFilter)) + { + RefreshServers(); + } + } + + public void RefreshServers() + { + MessageBus.Current.SendMessage("", Global.CommandRefreshProfiles); + } + + private void RefreshServersBiz() + { + var lstModel = LazyConfig.Instance.ProfileItems(_config.subIndexId, _serverFilter); + + ConfigHandler.SetDefaultServer(_config, lstModel); + + var lstServerStat = (_config.guiItem.enableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? []; + var lstProfileExs = ProfileExHandler.Instance.ProfileExs; + lstModel = (from t in lstModel + join t2 in lstServerStat on t.indexId equals t2.indexId into t2b + from t22 in t2b.DefaultIfEmpty() + join t3 in lstProfileExs on t.indexId equals t3.indexId into t3b + from t33 in t3b.DefaultIfEmpty() + 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, + sort = t33 == null ? 0 : t33.sort, + delay = t33 == null ? 0 : t33.delay, + delayVal = t33?.delay != 0 ? $"{t33?.delay} {Global.DelayUnit}" : string.Empty, + speedVal = t33?.speed != 0 ? $"{t33?.speed} {Global.SpeedUnit}" : string.Empty, + todayDown = t22 == null ? "" : Utils.HumanFy(t22.todayDown), + todayUp = t22 == null ? "" : Utils.HumanFy(t22.todayUp), + totalDown = t22 == null ? "" : Utils.HumanFy(t22.totalDown), + totalUp = t22 == null ? "" : Utils.HumanFy(t22.totalUp) + }).OrderBy(t => t.sort).ToList(); + _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)); + + Application.Current?.Dispatcher.Invoke((Action)(() => + { + _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[0]; + } + } + })); + } + + public void RefreshSubscriptions() + { + _subItems.Clear(); + + _subItems.Add(new SubItem { remarks = ResUI.AllGroupServers }); + foreach (var item in LazyConfig.Instance.SubItems().OrderBy(t => t.sort)) + { + _subItems.Add(item); + } + if (_config.subIndexId != null && _subItems.FirstOrDefault(t => t.id == _config.subIndexId) != null) + { + SelectedSub = _subItems.FirstOrDefault(t => t.id == _config.subIndexId); + } + else + { + SelectedSub = _subItems[0]; + } + } + + #endregion Servers && Groups + + #region Add Servers + + private int GetProfileItems(out List lstSelecteds, bool latest) + { + lstSelecteds = new List(); + if (SelectedProfiles == null || SelectedProfiles.Count <= 0) + { + return -1; + } + + var orderProfiles = SelectedProfiles?.OrderBy(t => t.sort); + if (latest) + { + foreach (var profile in orderProfiles) + { + var item = LazyConfig.Instance.GetProfileItem(profile.indexId); + if (item is not null) + { + lstSelecteds.Add(item); + } + } + } + else + { + lstSelecteds = JsonUtils.Deserialize>(JsonUtils.Serialize(orderProfiles)); + } + + return 0; + } + + public void EditServer(bool blNew, EConfigType eConfigType) + { + ProfileItem item; + if (blNew) + { + item = new() + { + subid = _config.subIndexId, + configType = eConfigType, + isSub = false, + }; + } + else + { + if (Utils.IsNullOrEmpty(SelectedProfile?.indexId)) + { + return; + } + item = LazyConfig.Instance.GetProfileItem(SelectedProfile.indexId); + if (item is null) + { + _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); + return; + } + eConfigType = item.configType; + } + bool? ret = false; + if (eConfigType == EConfigType.Custom) + { + ret = (new AddServer2Window(item)).ShowDialog(); + } + else + { + ret = (new AddServerWindow(item)).ShowDialog(); + } + if (ret == true) + { + RefreshServers(); + if (item.indexId == _config.indexId) + { + Reload(); + } + } + } + + public void RemoveServer() + { + if (GetProfileItems(out List lstSelecteds, true) < 0) + { + return; + } + + if (UI.ShowYesNo(ResUI.RemoveServer) == MessageBoxResult.No) + { + return; + } + var exists = lstSelecteds.Exists(t => t.indexId == _config.indexId); + + ConfigHandler.RemoveServer(_config, lstSelecteds); + _noticeHandler?.Enqueue(ResUI.OperationSuccess); + + RefreshServers(); + if (exists) + { + Reload(); + } + } + + private void RemoveDuplicateServer() + { + var tuple = ConfigHandler.DedupServerList(_config, _config.subIndexId); + RefreshServers(); + Reload(); + _noticeHandler?.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2)); + } + + private void CopyServer() + { + if (GetProfileItems(out List lstSelecteds, false) < 0) + { + return; + } + if (ConfigHandler.CopyServer(_config, lstSelecteds) == 0) + { + RefreshServers(); + _noticeHandler?.Enqueue(ResUI.OperationSuccess); + } + } + + public void SetDefaultServer() + { + if (Utils.IsNullOrEmpty(SelectedProfile?.indexId)) + { + return; + } + SetDefaultServer(SelectedProfile.indexId); + } + + private void SetDefaultServer(string indexId) + { + if (Utils.IsNullOrEmpty(indexId)) + { + return; + } + if (indexId == _config.indexId) + { + return; + } + var item = LazyConfig.Instance.GetProfileItem(indexId); + if (item is null) + { + _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); + return; + } + + if (ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0) + { + RefreshServers(); + Reload(); + } + } + + private void ServerSelectedChanged(bool c) + { + if (!c) + { + return; + } + if (SelectedServer == null) + { + return; + } + if (Utils.IsNullOrEmpty(SelectedServer.ID)) + { + return; + } + SetDefaultServer(SelectedServer.ID); + } + + public async void ShareServer() + { + var item = LazyConfig.Instance.GetProfileItem(SelectedProfile.indexId); + if (item is null) + { + _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); + return; + } + var url = FmtHandler.GetShareUri(item); + if (Utils.IsNullOrEmpty(url)) + { + return; + } + var img = QRCodeHelper.GetQRCode(url); + var dialog = new QrcodeView() + { + imgQrcode = { Source = img }, + txtContent = { Text = url }, + }; + + await DialogHost.Show(dialog, "RootDialog"); + } + + private void SetDefaultMultipleServer() + { + if (GetProfileItems(out List lstSelecteds, true) < 0) + { + return; + } + + if (ConfigHandler.AddCustomServer4Multiple(_config, lstSelecteds, out string indexId) != 0) + { + _noticeHandler?.Enqueue(ResUI.OperationFailed); + return; + } + if (indexId == _config.indexId) + { + Reload(); + } + else + { + SetDefaultServer(indexId); + } + } + + public void SortServer(string colName) + { + if (Utils.IsNullOrEmpty(colName)) + { + return; + } + + _dicHeaderSort.TryAdd(colName, true); + _dicHeaderSort.TryGetValue(colName, out bool asc); + if (ConfigHandler.SortServers(_config, _config.subIndexId, colName, asc) != 0) + { + return; + } + _dicHeaderSort[colName] = !asc; + RefreshServers(); + } + + //move server + private void MoveToGroup(bool c) + { + if (!c) + { + return; + } + + if (GetProfileItems(out List lstSelecteds, true) < 0) + { + return; + } + + ConfigHandler.MoveToGroup(_config, lstSelecteds, SelectedMoveToGroup.id); + _noticeHandler?.Enqueue(ResUI.OperationSuccess); + + RefreshServers(); + SelectedMoveToGroup = new(); + //Reload(); + } + + public void MoveServer(EMove eMove) + { + var item = _lstProfile.FirstOrDefault(t => t.indexId == SelectedProfile.indexId); + if (item is null) + { + _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); + return; + } + + int index = _lstProfile.IndexOf(item); + if (index < 0) + { + return; + } + if (ConfigHandler.MoveServer(_config, ref _lstProfile, index, eMove) == 0) + { + RefreshServers(); + } + } + + public void MoveServerTo(int startIndex, ProfileItemModel targetItem) + { + var targetIndex = _profileItems.IndexOf(targetItem); + if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex) + { + if (ConfigHandler.MoveServer(_config, ref _lstProfile, startIndex, EMove.Position, targetIndex) == 0) + { + RefreshServers(); + } + } + } + + public void ServerSpeedtest(ESpeedActionType actionType) + { + if (actionType == ESpeedActionType.Mixedtest) + { + SelectedProfiles = _profileItems; + } + if (GetProfileItems(out List lstSelecteds, false) < 0) + { + return; + } + //ClearTestResult(); + var coreHandler = Locator.Current.GetService(); + if (coreHandler != null) + { + new SpeedtestHandler(_config, coreHandler, lstSelecteds, actionType, UpdateSpeedtestHandler); + } + } + + private void Export2ClientConfig() + { + var item = LazyConfig.Instance.GetProfileItem(SelectedProfile.indexId); + if (item is null) + { + _noticeHandler?.Enqueue(ResUI.PleaseSelectServer); + return; + } + MainFormHandler.Instance.Export2ClientConfig(item, _config); + } + + public void Export2ShareUrl() + { + if (GetProfileItems(out List lstSelecteds, true) < 0) + { + return; + } + + StringBuilder sb = new(); + foreach (var it in lstSelecteds) + { + var url = FmtHandler.GetShareUri(it); + if (Utils.IsNullOrEmpty(url)) + { + continue; + } + sb.Append(url); + sb.AppendLine(); + } + if (sb.Length > 0) + { + Utils.SetClipboardData(sb.ToString()); + _noticeHandler?.SendMessage(ResUI.BatchExportURLSuccessfully); + } + } + + #endregion Add Servers + + #region Subscription + + private void EditSub(bool blNew) + { + SubItem item; + if (blNew) + { + item = new(); + } + else + { + item = LazyConfig.Instance.GetSubItem(_config.subIndexId); + if (item is null) + { + return; + } + } + var ret = (new SubEditWindow(item)).ShowDialog(); + if (ret == true) + { + RefreshSubscriptions(); + SubSelectedChanged(true); + } + } + + #endregion Subscription + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index a4e8ca3f..60ad831f 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -32,7 +32,6 @@ - @@ -390,62 +389,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml.cs b/v2rayN/v2rayN/Views/MainWindow.xaml.cs index a2edafce..98b3ea1b 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/MainWindow.xaml.cs @@ -5,17 +5,14 @@ using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; -using v2rayN.Base; using v2rayN.Enums; using v2rayN.Handler; using v2rayN.Models; using v2rayN.Resx; using v2rayN.ViewModels; -using Point = System.Windows.Point; namespace v2rayN.Views { @@ -27,39 +24,13 @@ namespace v2rayN.Views { InitializeComponent(); - // 设置窗口的尺寸不大于屏幕的尺寸 - if (this.Width > SystemParameters.WorkArea.Width) - { - this.Width = SystemParameters.WorkArea.Width; - } - if (this.Height > SystemParameters.WorkArea.Height) - { - this.Height = SystemParameters.WorkArea.Height; - } - - lstGroup.MaxHeight = Math.Floor(SystemParameters.WorkArea.Height * 0.20 / 40) * 40; - _config = LazyConfig.Instance.GetConfig(); App.Current.SessionEnding += Current_SessionEnding; this.Closing += MainWindow_Closing; this.PreviewKeyDown += MainWindow_PreviewKeyDown; - btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; - txtServerFilter.PreviewKeyDown += TxtServerFilter_PreviewKeyDown; - btnShowCalshUI.Click += BtnShowCalshUI_Click; - lstProfiles.PreviewKeyDown += LstProfiles_PreviewKeyDown; - lstProfiles.SelectionChanged += lstProfiles_SelectionChanged; - lstProfiles.LoadingRow += LstProfiles_LoadingRow; - if (_config.uiItem.enableDragDropSort) - { - lstProfiles.AllowDrop = true; - lstProfiles.PreviewMouseLeftButtonDown += LstProfiles_PreviewMouseLeftButtonDown; - lstProfiles.MouseMove += LstProfiles_MouseMove; - lstProfiles.DragEnter += LstProfiles_DragEnter; - lstProfiles.Drop += LstProfiles_Drop; - } - ViewModel = new MainWindowViewModel(MainSnackbar.MessageQueue!, UpdateViewHandler); + ViewModel = new MainWindowViewModel(MainSnackbar.MessageQueue, null); Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel)); for (int i = Global.MinFontSize; i <= Global.MinFontSize + 8; i++) @@ -74,15 +45,6 @@ namespace v2rayN.Views 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); - //servers this.BindCommand(ViewModel, vm => vm.AddVmessServerCmd, v => v.menuAddVmessServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddVlessServerCmd, v => v.menuAddVlessServer).DisposeWith(disposables); @@ -97,35 +59,6 @@ namespace v2rayN.Views this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).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.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).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.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables); - - //servers export - this.BindCommand(ViewModel, vm => vm.Export2ClientConfigCmd, v => v.menuExport2ClientConfig).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables); - //sub this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SubUpdateCmd, v => v.menuSubUpdate).DisposeWith(disposables); @@ -183,7 +116,6 @@ namespace v2rayN.Views this.OneWayBind(ViewModel, vm => vm.RunningServerToolTipText, v => v.tbNotify.ToolTipText).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.NotifyLeftClickCmd, v => v.tbNotify.LeftClickCommand).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.AppIcon, v => v.Icon).DisposeWith(disposables); - //this.OneWayBind(ViewModel, vm => vm.BlShowTrayTip, v => v.borTrayToolTip.Visibility).DisposeWith(disposables); //status bar this.OneWayBind(ViewModel, vm => vm.InboundDisplay, v => v.txtInboundDisplay.Text).DisposeWith(disposables); @@ -206,8 +138,7 @@ namespace v2rayN.Views this.Bind(ViewModel, vm => vm.SelectedSwatch, v => v.cmbSwatches.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CurrentFontSize, v => v.cmbCurrentFontSize.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CurrentLanguage, v => v.cmbCurrentLanguage.Text).DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.ShowCalshUI, v => v.btnShowCalshUI.Visibility).DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.ShowCalshUI, v => v.tabClashUI.Visibility).DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashUI.Visibility).DisposeWith(disposables); }); RestoreUI(); @@ -216,11 +147,6 @@ namespace v2rayN.Views var IsAdministrator = Utils.IsAdministrator(); this.Title = $"{Utils.GetVersion()} - {(IsAdministrator ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; - //if (_config.uiItem.autoHideStartup) - //{ - // WindowState = WindowState.Minimized; - //} - if (!_config.guiItem.enableHWA) { RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly; @@ -244,25 +170,22 @@ namespace v2rayN.Views return IntPtr.Zero; }); + if (tabProfiles.Content is null) + { + tabProfiles.Content = new ProfilesView(); + } + if (tabMsgView.Content is null) + { + tabMsgView.Content = new MsgView(); + } + if (tabClashUI.Content is null) + { + tabClashUI.Content = new ClashProxiesView(); + } } #region Event - private void UpdateViewHandler(EViewAction action) - { - if (action == EViewAction.AdjustMainLvColWidth) - { - Application.Current.Dispatcher.Invoke(() => - { - AutofitColumnWidth(); - }); - } - else if (action == EViewAction.ProfilesFocus) - { - lstProfiles.Focus(); - } - } - private void MainWindow_Closing(object? sender, CancelEventArgs e) { e.Cancel = true; @@ -271,6 +194,8 @@ namespace v2rayN.Views private void menuExit_Click(object sender, RoutedEventArgs e) { + tabProfiles = null; + tbNotify.Dispose(); StorageUI(); ViewModel?.MyAppExit(false); @@ -283,45 +208,6 @@ namespace v2rayN.Views ViewModel?.MyAppExit(true); } - private void lstProfiles_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) - { - ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast().ToList(); - } - - private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e) - { - e.Row.Header = $" {e.Row.GetIndex() + 1}"; - } - - private void LstProfiles_MouseDoubleClick(object sender, MouseButtonEventArgs e) - { - if (_config.uiItem.doubleClick2Activate) - { - ViewModel?.SetDefaultServer(); - } - else - { - ViewModel?.EditServer(false, EConfigType.Custom); - } - } - - 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 MainWindow_PreviewKeyDown(object sender, KeyEventArgs e) { if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) @@ -332,25 +218,9 @@ namespace v2rayN.Views ViewModel?.AddServerViaClipboard(); break; - case Key.O: - ViewModel?.ServerSpeedtest(ESpeedActionType.Tcping); - break; - - case Key.R: - ViewModel?.ServerSpeedtest(ESpeedActionType.Realping); - break; - case Key.S: ViewModel?.ScanScreenTaskAsync().ContinueWith(_ => { }); break; - - case Key.T: - ViewModel?.ServerSpeedtest(ESpeedActionType.Speedtest); - break; - - case Key.E: - ViewModel?.ServerSpeedtest(ESpeedActionType.Mixedtest); - break; } } else @@ -359,60 +229,6 @@ namespace v2rayN.Views { ViewModel?.Reload(); } - else if (e.Key == Key.Escape) - { - MessageBus.Current.SendMessage("true", Global.CommandStopSpeedTest); - } - } - } - - private void LstProfiles_PreviewKeyDown(object sender, KeyEventArgs e) - { - if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - { - if (e.Key == Key.A) - { - menuSelectAll_Click(null, null); - } - else if (e.Key == Key.C) - { - ViewModel?.Export2ShareUrl(); - } - else if (e.Key == Key.D) - { - ViewModel?.EditServer(false, EConfigType.Custom); - } - else if (e.Key == Key.F) - { - ViewModel?.ShareServer(); - } - } - else - { - if (e.Key is Key.Enter or Key.Return) - { - ViewModel?.SetDefaultServer(); - } - else if (e.Key == Key.Delete) - { - ViewModel?.RemoveServer(); - } - else if (e.Key == Key.T) - { - ViewModel?.MoveServer(EMove.Top); - } - else if (e.Key == Key.U) - { - ViewModel?.MoveServer(EMove.Up); - } - else if (e.Key == Key.D) - { - ViewModel?.MoveServer(EMove.Down); - } - else if (e.Key == Key.B) - { - ViewModel?.MoveServer(EMove.Bottom); - } } } @@ -437,48 +253,6 @@ namespace v2rayN.Views Utils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe")); } - private void BtnAutofitColumnWidth_Click(object sender, RoutedEventArgs e) - { - AutofitColumnWidth(); - } - - private void AutofitColumnWidth() - { - foreach (var it in lstProfiles.Columns) - { - it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto); - } - } - - private void TxtServerFilter_PreviewKeyDown(object sender, KeyEventArgs e) - { - if (e.Key is Key.Enter or Key.Return) - { - ViewModel?.RefreshServers(); - } - } - - private bool blShowClashUI = false; - - private void BtnShowCalshUI_Click(object sender, RoutedEventArgs e) - { - if (blShowClashUI) - { - tabClashUI.Visibility = Visibility.Hidden; - tabClashUI.Width = 0; - } - else - { - tabClashUI.Visibility = Visibility.Visible; - if (tabClashUI.Content is null) - { - tabClashUI.Content = new ClashProxiesView(); - } - tabClashUI.Width = this.ActualWidth * 5 / 6; - } - blShowClashUI = !blShowClashUI; - } - #endregion Event #region UI @@ -497,44 +271,8 @@ namespace v2rayN.Views if (Height > maxHeight) Height = maxHeight; if (_config.uiItem.mainGirdHeight1 > 0 && _config.uiItem.mainGirdHeight2 > 0) { - gridMain.RowDefinitions[0].Height = new GridLength(_config.uiItem.mainGirdHeight1, GridUnitType.Star); - gridMain.RowDefinitions[2].Height = new GridLength(_config.uiItem.mainGirdHeight2, GridUnitType.Star); - } - - var lvColumnItem = _config.uiItem.mainColumnItem.OrderBy(t => t.Index).ToList(); - var displayIndex = 0; - foreach (var item in lvColumnItem) - { - foreach (MyDGTextColumn item2 in lstProfiles.Columns) - { - if (item2.ExName == item.Name) - { - if (item.Width < 0) - { - item2.Visibility = Visibility.Hidden; - } - else - { - item2.Width = item.Width; - item2.DisplayIndex = displayIndex++; - } - } - } - } - - if (!_config.guiItem.enableStatistics) - { - colTodayUp.Visibility = - colTodayDown.Visibility = - colTotalUp.Visibility = - colTotalDown.Visibility = Visibility.Hidden; - } - else - { - colTodayUp.Visibility = - colTodayDown.Visibility = - colTotalUp.Visibility = - colTotalDown.Visibility = Visibility.Visible; + gridMain.ColumnDefinitions[0].Width = new GridLength(_config.uiItem.mainGirdHeight1, GridUnitType.Star); + gridMain.ColumnDefinitions[2].Width = new GridLength(_config.uiItem.mainGirdHeight2, GridUnitType.Star); } } @@ -543,21 +281,8 @@ namespace v2rayN.Views _config.uiItem.mainWidth = Utils.ToInt(this.Width); _config.uiItem.mainHeight = Utils.ToInt(this.Height); - List lvColumnItem = new(); - for (int k = 0; k < lstProfiles.Columns.Count; k++) - { - var item2 = (MyDGTextColumn)lstProfiles.Columns[k]; - lvColumnItem.Add(new() - { - Name = item2.ExName, - Width = item2.Visibility == Visibility.Visible ? Utils.ToInt(item2.ActualWidth) : -1, - Index = item2.DisplayIndex - }); - } - _config.uiItem.mainColumnItem = lvColumnItem; - - _config.uiItem.mainGirdHeight1 = Math.Ceiling(gridMain.RowDefinitions[0].ActualHeight + 0.1); - _config.uiItem.mainGirdHeight2 = Math.Ceiling(gridMain.RowDefinitions[2].ActualHeight + 0.1); + _config.uiItem.mainGirdHeight1 = Math.Ceiling(gridMain.ColumnDefinitions[0].ActualWidth + 0.1); + _config.uiItem.mainGirdHeight2 = Math.Ceiling(gridMain.ColumnDefinitions[2].ActualWidth + 0.1); } private void AddHelpMenuItem() @@ -588,97 +313,5 @@ namespace v2rayN.Views } #endregion UI - - #region Drag and Drop - - private Point startPoint = new(); - private int startIndex = -1; - private string formatData = "ProfileItemModel"; - - /// - /// Helper to search up the VisualTree - /// - /// - /// - /// - private static T? FindAncestor(DependencyObject current) where T : DependencyObject - { - do - { - if (current is T) - { - return (T)current; - } - current = VisualTreeHelper.GetParent(current); - } - while (current != null); - return null; - } - - private void LstProfiles_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) - { - // Get current mouse position - startPoint = e.GetPosition(null); - } - - private void LstProfiles_MouseMove(object sender, MouseEventArgs e) - { - // Get the current mouse position - Point mousePos = e.GetPosition(null); - Vector diff = startPoint - mousePos; - - if (e.LeftButton == MouseButtonState.Pressed && - (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance || - Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)) - { - // Get the dragged Item - if (sender is not DataGrid listView) return; - var listViewItem = FindAncestor((DependencyObject)e.OriginalSource); - if (listViewItem == null) return; // Abort - // Find the data behind the ListViewItem - ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem); - if (item == null) return; // Abort - // Initialize the drag & drop operation - startIndex = lstProfiles.SelectedIndex; - DataObject dragData = new(formatData, item); - DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Copy | DragDropEffects.Move); - } - } - - private void LstProfiles_DragEnter(object sender, DragEventArgs e) - { - if (!e.Data.GetDataPresent(formatData) || sender != e.Source) - { - e.Effects = DragDropEffects.None; - } - } - - private void LstProfiles_Drop(object sender, DragEventArgs e) - { - if (e.Data.GetDataPresent(formatData) && sender == e.Source) - { - // Get the drop Item destination - if (sender is not DataGrid listView) return; - var listViewItem = FindAncestor((DependencyObject)e.OriginalSource); - if (listViewItem == null) - { - // Abort - e.Effects = DragDropEffects.None; - return; - } - // Find the data behind the Item - ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem); - if (item == null) return; - // Move item into observable collection - // (this will be automatically reflected to lstView.ItemsSource) - e.Effects = DragDropEffects.Move; - - ViewModel?.MoveServerTo(startIndex, item); - - startIndex = -1; - } - } - - #endregion Drag and Drop } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml b/v2rayN/v2rayN/Views/ProfilesView.xaml new file mode 100644 index 00000000..a1c357af --- /dev/null +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs new file mode 100644 index 00000000..8316ec83 --- /dev/null +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -0,0 +1,399 @@ +using ReactiveUI; +using Splat; +using System.Reactive.Disposables; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using v2rayN.Base; +using v2rayN.Enums; +using v2rayN.Handler; +using v2rayN.Models; +using v2rayN.ViewModels; +using Point = System.Windows.Point; + +namespace v2rayN.Views +{ + public partial class ProfilesView + { + private static Config _config; + + public ProfilesView() + { + InitializeComponent(); + lstGroup.MaxHeight = Math.Floor(SystemParameters.WorkArea.Height * 0.20 / 40) * 40; + + _config = LazyConfig.Instance.GetConfig(); + + Application.Current.Exit += Current_Exit; + btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; + txtServerFilter.PreviewKeyDown += TxtServerFilter_PreviewKeyDown; + lstProfiles.PreviewKeyDown += LstProfiles_PreviewKeyDown; + lstProfiles.SelectionChanged += lstProfiles_SelectionChanged; + lstProfiles.LoadingRow += LstProfiles_LoadingRow; + if (_config.uiItem.enableDragDropSort) + { + lstProfiles.AllowDrop = true; + lstProfiles.PreviewMouseLeftButtonDown += LstProfiles_PreviewMouseLeftButtonDown; + lstProfiles.MouseMove += LstProfiles_MouseMove; + lstProfiles.DragEnter += LstProfiles_DragEnter; + lstProfiles.Drop += LstProfiles_Drop; + } + + ViewModel = new ProfilesViewModel(UpdateViewHandler); + Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ProfilesViewModel)); + + 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); + + //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.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).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.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables); + + //servers export + this.BindCommand(ViewModel, vm => vm.Export2ClientConfigCmd, v => v.menuExport2ClientConfig).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables); + }); + + RestoreUI(); + } + + #region Event + + private void Current_Exit(object sender, ExitEventArgs e) + { + StorageUI(); + } + + private void UpdateViewHandler(EViewAction action) + { + if (action == EViewAction.AdjustMainLvColWidth) + { + Application.Current.Dispatcher.Invoke(() => + { + AutofitColumnWidth(); + }); + } + else if (action == EViewAction.ProfilesFocus) + { + lstProfiles.Focus(); + } + } + + private void lstProfiles_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) + { + ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast().ToList(); + } + + private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e) + { + e.Row.Header = $" {e.Row.GetIndex() + 1}"; + } + + private void LstProfiles_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (_config.uiItem.doubleClick2Activate) + { + ViewModel?.SetDefaultServer(); + } + else + { + ViewModel?.EditServer(false, EConfigType.Custom); + } + } + + 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_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { + switch (e.Key) + { + case Key.A: + menuSelectAll_Click(null, null); + break; + + case Key.C: + ViewModel?.Export2ShareUrl(); + break; + + case Key.D: + ViewModel?.EditServer(false, EConfigType.Custom); + break; + + case Key.F: + ViewModel?.ShareServer(); + 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 + { + if (e.Key is Key.Enter or Key.Return) + { + ViewModel?.SetDefaultServer(); + } + else if (e.Key == Key.Delete) + { + ViewModel?.RemoveServer(); + } + else if (e.Key == Key.T) + { + ViewModel?.MoveServer(EMove.Top); + } + else if (e.Key == Key.U) + { + ViewModel?.MoveServer(EMove.Up); + } + else if (e.Key == Key.D) + { + ViewModel?.MoveServer(EMove.Down); + } + else if (e.Key == Key.B) + { + ViewModel?.MoveServer(EMove.Bottom); + } + else if (e.Key == Key.Escape) + { + MessageBus.Current.SendMessage("true", Global.CommandStopSpeedTest); + } + } + } + + private void BtnAutofitColumnWidth_Click(object sender, RoutedEventArgs e) + { + AutofitColumnWidth(); + } + + private void AutofitColumnWidth() + { + foreach (var it in lstProfiles.Columns) + { + it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto); + } + } + + private void TxtServerFilter_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is Key.Enter or Key.Return) + { + ViewModel?.RefreshServers(); + } + } + + #endregion Event + + #region UI + + private void RestoreUI() + { + var lvColumnItem = _config.uiItem.mainColumnItem.OrderBy(t => t.Index).ToList(); + var displayIndex = 0; + foreach (var item in lvColumnItem) + { + foreach (MyDGTextColumn item2 in lstProfiles.Columns) + { + if (item2.ExName == item.Name) + { + if (item.Width < 0) + { + item2.Visibility = Visibility.Hidden; + } + else + { + item2.Width = item.Width; + item2.DisplayIndex = displayIndex++; + } + } + } + } + + if (!_config.guiItem.enableStatistics) + { + colTodayUp.Visibility = + colTodayDown.Visibility = + colTotalUp.Visibility = + colTotalDown.Visibility = Visibility.Hidden; + } + else + { + colTodayUp.Visibility = + colTodayDown.Visibility = + colTotalUp.Visibility = + colTotalDown.Visibility = Visibility.Visible; + } + } + + private void StorageUI() + { + List lvColumnItem = new(); + for (int k = 0; k < lstProfiles.Columns.Count; k++) + { + var item2 = (MyDGTextColumn)lstProfiles.Columns[k]; + lvColumnItem.Add(new() + { + Name = item2.ExName, + Width = item2.Visibility == Visibility.Visible ? Utils.ToInt(item2.ActualWidth) : -1, + Index = item2.DisplayIndex + }); + } + _config.uiItem.mainColumnItem = lvColumnItem; + ConfigHandler.SaveConfig(_config); + } + + #endregion UI + + #region Drag and Drop + + private Point startPoint = new(); + private int startIndex = -1; + private string formatData = "ProfileItemModel"; + + /// + /// Helper to search up the VisualTree + /// + /// + /// + /// + private static T? FindAncestor(DependencyObject current) where T : DependencyObject + { + do + { + if (current is T) + { + return (T)current; + } + current = VisualTreeHelper.GetParent(current); + } + while (current != null); + return null; + } + + private void LstProfiles_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + // Get current mouse position + startPoint = e.GetPosition(null); + } + + private void LstProfiles_MouseMove(object sender, MouseEventArgs e) + { + // Get the current mouse position + Point mousePos = e.GetPosition(null); + Vector diff = startPoint - mousePos; + + if (e.LeftButton == MouseButtonState.Pressed && + (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance || + Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)) + { + // Get the dragged Item + if (sender is not DataGrid listView) return; + var listViewItem = FindAncestor((DependencyObject)e.OriginalSource); + if (listViewItem == null) return; // Abort + // Find the data behind the ListViewItem + ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem); + if (item == null) return; // Abort + // Initialize the drag & drop operation + startIndex = lstProfiles.SelectedIndex; + DataObject dragData = new(formatData, item); + DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Copy | DragDropEffects.Move); + } + } + + private void LstProfiles_DragEnter(object sender, DragEventArgs e) + { + if (!e.Data.GetDataPresent(formatData) || sender != e.Source) + { + e.Effects = DragDropEffects.None; + } + } + + private void LstProfiles_Drop(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(formatData) && sender == e.Source) + { + // Get the drop Item destination + if (sender is not DataGrid listView) return; + var listViewItem = FindAncestor((DependencyObject)e.OriginalSource); + if (listViewItem == null) + { + // Abort + e.Effects = DragDropEffects.None; + return; + } + // Find the data behind the Item + ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem); + if (item == null) return; + // Move item into observable collection + // (this will be automatically reflected to lstView.ItemsSource) + e.Effects = DragDropEffects.Move; + + ViewModel?.MoveServerTo(startIndex, item); + + startIndex = -1; + } + } + + #endregion Drag and Drop + } +} \ No newline at end of file