using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; using System.Reactive; using System.Windows; using System.Windows.Forms; using v2rayN.Base; using v2rayN.Handler; using v2rayN.Mode; using v2rayN.Resx; using v2rayN.Views; using Application = System.Windows.Application; namespace v2rayN.ViewModels { public class RoutingRuleSettingViewModel : ReactiveObject { private static Config _config; private NoticeHandler? _noticeHandler; private Window _view; private List _rules; [Reactive] public RoutingItem SelectedRouting { get; set; } private IObservableCollection _rulesItems = new ObservableCollectionExtended(); public IObservableCollection RulesItems => _rulesItems; [Reactive] public RulesItemModel SelectedSource { get; set; } public IList SelectedSources { get; set; } public ReactiveCommand RuleAddCmd { get; } public ReactiveCommand ImportRulesFromFileCmd { get; } public ReactiveCommand ImportRulesFromClipboardCmd { get; } public ReactiveCommand ImportRulesFromUrlCmd { get; } public ReactiveCommand RuleRemoveCmd { get; } public ReactiveCommand RuleExportSelectedCmd { get; } public ReactiveCommand MoveTopCmd { get; } public ReactiveCommand MoveUpCmd { get; } public ReactiveCommand MoveDownCmd { get; } public ReactiveCommand MoveBottomCmd { get; } public ReactiveCommand SaveCmd { get; } public RoutingRuleSettingViewModel(RoutingItem routingItem, Window view) { _config = LazyConfig.Instance.GetConfig(); _noticeHandler = Locator.Current.GetService(); _view = view; SelectedSource = new(); if (routingItem.id.IsNullOrEmpty()) { SelectedRouting = routingItem; _rules = new(); } else { SelectedRouting = routingItem; _rules = Utils.FromJson>(SelectedRouting.ruleSet); } RefreshRulesItems(); var canEditRemove = this.WhenAnyValue( x => x.SelectedSource, selectedSource => selectedSource != null && !selectedSource.outboundTag.IsNullOrEmpty()); RuleAddCmd = ReactiveCommand.Create(() => { RuleEdit(true); }); ImportRulesFromFileCmd = ReactiveCommand.Create(() => { ImportRulesFromFile(); }); ImportRulesFromClipboardCmd = ReactiveCommand.Create(() => { ImportRulesFromClipboard(); }); ImportRulesFromUrlCmd = ReactiveCommand.Create(() => { ImportRulesFromUrl(); }); RuleRemoveCmd = ReactiveCommand.Create(() => { RuleRemove(); }, canEditRemove); RuleExportSelectedCmd = ReactiveCommand.Create(() => { RuleExportSelected(); }, canEditRemove); MoveTopCmd = ReactiveCommand.Create(() => { MoveRule(EMove.Top); }, canEditRemove); MoveUpCmd = ReactiveCommand.Create(() => { MoveRule(EMove.Up); }, canEditRemove); MoveDownCmd = ReactiveCommand.Create(() => { MoveRule(EMove.Down); }, canEditRemove); MoveBottomCmd = ReactiveCommand.Create(() => { MoveRule(EMove.Bottom); }, canEditRemove); SaveCmd = ReactiveCommand.Create(() => { SaveRouting(); }); Utils.SetDarkBorder(view, _config.uiItem.colorModeDark); } public void RefreshRulesItems() { _rulesItems.Clear(); foreach (var item in _rules) { var it = new RulesItemModel() { id = item.id, outboundTag = item.outboundTag, port = item.port, protocols = Utils.List2String(item.protocol), inboundTags = Utils.List2String(item.inboundTag), domains = Utils.List2String(item.domain), ips = Utils.List2String(item.ip), enabled = item.enabled, }; _rulesItems.Add(it); } } public void RuleEdit(bool blNew) { RulesItem item; if (blNew) { item = new(); } else { item = _rules.FirstOrDefault(t => t.id == SelectedSource?.id); if (item is null) { return; } } var ret = (new RoutingRuleDetailsWindow(item)).ShowDialog(); if (ret == true) { if (blNew) { _rules.Add(item); } RefreshRulesItems(); } } private void RuleRemove() { if (SelectedSource is null || SelectedSource.outboundTag.IsNullOrEmpty()) { UI.Show(ResUI.PleaseSelectRules); return; } if (UI.ShowYesNo(ResUI.RemoveRules) == DialogResult.No) { return; } foreach (var it in SelectedSources) { var item = _rules.FirstOrDefault(t => t.id == it?.id); if (item != null) { _rules.Remove(item); } } RefreshRulesItems(); } public void RuleExportSelected() { if (SelectedSource is null || SelectedSource.outboundTag.IsNullOrEmpty()) { UI.Show(ResUI.PleaseSelectRules); return; } var lst = new List(); foreach (var it in SelectedSources) { var item = _rules.FirstOrDefault(t => t.id == it?.id); if (item != null) { lst.Add(item); } } if (lst.Count > 0) { Utils.SetClipboardData(Utils.ToJson(lst)); //UI.Show(ResUI.OperationSuccess")); } } public void MoveRule(EMove eMove) { if (SelectedSource is null || SelectedSource.outboundTag.IsNullOrEmpty()) { UI.Show(ResUI.PleaseSelectRules); return; } var item = _rules.FirstOrDefault(t => t.id == SelectedSource?.id); if (item == null) { return; } var index = _rules.IndexOf(item); if (ConfigHandler.MoveRoutingRule(_rules, index, eMove) == 0) { RefreshRulesItems(); } } private void SaveRouting() { string remarks = SelectedRouting.remarks; if (Utils.IsNullOrEmpty(remarks)) { UI.Show(ResUI.PleaseFillRemarks); return; } var item = SelectedRouting; foreach (var it in _rules) { it.id = Utils.GetGUID(false); } item.ruleNum = _rules.Count; item.ruleSet = Utils.ToJson(_rules, false); if (ConfigHandler.SaveRoutingItem(ref _config, item) == 0) { _noticeHandler?.Enqueue(ResUI.OperationSuccess); _view.DialogResult = true; } else { UI.ShowWarning(ResUI.OperationFailed); } } #region Import rules private void ImportRulesFromFile() { OpenFileDialog fileDialog = new OpenFileDialog { Multiselect = false, Filter = "Rules|*.json|All|*.*" }; if (fileDialog.ShowDialog() != DialogResult.OK) { return; } string fileName = fileDialog.FileName; if (Utils.IsNullOrEmpty(fileName)) { return; } string result = Utils.LoadResource(fileName); if (Utils.IsNullOrEmpty(result)) { return; } if (AddBatchRoutingRules(SelectedRouting, result) == 0) { RefreshRulesItems(); UI.Show(ResUI.OperationSuccess); } } private void ImportRulesFromClipboard() { string clipboardData = Utils.GetClipboardData(); if (AddBatchRoutingRules(SelectedRouting, clipboardData) == 0) { RefreshRulesItems(); UI.Show(ResUI.OperationSuccess); } } private void ImportRulesFromUrl() { var url = SelectedRouting.url; if (Utils.IsNullOrEmpty(url)) { UI.Show(ResUI.MsgNeedUrl); return; } Task.Run(async () => { DownloadHandle downloadHandle = new DownloadHandle(); string result = await downloadHandle.DownloadStringAsync(url, false, ""); if (AddBatchRoutingRules(SelectedRouting, result) == 0) { Application.Current.Dispatcher.Invoke((Action)(() => { RefreshRulesItems(); })); UI.Show(ResUI.OperationSuccess); } }); } private int AddBatchRoutingRules(RoutingItem routingItem, string clipboardData) { bool blReplace = false; if (UI.ShowYesNo(ResUI.AddBatchRoutingRulesYesNo) == DialogResult.No) { blReplace = true; } if (Utils.IsNullOrEmpty(clipboardData)) { return -1; } var lstRules = Utils.FromJson>(clipboardData); if (lstRules == null) { return -1; } foreach (var rule in lstRules) { rule.id = Utils.GetGUID(false); } if (blReplace) { _rules = lstRules; } else { _rules.AddRange(lstRules); } return 0; } #endregion } }