mirror of https://github.com/2dust/v2rayN
Profiles Select Window
parent
6391667c15
commit
5db990a2f8
|
@ -0,0 +1,298 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using Splat;
|
||||||
|
|
||||||
|
namespace ServiceLib.ViewModels;
|
||||||
|
public class ProfilesSelectViewModel : MyReactiveObject
|
||||||
|
{
|
||||||
|
#region private prop
|
||||||
|
|
||||||
|
private List<ProfileItem> _lstProfile;
|
||||||
|
private string _serverFilter = string.Empty;
|
||||||
|
private Dictionary<string, bool> _dicHeaderSort = new();
|
||||||
|
private string _subIndexId = string.Empty;
|
||||||
|
|
||||||
|
#endregion private prop
|
||||||
|
|
||||||
|
#region ObservableCollection
|
||||||
|
|
||||||
|
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||||
|
|
||||||
|
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public ProfileItemModel SelectedProfile { get; set; }
|
||||||
|
|
||||||
|
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public SubItem SelectedSub { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string ServerFilter { get; set; }
|
||||||
|
|
||||||
|
#endregion ObservableCollection
|
||||||
|
|
||||||
|
#region Init
|
||||||
|
|
||||||
|
public ProfilesSelectViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
|
{
|
||||||
|
_config = AppManager.Instance.Config;
|
||||||
|
_updateView = updateView;
|
||||||
|
_subIndexId = _config.SubIndexId ?? string.Empty;
|
||||||
|
#region WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
this.WhenAnyValue(
|
||||||
|
x => x.SelectedSub,
|
||||||
|
y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id)
|
||||||
|
.Subscribe(async c => await SubSelectedChangedAsync(c));
|
||||||
|
|
||||||
|
this.WhenAnyValue(
|
||||||
|
x => x.ServerFilter,
|
||||||
|
y => y != null && _serverFilter != y)
|
||||||
|
.Subscribe(async c => await ServerFilterChanged(c));
|
||||||
|
|
||||||
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
#region AppEvents
|
||||||
|
|
||||||
|
AppEvents.ProfilesRefreshRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
AppEvents.DispatcherStatisticsRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(UpdateStatistics);
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
|
_ = Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Init()
|
||||||
|
{
|
||||||
|
SelectedProfile = new();
|
||||||
|
SelectedSub = new();
|
||||||
|
|
||||||
|
await RefreshSubscriptions();
|
||||||
|
await RefreshServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Init
|
||||||
|
|
||||||
|
#region Actions
|
||||||
|
|
||||||
|
public void UpdateStatistics(ServerSpeedItem update)
|
||||||
|
{
|
||||||
|
if (!_config.GuiItem.EnableStatistics
|
||||||
|
|| (update.ProxyUp + update.ProxyDown) <= 0
|
||||||
|
|| DateTime.Now.Second % 3 != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var item = ProfileItems.FirstOrDefault(it => it.IndexId == update.IndexId);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanOk()
|
||||||
|
{
|
||||||
|
return SelectedProfile != null && !SelectedProfile.IndexId.IsNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SelectFinish()
|
||||||
|
{
|
||||||
|
if (!CanOk())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion Actions
|
||||||
|
|
||||||
|
#region Servers && Groups
|
||||||
|
|
||||||
|
private async Task SubSelectedChangedAsync(bool c)
|
||||||
|
{
|
||||||
|
if (!c)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_subIndexId = SelectedSub?.Id;
|
||||||
|
|
||||||
|
await RefreshServers();
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ServerFilterChanged(bool c)
|
||||||
|
{
|
||||||
|
if (!c)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_serverFilter = ServerFilter;
|
||||||
|
if (_serverFilter.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
await RefreshServers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshServers()
|
||||||
|
{
|
||||||
|
await RefreshServersBiz();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshServersBiz()
|
||||||
|
{
|
||||||
|
var lstModel = await GetProfileItemsEx(_subIndexId, _serverFilter);
|
||||||
|
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
||||||
|
|
||||||
|
ProfileItems.Clear();
|
||||||
|
ProfileItems.AddRange(lstModel);
|
||||||
|
if (lstModel.Count > 0)
|
||||||
|
{
|
||||||
|
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||||
|
if (selected != null)
|
||||||
|
{
|
||||||
|
SelectedProfile = selected;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedProfile = lstModel.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshSubscriptions()
|
||||||
|
{
|
||||||
|
SubItems.Clear();
|
||||||
|
|
||||||
|
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||||
|
|
||||||
|
foreach (var item in await AppManager.Instance.SubItems())
|
||||||
|
{
|
||||||
|
SubItems.Add(item);
|
||||||
|
}
|
||||||
|
if (_subIndexId != null && SubItems.FirstOrDefault(t => t.Id == _subIndexId) != null)
|
||||||
|
{
|
||||||
|
SelectedSub = SubItems.FirstOrDefault(t => t.Id == _subIndexId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedSub = SubItems.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||||
|
{
|
||||||
|
var lstModel = await AppManager.Instance.ProfileItems(_subIndexId, filter);
|
||||||
|
|
||||||
|
//await ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||||
|
|
||||||
|
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
|
||||||
|
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||||
|
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?.Sort ?? 0,
|
||||||
|
Delay = t33?.Delay ?? 0,
|
||||||
|
Speed = t33?.Speed ?? 0,
|
||||||
|
DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty,
|
||||||
|
SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? 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();
|
||||||
|
|
||||||
|
return lstModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProfileItem?> GetProfileItem()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SelectedProfile?.IndexId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var indexId = SelectedProfile.IndexId;
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SortServer(string colName)
|
||||||
|
{
|
||||||
|
//if (colName.IsNullOrEmpty())
|
||||||
|
//{
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//_dicHeaderSort.TryAdd(colName, true);
|
||||||
|
//_dicHeaderSort.TryGetValue(colName, out bool asc);
|
||||||
|
//if (await ConfigHandler.SortServers(_config, _config.SubIndexId, colName, asc) != 0)
|
||||||
|
//{
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
//_dicHeaderSort[colName] = !asc;
|
||||||
|
//await RefreshServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Servers && Groups
|
||||||
|
}
|
|
@ -38,15 +38,9 @@ public class ProfilesViewModel : MyReactiveObject
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedMoveToGroup { get; set; }
|
public SubItem SelectedMoveToGroup { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public ComboItem SelectedServer { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ServerFilter { get; set; }
|
public string ServerFilter { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public bool BlServers { get; set; }
|
|
||||||
|
|
||||||
#endregion ObservableCollection
|
#endregion ObservableCollection
|
||||||
|
|
||||||
#region Menu
|
#region Menu
|
||||||
|
@ -115,11 +109,6 @@ public class ProfilesViewModel : MyReactiveObject
|
||||||
y => y != null && !y.Remarks.IsNullOrEmpty())
|
y => y != null && !y.Remarks.IsNullOrEmpty())
|
||||||
.Subscribe(async c => await MoveToGroup(c));
|
.Subscribe(async c => await MoveToGroup(c));
|
||||||
|
|
||||||
this.WhenAnyValue(
|
|
||||||
x => x.SelectedServer,
|
|
||||||
y => y != null && !y.Text.IsNullOrEmpty())
|
|
||||||
.Subscribe(async c => await ServerSelectedChanged(c));
|
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.ServerFilter,
|
x => x.ServerFilter,
|
||||||
y => y != null && _serverFilter != y)
|
y => y != null && _serverFilter != y)
|
||||||
|
@ -266,7 +255,6 @@ public class ProfilesViewModel : MyReactiveObject
|
||||||
SelectedProfile = new();
|
SelectedProfile = new();
|
||||||
SelectedSub = new();
|
SelectedSub = new();
|
||||||
SelectedMoveToGroup = new();
|
SelectedMoveToGroup = new();
|
||||||
SelectedServer = new();
|
|
||||||
|
|
||||||
await RefreshSubscriptions();
|
await RefreshSubscriptions();
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
@ -613,19 +601,6 @@ public class ProfilesViewModel : MyReactiveObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerSelectedChanged(bool c)
|
|
||||||
{
|
|
||||||
if (!c)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SelectedServer == null || SelectedServer.ID.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await SetDefaultServer(SelectedServer.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ShareServerAsync()
|
public async Task ShareServerAsync()
|
||||||
{
|
{
|
||||||
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
<base:WindowBase
|
||||||
|
x:Class="v2rayN.Views.ProfilesSelectWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:base="clr-namespace:v2rayN.Base"
|
||||||
|
xmlns:conv="clr-namespace:v2rayN.Converters"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:reactiveui="http://reactiveui.net"
|
||||||
|
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||||
|
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||||
|
Title="ProfilesSelectWindow"
|
||||||
|
Width="800"
|
||||||
|
Height="450"
|
||||||
|
x:TypeArguments="vms:ProfilesSelectViewModel"
|
||||||
|
Style="{StaticResource WindowGlobal}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
|
||||||
|
<conv:DelayColorConverter x:Key="DelayColorConverter" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<DockPanel Margin="{StaticResource Margin8}">
|
||||||
|
<StackPanel
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
DockPanel.Dock="Bottom"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnSave"
|
||||||
|
Width="100"
|
||||||
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
|
IsDefault="True"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnCancel"
|
||||||
|
Width="100"
|
||||||
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
|
IsCancel="true"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
</StackPanel>
|
||||||
|
<Grid>
|
||||||
|
<DockPanel>
|
||||||
|
<WrapPanel Margin="{StaticResource Margin4}" DockPanel.Dock="Top">
|
||||||
|
<ListBox
|
||||||
|
x:Name="lstGroup"
|
||||||
|
MaxHeight="200"
|
||||||
|
AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}"
|
||||||
|
FontSize="{DynamicResource StdFontSize}"
|
||||||
|
ItemContainerStyle="{StaticResource MyChipListBoxItem}"
|
||||||
|
Style="{StaticResource MaterialDesignChoiceChipPrimaryOutlineListBox}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Remarks}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="btnAutofitColumnWidth"
|
||||||
|
Width="30"
|
||||||
|
Height="30"
|
||||||
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
|
AutomationProperties.Name="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"
|
||||||
|
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
|
||||||
|
ToolTip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
|
||||||
|
<materialDesign:PackIcon VerticalAlignment="Center" Kind="ArrowSplitVertical" />
|
||||||
|
</Button>
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtServerFilter"
|
||||||
|
Width="200"
|
||||||
|
Margin="{StaticResource MarginLeftRight4}"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
|
||||||
|
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||||
|
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
|
||||||
|
Style="{StaticResource DefTextBox}" />
|
||||||
|
</WrapPanel>
|
||||||
|
<DataGrid
|
||||||
|
x:Name="lstProfiles"
|
||||||
|
materialDesign:DataGridAssist.CellPadding="2,2"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
BorderThickness="1"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
CanUserResizeRows="False"
|
||||||
|
CanUserSortColumns="False"
|
||||||
|
EnableRowVirtualization="True"
|
||||||
|
Focusable="True"
|
||||||
|
GridLinesVisibility="All"
|
||||||
|
HeadersVisibility="All"
|
||||||
|
IsReadOnly="True"
|
||||||
|
RowHeaderWidth="40"
|
||||||
|
Style="{StaticResource DefDataGrid}">
|
||||||
|
<DataGrid.InputBindings>
|
||||||
|
<KeyBinding Command="ApplicationCommands.NotACommand" Gesture="Enter" />
|
||||||
|
</DataGrid.InputBindings>
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<Style BasedOn="{StaticResource MaterialDesignDataGridRow}" TargetType="DataGridRow">
|
||||||
|
<EventSetter Event="MouseDoubleClick" Handler="LstProfiles_MouseDoubleClick" />
|
||||||
|
</Style>
|
||||||
|
<Style BasedOn="{StaticResource MaterialDesignDataGridColumnHeader}" TargetType="DataGridColumnHeader">
|
||||||
|
<EventSetter Event="Click" Handler="LstProfiles_ColumnHeader_Click" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style BasedOn="{StaticResource MaterialDesignDataGridCell}" TargetType="DataGridCell">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||||
|
<Setter Property="Foreground" Value="Black" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="80"
|
||||||
|
Binding="{Binding ConfigType}"
|
||||||
|
ExName="ConfigType"
|
||||||
|
Header="{x:Static resx:ResUI.LvServiceType}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="150"
|
||||||
|
Binding="{Binding Remarks}"
|
||||||
|
ExName="Remarks"
|
||||||
|
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="120"
|
||||||
|
Binding="{Binding Address}"
|
||||||
|
ExName="Address"
|
||||||
|
Header="{x:Static resx:ResUI.LvAddress}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="60"
|
||||||
|
Binding="{Binding Port}"
|
||||||
|
ExName="Port"
|
||||||
|
Header="{x:Static resx:ResUI.LvPort}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding Network}"
|
||||||
|
ExName="Network"
|
||||||
|
Header="{x:Static resx:ResUI.LvTransportProtocol}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding StreamSecurity}"
|
||||||
|
ExName="StreamSecurity"
|
||||||
|
Header="{x:Static resx:ResUI.LvTLS}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding SubRemarks}"
|
||||||
|
ExName="SubRemarks"
|
||||||
|
Header="{x:Static resx:ResUI.LvSubscription}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding DelayVal}"
|
||||||
|
ExName="DelayVal"
|
||||||
|
Header="{x:Static resx:ResUI.LvTestDelay}"
|
||||||
|
SortMemberPath="Delay">
|
||||||
|
<DataGridTextColumn.ElementStyle>
|
||||||
|
<Style TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
<Setter Property="Foreground" Value="{Binding Delay, Converter={StaticResource DelayColorConverter}}" />
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.ElementStyle>
|
||||||
|
</base:MyDGTextColumn>
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding SpeedVal}"
|
||||||
|
ExName="SpeedVal"
|
||||||
|
Header="{x:Static resx:ResUI.LvTestSpeed}">
|
||||||
|
<DataGridTextColumn.ElementStyle>
|
||||||
|
<Style TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.ElementStyle>
|
||||||
|
</base:MyDGTextColumn>
|
||||||
|
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
x:Name="colTodayUp"
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding TodayUp}"
|
||||||
|
ExName="TodayUp"
|
||||||
|
Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
x:Name="colTodayDown"
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding TodayDown}"
|
||||||
|
ExName="TodayDown"
|
||||||
|
Header="{x:Static resx:ResUI.LvTodayDownloadDataAmount}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
x:Name="colTotalUp"
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding TotalUp}"
|
||||||
|
ExName="TotalUp"
|
||||||
|
Header="{x:Static resx:ResUI.LvTotalUploadDataAmount}" />
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
x:Name="colTotalDown"
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding TotalDown}"
|
||||||
|
ExName="TotalDown"
|
||||||
|
Header="{x:Static resx:ResUI.LvTotalDownloadDataAmount}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</base:WindowBase>
|
|
@ -0,0 +1,150 @@
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ServiceLib.Manager;
|
||||||
|
using Splat;
|
||||||
|
using v2rayN.Base;
|
||||||
|
|
||||||
|
namespace v2rayN.Views;
|
||||||
|
|
||||||
|
public partial class ProfilesSelectWindow
|
||||||
|
{
|
||||||
|
private static Config _config;
|
||||||
|
|
||||||
|
public Task<ProfileItem?> ProfileItem => GetFirstProfileItemAsync();
|
||||||
|
|
||||||
|
public ProfilesSelectWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
lstGroup.MaxHeight = Math.Floor(SystemParameters.WorkArea.Height * 0.20 / 40) * 40;
|
||||||
|
|
||||||
|
_config = AppManager.Instance.Config;
|
||||||
|
|
||||||
|
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
||||||
|
txtServerFilter.PreviewKeyDown += TxtServerFilter_PreviewKeyDown;
|
||||||
|
lstProfiles.PreviewKeyDown += LstProfiles_PreviewKeyDown;
|
||||||
|
lstProfiles.SelectionChanged += LstProfiles_SelectionChanged;
|
||||||
|
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
|
||||||
|
|
||||||
|
ViewModel = new ProfilesSelectViewModel(UpdateViewHandler);
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Event
|
||||||
|
|
||||||
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case EViewAction.CloseWindow:
|
||||||
|
this.DialogResult = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel != null)
|
||||||
|
{
|
||||||
|
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e)
|
||||||
|
{
|
||||||
|
e.Row.Header = $" {e.Row.GetIndex() + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnAutofitColumnWidth_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AutofitColumnWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutofitColumnWidth()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var it in lstProfiles.Columns)
|
||||||
|
{
|
||||||
|
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog("ProfilesView", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TxtServerFilter_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.RefreshServers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProfileItem?> GetFirstProfileItemAsync()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItem();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
#endregion Event
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ public partial class ProfilesView
|
||||||
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
||||||
txtServerFilter.PreviewKeyDown += TxtServerFilter_PreviewKeyDown;
|
txtServerFilter.PreviewKeyDown += TxtServerFilter_PreviewKeyDown;
|
||||||
lstProfiles.PreviewKeyDown += LstProfiles_PreviewKeyDown;
|
lstProfiles.PreviewKeyDown += LstProfiles_PreviewKeyDown;
|
||||||
lstProfiles.SelectionChanged += lstProfiles_SelectionChanged;
|
lstProfiles.SelectionChanged += LstProfiles_SelectionChanged;
|
||||||
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
|
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
|
||||||
menuSelectAll.Click += menuSelectAll_Click;
|
menuSelectAll.Click += menuSelectAll_Click;
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ public partial class ProfilesView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lstProfiles_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
private void LstProfiles_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (ViewModel != null)
|
if (ViewModel != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -259,6 +259,14 @@
|
||||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||||
AcceptsReturn="True"
|
AcceptsReturn="True"
|
||||||
Style="{StaticResource MyOutlinedTextBox}" />
|
Style="{StaticResource MyOutlinedTextBox}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Click="BtnSelectPrevProfile_Click"
|
||||||
|
Content="btnSelectPrevProfile"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="10"
|
Grid.Row="10"
|
||||||
|
@ -276,6 +284,14 @@
|
||||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||||
AcceptsReturn="True"
|
AcceptsReturn="True"
|
||||||
Style="{StaticResource MyOutlinedTextBox}" />
|
Style="{StaticResource MyOutlinedTextBox}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Click="BtnSelectNextProfile_Click"
|
||||||
|
Content="btnNextPrevProfile"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="11"
|
Grid.Row="11"
|
||||||
|
|
|
@ -54,4 +54,30 @@ public partial class SubEditWindow
|
||||||
{
|
{
|
||||||
txtRemarks.Focus();
|
txtRemarks.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
if (selectWindow.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
txtPrevProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
if (selectWindow.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
txtNextProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue