Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ffb3bd30c | ||
|
|
2826444ffc | ||
|
|
56c3e9c46d | ||
|
|
0770e30034 | ||
|
|
04195c2957 | ||
|
|
d18d74ac1c | ||
|
|
6391667c15 | ||
|
|
7f26445327 | ||
|
|
291d4bd8e5 | ||
|
|
f2f3a7eb5f | ||
|
|
e7609619d4 | ||
|
|
84bf9ecfaf | ||
|
|
a2917b3ce8 | ||
|
|
d094370209 | ||
|
|
1a6fbf782d | ||
|
|
3f67a23f8b | ||
|
|
b8eb7e7b29 | ||
|
|
1d69916410 | ||
|
|
49fa103077 | ||
|
|
e3a63db966 | ||
|
|
ef4a1903ec | ||
|
|
5a3286dad1 | ||
|
|
058c6e4a85 | ||
|
|
ea1d438e40 | ||
|
|
a108eaf34b | ||
|
|
da28c639b3 | ||
|
|
8ef68127d4 | ||
|
|
f39d966a33 | ||
|
|
f83e83de13 | ||
|
|
abdafc9b3b | ||
|
|
8f93c50151 | ||
|
|
fe7c505cc9 | ||
|
|
0d5afa4ff5 | ||
|
|
2ad716a4ad | ||
|
|
cddf88730f | ||
|
|
3eb49aa24c |
2
.github/workflows/build-linux.yml
vendored
2
.github/workflows/build-linux.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/build-osx.yml
vendored
2
.github/workflows/build-osx.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/build-windows-desktop.yml
vendored
2
.github/workflows/build-windows-desktop.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v5.0.0
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,67 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Install deps
|
||||||
sudo apt update -y
|
sudo apt update -y
|
||||||
sudo apt install -y libfuse2
|
sudo apt install -y libfuse2 wget file
|
||||||
wget -O pkg2appimage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage
|
|
||||||
chmod a+x pkg2appimage
|
# Get tools
|
||||||
export AppImageOutputArch=$OutputArch
|
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
export OutputPath=$OutputPath64
|
chmod +x appimagetool
|
||||||
./pkg2appimage ./pkg2appimage.yml
|
|
||||||
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
|
# x86_64 AppDir
|
||||||
export AppImageOutputArch=$OutputArchArm
|
APPDIR_X64="AppDir-x86_64"
|
||||||
export OutputPath=$OutputPathArm64
|
rm -rf "$APPDIR_X64"
|
||||||
./pkg2appimage ./pkg2appimage.yml
|
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
|
||||||
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
|
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
|
||||||
|
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
|
||||||
|
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
|
||||||
|
chmod +x "$APPDIR_X64/AppRun"
|
||||||
|
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
|
||||||
|
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=v2rayN
|
||||||
|
Icon=v2rayN
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
|
||||||
|
|
||||||
|
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
|
||||||
|
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
|
||||||
|
|
||||||
|
# aarch64 AppDir
|
||||||
|
APPDIR_ARM64="AppDir-aarch64"
|
||||||
|
rm -rf "$APPDIR_ARM64"
|
||||||
|
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
|
||||||
|
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
|
||||||
|
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
|
||||||
|
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
|
||||||
|
chmod +x "$APPDIR_ARM64/AppRun"
|
||||||
|
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
|
||||||
|
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=v2rayN
|
||||||
|
Icon=v2rayN
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
|
||||||
|
|
||||||
|
# aarch64 runtime
|
||||||
|
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
|
||||||
|
chmod +x runtime-aarch64
|
||||||
|
|
||||||
|
# build aarch64 AppImage
|
||||||
|
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
|
||||||
|
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Package: v2rayN
|
|||||||
Version: $Version
|
Version: $Version
|
||||||
Architecture: $Arch2
|
Architecture: $Arch2
|
||||||
Maintainer: https://github.com/2dust/v2rayN
|
Maintainer: https://github.com/2dust/v2rayN
|
||||||
|
Depends: desktop-file-utils
|
||||||
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -52,7 +53,17 @@ sudo chmod 0755 "${PackagePath}/DEBIAN/postinst"
|
|||||||
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
|
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
|
||||||
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool"
|
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool"
|
||||||
|
|
||||||
# desktop && PATH
|
# Patch
|
||||||
|
# set owner to root:root
|
||||||
|
sudo chown -R root:root "${PackagePath}"
|
||||||
|
# set all directories to 755 (readable & traversable by all users)
|
||||||
|
sudo find "${PackagePath}/opt/v2rayN" -type d -exec chmod 755 {} +
|
||||||
|
# set all regular files to 644 (readable by all users)
|
||||||
|
sudo find "${PackagePath}/opt/v2rayN" -type f -exec chmod 644 {} +
|
||||||
|
# ensure main binaries are 755 (executable by all users)
|
||||||
|
sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true
|
||||||
|
sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true
|
||||||
|
|
||||||
|
# build deb package
|
||||||
sudo dpkg-deb -Zxz --build $PackagePath
|
sudo dpkg-deb -Zxz --build $PackagePath
|
||||||
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"
|
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
app: v2rayN
|
|
||||||
binpatch: true
|
|
||||||
|
|
||||||
ingredients:
|
|
||||||
script:
|
|
||||||
- export FileName="v2rayN-${AppImageOutputArch}.zip"
|
|
||||||
- wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/${FileName}"
|
|
||||||
- 7z x $FileName -aoa
|
|
||||||
- cp -rf v2rayN-${AppImageOutputArch}/* $OutputPath
|
|
||||||
|
|
||||||
script:
|
|
||||||
- mkdir -p usr/bin usr/lib
|
|
||||||
- cp -rf $OutputPath usr/lib/v2rayN
|
|
||||||
- echo "When this file exists, app will not store configs under this folder" > usr/lib/v2rayN/NotStoreConfigHere.txt
|
|
||||||
- ln -sf usr/lib/v2rayN/v2rayN usr/bin/v2rayN
|
|
||||||
- chmod a+x usr/lib/v2rayN/v2rayN
|
|
||||||
- find usr -type f -exec sh -c 'file "{}" | grep -qi "executable" && chmod +x "{}"' \;
|
|
||||||
- install -Dm644 usr/lib/v2rayN/v2rayN.png v2rayN.png
|
|
||||||
- install -Dm644 usr/lib/v2rayN/v2rayN.png usr/share/pixmaps/v2rayN.png
|
|
||||||
- cat > v2rayN.desktop <<EOF
|
|
||||||
- [Desktop Entry]
|
|
||||||
- Name=v2rayN
|
|
||||||
- Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
|
||||||
- Exec=v2rayN
|
|
||||||
- Icon=v2rayN
|
|
||||||
- Terminal=false
|
|
||||||
- Type=Application
|
|
||||||
- Categories=Network;
|
|
||||||
- EOF
|
|
||||||
- install -Dm644 v2rayN.desktop usr/share/applications/v2rayN.desktop
|
|
||||||
- cat > AppRun <<\EOF
|
|
||||||
- #!/bin/sh
|
|
||||||
- HERE="$(dirname "$(readlink -f "${0}")")"
|
|
||||||
- cd ${HERE}/usr/lib/v2rayN
|
|
||||||
- exec ${HERE}/usr/lib/v2rayN/v2rayN $@
|
|
||||||
- EOF
|
|
||||||
- chmod a+x AppRun
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.14.3</Version>
|
<Version>7.14.8</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.4" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.5" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.4" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.5" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.4" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.5" />
|
||||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.4" />
|
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.5" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
||||||
<PackageVersion Include="Downloader" Version="4.0.3" />
|
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
|
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
|
||||||
<PackageVersion Include="Splat.NLog" Version="15.5.3" />
|
<PackageVersion Include="Splat.NLog" Version="16.2.1" />
|
||||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
|||||||
Submodule v2rayN/GlobalHotKeys updated: ef73fa22c4...270f023fcc
@@ -582,9 +582,9 @@ public class Utils
|
|||||||
if (host.StartsWith("#"))
|
if (host.StartsWith("#"))
|
||||||
continue;
|
continue;
|
||||||
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (hostItem.Length != 2)
|
if (hostItem.Length < 2)
|
||||||
continue;
|
continue;
|
||||||
systemHosts.Add(hostItem.Last(), hostItem.First());
|
systemHosts.Add(hostItem[1], hostItem[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,5 +15,6 @@ public enum ECoreType
|
|||||||
brook = 27,
|
brook = 27,
|
||||||
overtls = 28,
|
overtls = 28,
|
||||||
shadowquic = 29,
|
shadowquic = 29,
|
||||||
|
mieru = 30,
|
||||||
v2rayN = 99
|
v2rayN = 99
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace ServiceLib.Enums;
|
|
||||||
|
|
||||||
public enum EMsgCommand
|
|
||||||
{
|
|
||||||
ClearMsg,
|
|
||||||
SendMsgView,
|
|
||||||
SendSnackMsg,
|
|
||||||
RefreshProfiles,
|
|
||||||
AppExit
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ public enum EViewAction
|
|||||||
ShowYesNo,
|
ShowYesNo,
|
||||||
SaveFileDialog,
|
SaveFileDialog,
|
||||||
AddBatchRoutingRulesYesNo,
|
AddBatchRoutingRulesYesNo,
|
||||||
AdjustMainLvColWidth,
|
|
||||||
SetClipboardData,
|
SetClipboardData,
|
||||||
AddServerViaClipboard,
|
AddServerViaClipboard,
|
||||||
ImportRulesFromClipboard,
|
ImportRulesFromClipboard,
|
||||||
@@ -16,7 +15,6 @@ public enum EViewAction
|
|||||||
ShowHideWindow,
|
ShowHideWindow,
|
||||||
ScanScreenTask,
|
ScanScreenTask,
|
||||||
ScanImageTask,
|
ScanImageTask,
|
||||||
Shutdown,
|
|
||||||
BrowseServer,
|
BrowseServer,
|
||||||
ImportRulesFromFile,
|
ImportRulesFromFile,
|
||||||
InitSettingFont,
|
InitSettingFont,
|
||||||
@@ -32,16 +30,7 @@ public enum EViewAction
|
|||||||
FullConfigTemplateWindow,
|
FullConfigTemplateWindow,
|
||||||
GlobalHotkeySettingWindow,
|
GlobalHotkeySettingWindow,
|
||||||
SubSettingWindow,
|
SubSettingWindow,
|
||||||
DispatcherSpeedTest,
|
|
||||||
DispatcherRefreshConnections,
|
|
||||||
DispatcherRefreshProxyGroups,
|
|
||||||
DispatcherProxiesDelayTest,
|
|
||||||
DispatcherStatistics,
|
|
||||||
DispatcherServerAvailability,
|
|
||||||
DispatcherReload,
|
|
||||||
DispatcherRefreshServersBiz,
|
DispatcherRefreshServersBiz,
|
||||||
DispatcherRefreshIcon,
|
DispatcherRefreshIcon,
|
||||||
DispatcherCheckUpdate,
|
|
||||||
DispatcherCheckUpdateFinished,
|
|
||||||
DispatcherShowMsg,
|
DispatcherShowMsg,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -560,6 +560,7 @@ public class Global
|
|||||||
{ ECoreType.brook, "txthinking/brook" },
|
{ ECoreType.brook, "txthinking/brook" },
|
||||||
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
||||||
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
||||||
|
{ ECoreType.mieru, "enfein/mieru" },
|
||||||
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
21
v2rayN/ServiceLib/Handler/AppEvents.cs
Normal file
21
v2rayN/ServiceLib/Handler/AppEvents.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
|
||||||
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
|
public static class AppEvents
|
||||||
|
{
|
||||||
|
public static readonly Subject<Unit> ProfilesRefreshRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<string> SendSnackMsgRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<string> SendMsgViewRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<Unit> AppExitRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<bool> ShutdownRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<Unit> AdjustMainLvColWidthRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||||
|
}
|
||||||
@@ -2321,10 +2321,22 @@ public static class ConfigHandler
|
|||||||
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
|
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
|
||||||
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
|
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
|
||||||
|
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json"));
|
var xrayDnsRussia = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json");
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json"));
|
var singboxDnsRussia = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json");
|
||||||
|
var simpleDnsRussia = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json");
|
||||||
|
|
||||||
config.SimpleDNSItem = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json") ?? InitBuiltinSimpleDNS();
|
if (simpleDnsRussia == null)
|
||||||
|
{
|
||||||
|
xrayDnsRussia.Enabled = true;
|
||||||
|
singboxDnsRussia.Enabled = true;
|
||||||
|
config.SimpleDNSItem = InitBuiltinSimpleDNS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.SimpleDNSItem = simpleDnsRussia;
|
||||||
|
}
|
||||||
|
await SaveDNSItems(config, xrayDnsRussia);
|
||||||
|
await SaveDNSItems(config, singboxDnsRussia);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EPresetType.Iran:
|
case EPresetType.Iran:
|
||||||
@@ -2332,10 +2344,22 @@ public static class ConfigHandler
|
|||||||
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2];
|
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2];
|
||||||
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2];
|
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2];
|
||||||
|
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json"));
|
var xrayDnsIran = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json");
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json"));
|
var singboxDnsIran = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json");
|
||||||
|
var simpleDnsIran = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json");
|
||||||
|
|
||||||
config.SimpleDNSItem = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json") ?? InitBuiltinSimpleDNS();
|
if (simpleDnsIran == null)
|
||||||
|
{
|
||||||
|
xrayDnsIran.Enabled = true;
|
||||||
|
singboxDnsIran.Enabled = true;
|
||||||
|
config.SimpleDNSItem = InitBuiltinSimpleDNS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.SimpleDNSItem = simpleDnsIran;
|
||||||
|
}
|
||||||
|
await SaveDNSItems(config, xrayDnsIran);
|
||||||
|
await SaveDNSItems(config, singboxDnsIran);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ namespace ServiceLib.Handler;
|
|||||||
|
|
||||||
public static class SubscriptionHandler
|
public static class SubscriptionHandler
|
||||||
{
|
{
|
||||||
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Action<bool, string> updateFunc)
|
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
|
await updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
|
||||||
var subItem = await AppManager.Instance.SubItems();
|
var subItem = await AppManager.Instance.SubItems();
|
||||||
|
|
||||||
if (subItem is not { Count: > 0 })
|
if (subItem is not { Count: > 0 })
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
|
await updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var successCount = 0;
|
||||||
foreach (var item in subItem)
|
foreach (var item in subItem)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -25,32 +26,35 @@ public static class SubscriptionHandler
|
|||||||
var hashCode = $"{item.Remarks}->";
|
var hashCode = $"{item.Remarks}->";
|
||||||
if (item.Enabled == false)
|
if (item.Enabled == false)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create download handler
|
// Create download handler
|
||||||
var downloadHandle = CreateDownloadHandler(hashCode, updateFunc);
|
var downloadHandle = CreateDownloadHandler(hashCode, updateFunc);
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
||||||
|
|
||||||
// Get all subscription content (main subscription + additional subscriptions)
|
// Get all subscription content (main subscription + additional subscriptions)
|
||||||
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
|
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
|
||||||
|
|
||||||
// Process download result
|
// Process download result
|
||||||
await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc);
|
if (await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc))
|
||||||
|
{
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
updateFunc?.Invoke(false, "-------------------------------------------------------");
|
await updateFunc?.Invoke(false, "-------------------------------------------------------");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var hashCode = $"{item.Remarks}->";
|
var hashCode = $"{item.Remarks}->";
|
||||||
Logging.SaveLog("UpdateSubscription", ex);
|
Logging.SaveLog("UpdateSubscription", ex);
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}");
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}");
|
||||||
updateFunc?.Invoke(false, "-------------------------------------------------------");
|
await updateFunc?.Invoke(false, "-------------------------------------------------------");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
await updateFunc?.Invoke(successCount > 0, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidSubscription(SubItem item, string subId)
|
private static bool IsValidSubscription(SubItem item, string subId)
|
||||||
@@ -76,7 +80,7 @@ public static class SubscriptionHandler
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DownloadService CreateDownloadHandler(string hashCode, Action<bool, string> updateFunc)
|
private static DownloadService CreateDownloadHandler(string hashCode, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
var downloadHandle = new DownloadService();
|
var downloadHandle = new DownloadService();
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
@@ -181,22 +185,24 @@ public static class SubscriptionHandler
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task ProcessDownloadResult(Config config, string id, string result, string hashCode, Action<bool, string> updateFunc)
|
private static async Task<bool> ProcessDownloadResult(Config config, string id, string result, string hashCode, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
||||||
|
|
||||||
// If result is too short, display content directly
|
// If result is too short, display content directly
|
||||||
if (result.Length < 99)
|
if (result.Length < 99)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{result}");
|
await updateFunc?.Invoke(false, $"{hashCode}{result}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}");
|
||||||
|
|
||||||
// Add servers to configuration
|
// Add servers to configuration
|
||||||
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
|
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
|
||||||
if (ret <= 0)
|
if (ret <= 0)
|
||||||
@@ -206,9 +212,10 @@ public static class SubscriptionHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update completion message
|
// Update completion message
|
||||||
updateFunc?.Invoke(false,
|
await updateFunc?.Invoke(false, ret > 0
|
||||||
ret > 0
|
|
||||||
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
||||||
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
||||||
|
|
||||||
|
return ret > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public sealed class SQLiteHelper
|
|||||||
|
|
||||||
public async Task<int> InsertAllAsync(IEnumerable models)
|
public async Task<int> InsertAllAsync(IEnumerable models)
|
||||||
{
|
{
|
||||||
return await _dbAsync.InsertAllAsync(models);
|
return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> InsertAsync(object model)
|
public async Task<int> InsertAsync(object model)
|
||||||
@@ -46,7 +46,7 @@ public sealed class SQLiteHelper
|
|||||||
|
|
||||||
public async Task<int> UpdateAllAsync(IEnumerable models)
|
public async Task<int> UpdateAllAsync(IEnumerable models)
|
||||||
{
|
{
|
||||||
return await _dbAsync.UpdateAllAsync(models);
|
return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> DeleteAsync(object model)
|
public async Task<int> DeleteAsync(object model)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Reactive;
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public sealed class AppManager
|
public sealed class AppManager
|
||||||
@@ -34,7 +36,7 @@ public sealed class AppManager
|
|||||||
|
|
||||||
#endregion Property
|
#endregion Property
|
||||||
|
|
||||||
#region Init
|
#region App
|
||||||
|
|
||||||
public bool InitApp()
|
public bool InitApp()
|
||||||
{
|
{
|
||||||
@@ -87,7 +89,40 @@ public sealed class AppManager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Init
|
public async Task AppExitAsync(bool needShutdown)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logging.SaveLog("AppExitAsync Begin");
|
||||||
|
|
||||||
|
await SysProxyHandler.UpdateSysProxy(_config, true);
|
||||||
|
AppEvents.AppExitRequested.OnNext(Unit.Default);
|
||||||
|
await Task.Delay(50); //Wait for AppExitRequested to be processed
|
||||||
|
|
||||||
|
await ConfigHandler.SaveConfig(_config);
|
||||||
|
await ProfileExManager.Instance.SaveTo();
|
||||||
|
await StatisticsManager.Instance.SaveTo();
|
||||||
|
await CoreManager.Instance.CoreStop();
|
||||||
|
StatisticsManager.Instance.Close();
|
||||||
|
|
||||||
|
Logging.SaveLog("AppExitAsync End");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (needShutdown)
|
||||||
|
{
|
||||||
|
Shutdown(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shutdown(bool byUser)
|
||||||
|
{
|
||||||
|
AppEvents.ShutdownRequested.OnNext(byUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion App
|
||||||
|
|
||||||
#region Config
|
#region Config
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public sealed class ClashApiManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> updateFunc)
|
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Func<ClashProxyModel?, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -79,12 +79,12 @@ public sealed class ClashApiManager
|
|||||||
tasks.Add(Task.Run(async () =>
|
tasks.Add(Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||||
updateFunc?.Invoke(it, result);
|
await updateFunc?.Invoke(it, result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
updateFunc?.Invoke(null, "");
|
await updateFunc?.Invoke(null, "");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class CoreAdminManager
|
|||||||
private static readonly Lazy<CoreAdminManager> _instance = new(() => new());
|
private static readonly Lazy<CoreAdminManager> _instance = new(() => new());
|
||||||
public static CoreAdminManager Instance => _instance.Value;
|
public static CoreAdminManager Instance => _instance.Value;
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private Action<bool, string>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private int _linuxSudoPid = -1;
|
private int _linuxSudoPid = -1;
|
||||||
private const string _tag = "CoreAdminHandler";
|
private const string _tag = "CoreAdminHandler";
|
||||||
|
|
||||||
public async Task Init(Config config, Action<bool, string> updateFunc)
|
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
if (_config != null)
|
if (_config != null)
|
||||||
{
|
{
|
||||||
@@ -26,9 +26,9 @@ public class CoreAdminManager
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFunc(bool notify, string msg)
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(notify, msg);
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
||||||
@@ -60,7 +60,7 @@ public class CoreAdminManager
|
|||||||
{
|
{
|
||||||
if (e.Data.IsNotEmpty())
|
if (e.Data.IsNotEmpty())
|
||||||
{
|
{
|
||||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
_ = UpdateFunc(false, e.Data + Environment.NewLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ public class CoreAdminManager
|
|||||||
.WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd))
|
.WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd))
|
||||||
.ExecuteBufferedAsync();
|
.ExecuteBufferedAsync();
|
||||||
|
|
||||||
UpdateFunc(false, result.StandardOutput.ToString());
|
await UpdateFunc(false, result.StandardOutput.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ public sealed class CoreInfoManager
|
|||||||
Url = GetCoreUrl(ECoreType.v2fly),
|
Url = GetCoreUrl(ECoreType.v2fly),
|
||||||
Match = "V2Ray",
|
Match = "V2Ray",
|
||||||
VersionArg = "-version",
|
VersionArg = "-version",
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
@@ -90,6 +94,10 @@ public sealed class CoreInfoManager
|
|||||||
Url = GetCoreUrl(ECoreType.v2fly_v5),
|
Url = GetCoreUrl(ECoreType.v2fly_v5),
|
||||||
Match = "V2Ray",
|
Match = "V2Ray",
|
||||||
VersionArg = "version",
|
VersionArg = "version",
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
@@ -107,20 +115,25 @@ public sealed class CoreInfoManager
|
|||||||
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
|
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
|
||||||
Match = "Xray",
|
Match = "Xray",
|
||||||
VersionArg = "-version",
|
VersionArg = "-version",
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ Global.XrayLocalAsset, Utils.GetBinPath("") },
|
||||||
|
{ Global.XrayLocalCert, Utils.GetBinPath("") },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.mihomo,
|
CoreType = ECoreType.mihomo,
|
||||||
CoreExes = ["mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
|
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
|
||||||
Arguments = "-f {0}" + PortableMode(),
|
Arguments = "-f {0}" + PortableMode(),
|
||||||
Url = GetCoreUrl(ECoreType.mihomo),
|
Url = GetCoreUrl(ECoreType.mihomo),
|
||||||
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||||
DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
|
DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-v1-{0}.zip",
|
||||||
DownloadUrlWinArm64 = urlMihomo + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
DownloadUrlWinArm64 = urlMihomo + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
||||||
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-compatible-{0}.gz",
|
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-v1-{0}.gz",
|
||||||
DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
||||||
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-compatible-{0}.gz",
|
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-v1-{0}.gz",
|
||||||
DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
|
DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
|
||||||
Match = "Mihomo",
|
Match = "Mihomo",
|
||||||
VersionArg = "-v",
|
VersionArg = "-v",
|
||||||
@@ -205,12 +218,24 @@ public sealed class CoreInfoManager
|
|||||||
new CoreInfo
|
new CoreInfo
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.shadowquic,
|
CoreType = ECoreType.shadowquic,
|
||||||
CoreExes = [ "shadowquic", "shadowquic"],
|
CoreExes = [ "shadowquic" ],
|
||||||
Arguments = "-c {0}",
|
Arguments = "-c {0}",
|
||||||
Url = GetCoreUrl(ECoreType.shadowquic),
|
Url = GetCoreUrl(ECoreType.shadowquic),
|
||||||
AbsolutePath = false,
|
AbsolutePath = false,
|
||||||
}
|
},
|
||||||
|
|
||||||
|
new CoreInfo
|
||||||
|
{
|
||||||
|
CoreType = ECoreType.mieru,
|
||||||
|
CoreExes = [ "mieru" ],
|
||||||
|
Arguments = "run",
|
||||||
|
Url = GetCoreUrl(ECoreType.mieru),
|
||||||
|
AbsolutePath = false,
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ "MIERU_CONFIG_JSON_FILE", "{0}" },
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,14 @@ public class CoreManager
|
|||||||
private Process? _process;
|
private Process? _process;
|
||||||
private Process? _processPre;
|
private Process? _processPre;
|
||||||
private bool _linuxSudo = false;
|
private bool _linuxSudo = false;
|
||||||
private Action<bool, string>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private const string _tag = "CoreHandler";
|
private const string _tag = "CoreHandler";
|
||||||
|
|
||||||
public async Task Init(Config config, Action<bool, string> updateFunc)
|
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
|
||||||
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
|
||||||
Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
|
||||||
|
|
||||||
//Copy the bin folder to the storage location (for init)
|
//Copy the bin folder to the storage location (for init)
|
||||||
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
||||||
{
|
{
|
||||||
@@ -67,7 +63,7 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
UpdateFunc(false, ResUI.CheckServerSettings);
|
await UpdateFunc(false, ResUI.CheckServerSettings);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +71,13 @@ public class CoreManager
|
|||||||
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
UpdateFunc(true, result.Msg);
|
await UpdateFunc(true, result.Msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFunc(false, $"{node.GetSummary()}");
|
await UpdateFunc(false, $"{node.GetSummary()}");
|
||||||
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
|
await UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
|
||||||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||||
await CoreStop();
|
await CoreStop();
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
@@ -95,7 +91,7 @@ public class CoreManager
|
|||||||
await CoreStartPreService(node);
|
await CoreStartPreService(node);
|
||||||
if (_process != null)
|
if (_process != null)
|
||||||
{
|
{
|
||||||
UpdateFunc(true, $"{node.GetSummary()}");
|
await UpdateFunc(true, $"{node.GetSummary()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,14 +101,14 @@ public class CoreManager
|
|||||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||||
var configPath = Utils.GetBinConfigPath(fileName);
|
var configPath = Utils.GetBinConfigPath(fileName);
|
||||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
|
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
|
||||||
UpdateFunc(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||||
UpdateFunc(false, configPath);
|
await UpdateFunc(false, configPath);
|
||||||
|
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
var proc = await RunProcess(coreInfo, fileName, true, false);
|
||||||
@@ -220,9 +216,9 @@ public class CoreManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFunc(bool notify, string msg)
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(notify, msg);
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Private
|
#endregion Private
|
||||||
@@ -234,7 +230,7 @@ public class CoreManager
|
|||||||
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
UpdateFunc(false, msg);
|
await UpdateFunc(false, msg);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +251,7 @@ public class CoreManager
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
UpdateFunc(mayNeedSudo, ex.Message);
|
await UpdateFunc(mayNeedSudo, ex.Message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +273,10 @@ public class CoreManager
|
|||||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
foreach (var kv in coreInfo.Environment)
|
||||||
|
{
|
||||||
|
proc.StartInfo.Environment[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (displayLog)
|
if (displayLog)
|
||||||
{
|
{
|
||||||
@@ -284,7 +284,7 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
if (e.Data.IsNotEmpty())
|
if (e.Data.IsNotEmpty())
|
||||||
{
|
{
|
||||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
_ = UpdateFunc(false, e.Data + Environment.NewLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proc.OutputDataReceived += dataHandler;
|
proc.OutputDataReceived += dataHandler;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class NoticeManager
|
public class NoticeManager
|
||||||
@@ -13,7 +11,7 @@ public class NoticeManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MessageBus.Current.SendMessage(content, EMsgCommand.SendSnackMsg.ToString());
|
AppEvents.SendSnackMsgRequested.OnNext(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessage(string? content)
|
public void SendMessage(string? content)
|
||||||
@@ -22,7 +20,7 @@ public class NoticeManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MessageBus.Current.SendMessage(content, EMsgCommand.SendMsgView.ToString());
|
AppEvents.SendMsgViewRequested.OnNext(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessageEx(string? content)
|
public void SendMessageEx(string? content)
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ public class StatisticsManager
|
|||||||
private Config _config;
|
private Config _config;
|
||||||
private ServerStatItem? _serverStatItem;
|
private ServerStatItem? _serverStatItem;
|
||||||
private List<ServerStatItem> _lstServerStat;
|
private List<ServerStatItem> _lstServerStat;
|
||||||
private Action<ServerSpeedItem>? _updateFunc;
|
private Func<ServerSpeedItem, Task>? _updateFunc;
|
||||||
|
|
||||||
private StatisticsXrayService? _statisticsXray;
|
private StatisticsXrayService? _statisticsXray;
|
||||||
private StatisticsSingboxService? _statisticsSingbox;
|
private StatisticsSingboxService? _statisticsSingbox;
|
||||||
private static readonly string _tag = "StatisticsHandler";
|
private static readonly string _tag = "StatisticsHandler";
|
||||||
public List<ServerStatItem> ServerStat => _lstServerStat;
|
public List<ServerStatItem> ServerStat => _lstServerStat;
|
||||||
|
|
||||||
public async Task Init(Config config, Action<ServerSpeedItem> updateFunc)
|
public async Task Init(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
@@ -97,9 +97,9 @@ public class StatisticsManager
|
|||||||
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
|
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateServerStatHandler(ServerSpeedItem server)
|
private async Task UpdateServerStatHandler(ServerSpeedItem server)
|
||||||
{
|
{
|
||||||
_ = UpdateServerStat(server);
|
await UpdateServerStat(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateServerStat(ServerSpeedItem server)
|
private async Task UpdateServerStat(ServerSpeedItem server)
|
||||||
@@ -123,7 +123,7 @@ public class StatisticsManager
|
|||||||
server.TodayDown = _serverStatItem.TodayDown;
|
server.TodayDown = _serverStatItem.TodayDown;
|
||||||
server.TotalUp = _serverStatItem.TotalUp;
|
server.TotalUp = _serverStatItem.TotalUp;
|
||||||
server.TotalDown = _serverStatItem.TotalDown;
|
server.TotalDown = _serverStatItem.TotalDown;
|
||||||
_updateFunc?.Invoke(server);
|
await _updateFunc?.Invoke(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetServerStatItem(string indexId)
|
private async Task GetServerStatItem(string indexId)
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ public class TaskManager
|
|||||||
{
|
{
|
||||||
private static readonly Lazy<TaskManager> _instance = new(() => new());
|
private static readonly Lazy<TaskManager> _instance = new(() => new());
|
||||||
public static TaskManager Instance => _instance.Value;
|
public static TaskManager Instance => _instance.Value;
|
||||||
|
private Config _config;
|
||||||
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
|
|
||||||
public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
|
public void RegUpdateTask(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
Task.Run(() => ScheduledTasks(config, updateFunc));
|
_config = config;
|
||||||
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
|
Task.Run(ScheduledTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ScheduledTasks(Config config, Action<bool, string> updateFunc)
|
private async Task ScheduledTasks()
|
||||||
{
|
{
|
||||||
Logging.SaveLog("Setup Scheduled Tasks");
|
Logging.SaveLog("Setup Scheduled Tasks");
|
||||||
|
|
||||||
@@ -21,14 +26,14 @@ public class TaskManager
|
|||||||
await Task.Delay(1000 * 60);
|
await Task.Delay(1000 * 60);
|
||||||
|
|
||||||
//Execute once 1 minute
|
//Execute once 1 minute
|
||||||
await UpdateTaskRunSubscription(config, updateFunc);
|
await UpdateTaskRunSubscription();
|
||||||
|
|
||||||
//Execute once 20 minute
|
//Execute once 20 minute
|
||||||
if (numOfExecuted % 20 == 0)
|
if (numOfExecuted % 20 == 0)
|
||||||
{
|
{
|
||||||
//Logging.SaveLog("Execute save config");
|
//Logging.SaveLog("Execute save config");
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
await ProfileExManager.Instance.SaveTo();
|
await ProfileExManager.Instance.SaveTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,14 +47,14 @@ public class TaskManager
|
|||||||
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
||||||
|
|
||||||
//Check once 1 hour
|
//Check once 1 hour
|
||||||
await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc);
|
await UpdateTaskRunGeo(numOfExecuted / 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
numOfExecuted++;
|
numOfExecuted++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateTaskRunSubscription()
|
||||||
{
|
{
|
||||||
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
||||||
var lstSubs = (await AppManager.Instance.SubItems())?
|
var lstSubs = (await AppManager.Instance.SubItems())?
|
||||||
@@ -66,30 +71,30 @@ public class TaskManager
|
|||||||
|
|
||||||
foreach (var item in lstSubs)
|
foreach (var item in lstSubs)
|
||||||
{
|
{
|
||||||
await SubscriptionHandler.UpdateProcess(config, item.Id, true, (success, msg) =>
|
await SubscriptionHandler.UpdateProcess(_config, item.Id, true, async (success, msg) =>
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(success, msg);
|
await _updateFunc?.Invoke(success, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
Logging.SaveLog($"Update subscription end. {msg}");
|
Logging.SaveLog($"Update subscription end. {msg}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
item.UpdateTime = updateTime;
|
item.UpdateTime = updateTime;
|
||||||
await ConfigHandler.AddSubItem(config, item);
|
await ConfigHandler.AddSubItem(_config, item);
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTaskRunGeo(Config config, int hours, Action<bool, string> updateFunc)
|
private async Task UpdateTaskRunGeo(int hours)
|
||||||
{
|
{
|
||||||
if (config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % config.GuiItem.AutoUpdateInterval == 0)
|
if (_config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % _config.GuiItem.AutoUpdateInterval == 0)
|
||||||
{
|
{
|
||||||
Logging.SaveLog("Execute update geo files");
|
Logging.SaveLog("Execute update geo files");
|
||||||
|
|
||||||
var updateHandle = new UpdateService();
|
var updateHandle = new UpdateService();
|
||||||
await updateHandle.UpdateGeoFileAll(config, (success, msg) =>
|
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, msg);
|
await _updateFunc?.Invoke(false, msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ public class CoreInfo
|
|||||||
public string? Match { get; set; }
|
public string? Match { get; set; }
|
||||||
public string? VersionArg { get; set; }
|
public string? VersionArg { get; set; }
|
||||||
public bool AbsolutePath { get; set; }
|
public bool AbsolutePath { get; set; }
|
||||||
|
public IDictionary<string, string?> Environment { get; set; } = new Dictionary<string, string?>();
|
||||||
}
|
}
|
||||||
|
|||||||
70
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
70
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -1860,6 +1860,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Start parsing and processing subscription content 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string MsgStartParsingSubscription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MsgStartParsingSubscription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Started updating {0}... 的本地化字符串。
|
/// 查找类似 Started updating {0}... 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3021,6 +3030,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Select Profile 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSelectProfile {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSelectProfile", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Set system proxy 的本地化字符串。
|
/// 查找类似 Set system proxy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3736,28 +3754,9 @@ namespace ServiceLib.Resx {
|
|||||||
/// 查找类似 Auto Route 的本地化字符串。
|
/// 查找类似 Auto Route 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbSettingsTunAutoRoute {
|
public static string TbSettingsTunAutoRoute {
|
||||||
get { return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture); }
|
get {
|
||||||
}
|
return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture);
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Strict Route 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSettingsTunStrictRoute {
|
|
||||||
get { return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Stack 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSettingsTunStack {
|
|
||||||
get { return ResourceManager.GetString("TbSettingsTunStack", resourceCulture); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 MTU 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSettingsTunMtu {
|
|
||||||
get { return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -3769,6 +3768,33 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 MTU 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunMtu {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Stack 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunStack {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunStack", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Strict Route 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunStrictRoute {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Enable UDP 的本地化字符串。
|
/// 查找类似 Enable UDP 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1509,4 +1509,10 @@
|
|||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1509,4 +1509,10 @@
|
|||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1509,4 +1509,10 @@
|
|||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1509,4 +1509,10 @@
|
|||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
|
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1506,4 +1506,10 @@
|
|||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置,DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。</value>
|
<value>此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置,DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>开始解析和处理订阅内容</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>选择配置文件</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1506,4 +1506,10 @@
|
|||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>開始解析和處理訂閱內容</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -80,25 +80,31 @@ public partial class CoreConfigSingboxService
|
|||||||
hostsDns.predefined = Global.PredefinedHosts;
|
hostsDns.predefined = Global.PredefinedHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (simpleDNSItem.UseSystemHosts == true)
|
||||||
|
{
|
||||||
|
var systemHosts = Utils.GetSystemHosts();
|
||||||
|
if (systemHosts != null && systemHosts.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var host in systemHosts)
|
||||||
|
{
|
||||||
|
hostsDns.predefined.TryAdd(host.Key, new List<string> { host.Value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
var userHostsMap = simpleDNSItem.Hosts?
|
var userHostsMap = simpleDNSItem.Hosts
|
||||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
.Select(line => line.Trim())
|
||||||
.Where(line => line.Contains(' '))
|
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||||
|
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Where(parts => parts.Length >= 2)
|
||||||
|
.GroupBy(parts => parts[0])
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
line =>
|
group => group.Key,
|
||||||
{
|
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
);
|
||||||
return parts[0];
|
|
||||||
},
|
|
||||||
line =>
|
|
||||||
{
|
|
||||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var values = parts.Skip(1).ToList();
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
) ?? new Dictionary<string, List<string>>();
|
|
||||||
|
|
||||||
foreach (var kvp in userHostsMap)
|
foreach (var kvp in userHostsMap)
|
||||||
{
|
{
|
||||||
@@ -106,22 +112,6 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simpleDNSItem.UseSystemHosts == true)
|
|
||||||
{
|
|
||||||
var systemHosts = Utils.GetSystemHosts();
|
|
||||||
if (systemHosts.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var host in systemHosts)
|
|
||||||
{
|
|
||||||
if (hostsDns.predefined[host.Key] != null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
hostsDns.predefined[host.Key] = new List<string> { host.Value };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var host in hostsDns.predefined)
|
foreach (var host in hostsDns.predefined)
|
||||||
{
|
{
|
||||||
if (finalDns.server == host.Key)
|
if (finalDns.server == host.Key)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public partial class CoreConfigSingboxService
|
|||||||
singboxConfig.inbounds = [];
|
singboxConfig.inbounds = [];
|
||||||
|
|
||||||
if (!_config.TunModeItem.EnableTun
|
if (!_config.TunModeItem.EnableTun
|
||||||
|| _config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box)
|
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
|
||||||
{
|
{
|
||||||
var inbound = new Inbound4Sbox()
|
var inbound = new Inbound4Sbox()
|
||||||
{
|
{
|
||||||
@@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
return await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
|
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
|
||||||
|
|||||||
@@ -248,43 +248,31 @@ public partial class CoreConfigV2rayService
|
|||||||
if (simpleDNSItem.UseSystemHosts == true)
|
if (simpleDNSItem.UseSystemHosts == true)
|
||||||
{
|
{
|
||||||
var systemHosts = Utils.GetSystemHosts();
|
var systemHosts = Utils.GetSystemHosts();
|
||||||
if (systemHosts.Count > 0)
|
var normalHost = v2rayConfig?.dns?.hosts;
|
||||||
|
|
||||||
|
if (normalHost != null && systemHosts?.Count > 0)
|
||||||
{
|
{
|
||||||
var normalHost = v2rayConfig.dns.hosts;
|
foreach (var host in systemHosts)
|
||||||
if (normalHost != null)
|
|
||||||
{
|
{
|
||||||
foreach (var host in systemHosts)
|
normalHost.TryAdd(host.Key, new List<string> { host.Value });
|
||||||
{
|
|
||||||
if (normalHost[host.Key] != null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
normalHost[host.Key] = new List<string> { host.Value };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var userHostsMap = simpleDNSItem.Hosts?
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
|
||||||
.Where(line => line.Contains(' '))
|
|
||||||
.ToDictionary(
|
|
||||||
line =>
|
|
||||||
{
|
|
||||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
return parts[0];
|
|
||||||
},
|
|
||||||
line =>
|
|
||||||
{
|
|
||||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var values = parts.Skip(1).ToList();
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (userHostsMap != null)
|
|
||||||
{
|
{
|
||||||
|
var userHostsMap = simpleDNSItem.Hosts
|
||||||
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||||
|
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Where(parts => parts.Length >= 2)
|
||||||
|
.GroupBy(parts => parts[0])
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||||
|
);
|
||||||
|
|
||||||
foreach (var kvp in userHostsMap)
|
foreach (var kvp in userHostsMap)
|
||||||
{
|
{
|
||||||
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
|
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class DownloadService
|
|||||||
|
|
||||||
private static readonly string _tag = "DownloadService";
|
private static readonly string _tag = "DownloadService";
|
||||||
|
|
||||||
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action<bool, string> updateFunc)
|
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -31,10 +31,10 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, ex.Message);
|
await updateFunc?.Invoke(false, ex.Message);
|
||||||
if (ex.InnerException != null)
|
if (ex.InnerException != null)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, ex.InnerException.Message);
|
await updateFunc?.Invoke(false, ex.InnerException.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -5,26 +5,20 @@ using System.Net.Sockets;
|
|||||||
|
|
||||||
namespace ServiceLib.Services;
|
namespace ServiceLib.Services;
|
||||||
|
|
||||||
public class SpeedtestService
|
public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateFunc)
|
||||||
{
|
{
|
||||||
private static readonly string _tag = "SpeedtestService";
|
private static readonly string _tag = "SpeedtestService";
|
||||||
private Config? _config;
|
private readonly Config? _config = config;
|
||||||
private Action<SpeedTestResult>? _updateFunc;
|
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
|
||||||
private static readonly ConcurrentBag<string> _lstExitLoop = new();
|
private static readonly ConcurrentBag<string> _lstExitLoop = new();
|
||||||
|
|
||||||
public SpeedtestService(Config config, Action<SpeedTestResult> updateFunc)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
_updateFunc = updateFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await RunAsync(actionType, selecteds);
|
await RunAsync(actionType, selecteds);
|
||||||
await ProfileExManager.Instance.SaveTo();
|
await ProfileExManager.Instance.SaveTo();
|
||||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
await UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +37,7 @@ public class SpeedtestService
|
|||||||
var exitLoopKey = Utils.GetGuid(false);
|
var exitLoopKey = Utils.GetGuid(false);
|
||||||
_lstExitLoop.Add(exitLoopKey);
|
_lstExitLoop.Add(exitLoopKey);
|
||||||
|
|
||||||
var lstSelected = GetClearItem(actionType, selecteds);
|
var lstSelected = await GetClearItem(actionType, selecteds);
|
||||||
|
|
||||||
switch (actionType)
|
switch (actionType)
|
||||||
{
|
{
|
||||||
@@ -65,7 +59,7 @@ public class SpeedtestService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ServerTestItem> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
private async Task<List<ServerTestItem>> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
||||||
{
|
{
|
||||||
var lstSelected = new List<ServerTestItem>();
|
var lstSelected = new List<ServerTestItem>();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
@@ -97,17 +91,17 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
case ESpeedActionType.Tcping:
|
case ESpeedActionType.Tcping:
|
||||||
case ESpeedActionType.Realping:
|
case ESpeedActionType.Realping:
|
||||||
UpdateFunc(it.IndexId, ResUI.Speedtesting, "");
|
await UpdateFunc(it.IndexId, ResUI.Speedtesting, "");
|
||||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESpeedActionType.Speedtest:
|
case ESpeedActionType.Speedtest:
|
||||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait);
|
||||||
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESpeedActionType.Mixedtest:
|
case ESpeedActionType.Mixedtest:
|
||||||
UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
await UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
||||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
||||||
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
||||||
break;
|
break;
|
||||||
@@ -133,7 +127,7 @@ public class SpeedtestService
|
|||||||
var responseTime = await GetTcpingTime(it.Address, it.Port);
|
var responseTime = await GetTcpingTime(it.Address, it.Port);
|
||||||
|
|
||||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||||
UpdateFunc(it.IndexId, responseTime.ToString());
|
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -169,11 +163,11 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
||||||
{
|
{
|
||||||
UpdateFunc("", ResUI.SpeedtestingSkip);
|
await UpdateFunc("", ResUI.SpeedtestingSkip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count));
|
await UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count));
|
||||||
|
|
||||||
if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount)
|
if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount)
|
||||||
{
|
{
|
||||||
@@ -239,7 +233,7 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (it.ConfigType == EConfigType.Custom)
|
||||||
@@ -256,7 +250,7 @@ public class SpeedtestService
|
|||||||
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
|
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
|
||||||
if (pid < 0)
|
if (pid < 0)
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -270,7 +264,7 @@ public class SpeedtestService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,25 +292,25 @@ public class SpeedtestService
|
|||||||
var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
||||||
|
|
||||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||||
UpdateFunc(it.IndexId, responseTime.ToString());
|
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it)
|
private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it)
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.Speedtesting);
|
await UpdateFunc(it.IndexId, "", ResUI.Speedtesting);
|
||||||
|
|
||||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||||
var url = _config.SpeedTestItem.SpeedTestUrl;
|
var url = _config.SpeedTestItem.SpeedTestUrl;
|
||||||
var timeout = _config.SpeedTestItem.SpeedTestTimeout;
|
var timeout = _config.SpeedTestItem.SpeedTestTimeout;
|
||||||
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (success, msg) =>
|
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, async (success, msg) =>
|
||||||
{
|
{
|
||||||
decimal.TryParse(msg, out var dec);
|
decimal.TryParse(msg, out var dec);
|
||||||
if (dec > 0)
|
if (dec > 0)
|
||||||
{
|
{
|
||||||
ProfileExManager.Instance.SetTestSpeed(it.IndexId, dec);
|
ProfileExManager.Instance.SetTestSpeed(it.IndexId, dec);
|
||||||
}
|
}
|
||||||
UpdateFunc(it.IndexId, "", msg);
|
await UpdateFunc(it.IndexId, "", msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,9 +365,9 @@ public class SpeedtestService
|
|||||||
return lstTest;
|
return lstTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFunc(string indexId, string delay, string speed = "")
|
private async Task UpdateFunc(string indexId, string delay, string speed = "")
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
await _updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
||||||
if (indexId.IsNotEmpty() && speed.IsNotEmpty())
|
if (indexId.IsNotEmpty() && speed.IsNotEmpty())
|
||||||
{
|
{
|
||||||
ProfileExManager.Instance.SetTestMessage(indexId, speed);
|
ProfileExManager.Instance.SetTestMessage(indexId, speed);
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ public class StatisticsSingboxService
|
|||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
private bool _exitFlag;
|
private bool _exitFlag;
|
||||||
private ClientWebSocket? webSocket;
|
private ClientWebSocket? webSocket;
|
||||||
private Action<ServerSpeedItem>? _updateFunc;
|
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
|
||||||
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
|
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
|
||||||
private static readonly string _tag = "StatisticsSingboxService";
|
private static readonly string _tag = "StatisticsSingboxService";
|
||||||
|
|
||||||
public StatisticsSingboxService(Config config, Action<ServerSpeedItem> updateFunc)
|
public StatisticsSingboxService(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
@@ -90,7 +90,7 @@ public class StatisticsSingboxService
|
|||||||
{
|
{
|
||||||
ParseOutput(result, out var up, out var down);
|
ParseOutput(result, out var up, out var down);
|
||||||
|
|
||||||
_updateFunc?.Invoke(new ServerSpeedItem()
|
await _updateFunc?.Invoke(new ServerSpeedItem()
|
||||||
{
|
{
|
||||||
ProxyUp = (long)(up / 1000),
|
ProxyUp = (long)(up / 1000),
|
||||||
ProxyDown = (long)(down / 1000)
|
ProxyDown = (long)(down / 1000)
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ public class StatisticsXrayService
|
|||||||
private ServerSpeedItem _serverSpeedItem = new();
|
private ServerSpeedItem _serverSpeedItem = new();
|
||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
private bool _exitFlag;
|
private bool _exitFlag;
|
||||||
private Action<ServerSpeedItem>? _updateFunc;
|
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
|
||||||
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
|
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
|
||||||
|
|
||||||
public StatisticsXrayService(Config config, Action<ServerSpeedItem> updateFunc)
|
public StatisticsXrayService(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
@@ -39,7 +39,7 @@ public class StatisticsXrayService
|
|||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
var server = ParseOutput(result) ?? new ServerSpeedItem();
|
var server = ParseOutput(result) ?? new ServerSpeedItem();
|
||||||
_updateFunc?.Invoke(server);
|
await _updateFunc?.Invoke(server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ namespace ServiceLib.Services;
|
|||||||
|
|
||||||
public class UpdateService
|
public class UpdateService
|
||||||
{
|
{
|
||||||
private Action<bool, string>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private readonly int _timeout = 30;
|
private readonly int _timeout = 30;
|
||||||
private static readonly string _tag = "UpdateService";
|
private static readonly string _tag = "UpdateService";
|
||||||
|
|
||||||
public async Task CheckUpdateGuiN(Config config, Action<bool, string> updateFunc, bool preRelease)
|
public async Task CheckUpdateGuiN(Config config, Func<bool, string, Task> updateFunc, bool preRelease)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
var url = string.Empty;
|
var url = string.Empty;
|
||||||
@@ -20,25 +20,25 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
UpdateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||||
_updateFunc?.Invoke(true, Utils.UrlEncode(fileName));
|
UpdateFunc(true, Utils.UrlEncode(fileName));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.Msg);
|
UpdateFunc(false, args.Msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.GetException().Message);
|
UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
|
await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
|
||||||
var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
|
var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
|
|
||||||
url = result.Data?.ToString();
|
url = result.Data?.ToString();
|
||||||
fileName = Utils.GetTempPath(Utils.GetGuid());
|
fileName = Utils.GetTempPath(Utils.GetGuid());
|
||||||
@@ -46,11 +46,11 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckUpdateCore(ECoreType type, Config config, Action<bool, string> updateFunc, bool preRelease)
|
public async Task CheckUpdateCore(ECoreType type, Config config, Func<bool, string, Task> updateFunc, bool preRelease)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
var url = string.Empty;
|
var url = string.Empty;
|
||||||
@@ -61,34 +61,34 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
UpdateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgUnpacking);
|
UpdateFunc(false, ResUI.MsgUnpacking);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(true, fileName);
|
UpdateFunc(true, fileName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
UpdateFunc(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.Msg);
|
UpdateFunc(false, args.Msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.GetException().Message);
|
UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, type));
|
await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, type));
|
||||||
var result = await CheckUpdateAsync(downloadHandle, type, preRelease);
|
var result = await CheckUpdateAsync(downloadHandle, type, preRelease);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
|
|
||||||
url = result.Data?.ToString();
|
url = result.Data?.ToString();
|
||||||
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
|
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
|
||||||
@@ -99,17 +99,17 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (!result.Msg.IsNullOrEmpty())
|
if (!result.Msg.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateGeoFileAll(Config config, Action<bool, string> updateFunc)
|
public async Task UpdateGeoFileAll(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
await UpdateGeoFiles(config, updateFunc);
|
await UpdateGeoFiles(config, updateFunc);
|
||||||
await UpdateOtherFiles(config, updateFunc);
|
await UpdateOtherFiles(config, updateFunc);
|
||||||
await UpdateSrsFileAll(config, updateFunc);
|
await UpdateSrsFileAll(config, updateFunc);
|
||||||
_updateFunc?.Invoke(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region CheckUpdate private
|
#region CheckUpdate private
|
||||||
@@ -128,7 +128,7 @@ public class UpdateService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
await UpdateFunc(false, ex.Message);
|
||||||
return new RetResult(false, ex.Message);
|
return new RetResult(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ public class UpdateService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
await UpdateFunc(false, ex.Message);
|
||||||
return new SemanticVersion("");
|
return new SemanticVersion("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ public class UpdateService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
await UpdateFunc(false, ex.Message);
|
||||||
return new RetResult(false, ex.Message);
|
return new RetResult(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ public class UpdateService
|
|||||||
|
|
||||||
#region Geo private
|
#region Geo private
|
||||||
|
|
||||||
private async Task UpdateGeoFiles(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateGeoFiles(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateOtherFiles(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateOtherFiles(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
//If it is not in China area, no update is required
|
//If it is not in China area, no update is required
|
||||||
if (config.ConstItem.GeoSourceUrl.IsNotEmpty())
|
if (config.ConstItem.GeoSourceUrl.IsNotEmpty())
|
||||||
@@ -371,7 +371,7 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSrsFileAll(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateSrsFileAll(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
@@ -426,7 +426,7 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSrsFile(string type, string srsName, Config config, Action<bool, string> updateFunc)
|
private async Task UpdateSrsFile(string type, string srsName, Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl)
|
var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl)
|
||||||
? Global.SingboxRulesetUrl
|
? Global.SingboxRulesetUrl
|
||||||
@@ -439,7 +439,7 @@ public class UpdateService
|
|||||||
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
|
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadGeoFile(string url, string fileName, string targetPath, Action<bool, string> updateFunc)
|
private async Task DownloadGeoFile(string url, string fileName, string targetPath, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
var tmpFileName = Utils.GetTempPath(Utils.GetGuid());
|
var tmpFileName = Utils.GetTempPath(Utils.GetGuid());
|
||||||
|
|
||||||
@@ -448,7 +448,7 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName));
|
UpdateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -457,26 +457,31 @@ public class UpdateService
|
|||||||
File.Copy(tmpFileName, targetPath, true);
|
File.Copy(tmpFileName, targetPath, true);
|
||||||
|
|
||||||
File.Delete(tmpFileName);
|
File.Delete(tmpFileName);
|
||||||
//_updateFunc?.Invoke(true, "");
|
//await UpdateFunc(true, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
UpdateFunc(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.Msg);
|
UpdateFunc(false, args.Msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.GetException().Message);
|
UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout);
|
await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Geo private
|
#endregion Geo private
|
||||||
|
|
||||||
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
|
{
|
||||||
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
@@ -136,8 +135,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
var result = await CreateZipFileFromDirectory(fileBackup);
|
var result = await CreateZipFileFromDirectory(fileBackup);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
await AppManager.Instance.AppExitAsync(false);
|
||||||
await service?.MyAppExitAsync(true);
|
|
||||||
await SQLiteHelper.Instance.DisposeDbConnectionAsync();
|
await SQLiteHelper.Instance.DisposeDbConnectionAsync();
|
||||||
|
|
||||||
var toPath = Utils.GetConfigPath();
|
var toPath = Utils.GetConfigPath();
|
||||||
@@ -154,7 +152,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
_ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath());
|
_ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
service?.Shutdown(true);
|
AppManager.Instance.Shutdown(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
@@ -11,11 +13,11 @@ namespace ServiceLib.ViewModels;
|
|||||||
public class CheckUpdateViewModel : MyReactiveObject
|
public class CheckUpdateViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private const string _geo = "GeoFiles";
|
private const string _geo = "GeoFiles";
|
||||||
private string _v2rayN = ECoreType.v2rayN.ToString();
|
private readonly string _v2rayN = ECoreType.v2rayN.ToString();
|
||||||
private List<CheckUpdateModel> _lstUpdated = [];
|
private List<CheckUpdateModel> _lstUpdated = [];
|
||||||
|
private static readonly string _tag = "CheckUpdateViewModel";
|
||||||
|
|
||||||
private IObservableCollection<CheckUpdateModel> _checkUpdateModel = new ObservableCollectionExtended<CheckUpdateModel>();
|
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
||||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels => _checkUpdateModel;
|
|
||||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
||||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
||||||
|
|
||||||
@@ -24,9 +26,11 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
_config = AppManager.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () =>
|
CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
|
||||||
|
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
|
||||||
{
|
{
|
||||||
await CheckUpdate();
|
Logging.SaveLog(_tag, ex);
|
||||||
|
_ = UpdateView(_v2rayN, ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
|
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
|
||||||
@@ -41,20 +45,20 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private void RefreshCheckUpdateItems()
|
private void RefreshCheckUpdateItems()
|
||||||
{
|
{
|
||||||
_checkUpdateModel.Clear();
|
CheckUpdateModels.Clear();
|
||||||
|
|
||||||
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
|
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
|
||||||
{
|
{
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(_v2rayN));
|
CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN));
|
||||||
//Not Windows and under Win10
|
//Not Windows and under Win10
|
||||||
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
|
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
|
||||||
{
|
{
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.Xray.ToString()));
|
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString()));
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString()));
|
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString()));
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString()));
|
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(_geo));
|
CheckUpdateModels.Add(GetCheckUpdateModel(_geo));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
||||||
@@ -69,7 +73,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task SaveSelectedCoreTypes()
|
private async Task SaveSelectedCoreTypes()
|
||||||
{
|
{
|
||||||
_config.CheckUpdateItem.SelectedCoreTypes = _checkUpdateModel.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList();
|
_config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList();
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,17 +85,19 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
private async Task CheckUpdateTask()
|
private async Task CheckUpdateTask()
|
||||||
{
|
{
|
||||||
_lstUpdated.Clear();
|
_lstUpdated.Clear();
|
||||||
_lstUpdated = _checkUpdateModel.Where(x => x.IsSelected == true)
|
_lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true)
|
||||||
.Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList();
|
.Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList();
|
||||||
await SaveSelectedCoreTypes();
|
await SaveSelectedCoreTypes();
|
||||||
|
|
||||||
for (var k = _checkUpdateModel.Count - 1; k >= 0; k--)
|
for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
|
||||||
{
|
{
|
||||||
var item = _checkUpdateModel[k];
|
var item = CheckUpdateModels[k];
|
||||||
if (item.IsSelected != true)
|
if (item.IsSelected != true)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
UpdateView(item.CoreType, "...");
|
await UpdateView(item.CoreType, "...");
|
||||||
if (item.CoreType == _geo)
|
if (item.CoreType == _geo)
|
||||||
{
|
{
|
||||||
await CheckUpdateGeo();
|
await CheckUpdateGeo();
|
||||||
@@ -129,9 +135,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task CheckUpdateGeo()
|
private async Task CheckUpdateGeo()
|
||||||
{
|
{
|
||||||
void _updateUI(bool success, string msg)
|
async Task _updateUI(bool success, string msg)
|
||||||
{
|
{
|
||||||
UpdateView(_geo, msg);
|
await UpdateView(_geo, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
UpdatedPlusPlus(_geo, "");
|
UpdatedPlusPlus(_geo, "");
|
||||||
@@ -146,12 +152,12 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task CheckUpdateN(bool preRelease)
|
private async Task CheckUpdateN(bool preRelease)
|
||||||
{
|
{
|
||||||
void _updateUI(bool success, string msg)
|
async Task _updateUI(bool success, string msg)
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, msg);
|
await UpdateView(_v2rayN, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, ResUI.OperationSuccess);
|
await UpdateView(_v2rayN, ResUI.OperationSuccess);
|
||||||
UpdatedPlusPlus(_v2rayN, msg);
|
UpdatedPlusPlus(_v2rayN, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,12 +170,12 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
|
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
|
||||||
{
|
{
|
||||||
void _updateUI(bool success, string msg)
|
async Task _updateUI(bool success, string msg)
|
||||||
{
|
{
|
||||||
UpdateView(model.CoreType, msg);
|
await UpdateView(model.CoreType, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
|
await UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
|
||||||
|
|
||||||
UpdatedPlusPlus(model.CoreType, msg);
|
UpdatedPlusPlus(model.CoreType, msg);
|
||||||
}
|
}
|
||||||
@@ -186,21 +192,30 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count)
|
if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count)
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, false);
|
await UpdateFinishedSub(false);
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
await UpgradeCore();
|
await UpgradeCore();
|
||||||
|
|
||||||
if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true))
|
if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true))
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
UpgradeN();
|
await UpgradeN();
|
||||||
}
|
}
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
_updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, true);
|
await UpdateFinishedSub(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFinishedResult(bool blReload)
|
private async Task UpdateFinishedSub(bool blReload)
|
||||||
|
{
|
||||||
|
RxApp.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) =>
|
||||||
|
{
|
||||||
|
_ = UpdateFinishedResult(blReload);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateFinishedResult(bool blReload)
|
||||||
{
|
{
|
||||||
if (blReload)
|
if (blReload)
|
||||||
{
|
{
|
||||||
@@ -212,7 +227,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpgradeN()
|
private async Task UpgradeN()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -221,16 +236,23 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Utils.UpgradeAppExists(out _))
|
if (!Utils.UpgradeAppExists(out var upgradeFileName))
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip);
|
await UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip);
|
||||||
|
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
|
||||||
|
Logging.SaveLog("UpgradeApp does not exist");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.UpgradeApp(fileName);
|
|
||||||
|
var id = ProcUtils.ProcessStart(upgradeFileName, fileName, Utils.StartupPath());
|
||||||
|
if (id > 0)
|
||||||
|
{
|
||||||
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, ex.Message);
|
await UpdateView(_v2rayN, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +303,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully);
|
await UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully);
|
||||||
|
|
||||||
if (File.Exists(fileName))
|
if (File.Exists(fileName))
|
||||||
{
|
{
|
||||||
@@ -290,23 +312,31 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateView(string coreType, string msg)
|
private async Task UpdateView(string coreType, string msg)
|
||||||
{
|
{
|
||||||
var item = new CheckUpdateModel()
|
var item = new CheckUpdateModel()
|
||||||
{
|
{
|
||||||
CoreType = coreType,
|
CoreType = coreType,
|
||||||
Remarks = msg,
|
Remarks = msg,
|
||||||
};
|
};
|
||||||
_updateView?.Invoke(EViewAction.DispatcherCheckUpdate, item);
|
|
||||||
|
RxApp.MainThreadScheduler.Schedule(item, (scheduler, model) =>
|
||||||
|
{
|
||||||
|
_ = UpdateViewResult(model);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateViewResult(CheckUpdateModel model)
|
public async Task UpdateViewResult(CheckUpdateModel model)
|
||||||
{
|
{
|
||||||
var found = _checkUpdateModel.FirstOrDefault(t => t.CoreType == model.CoreType);
|
var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType);
|
||||||
if (found == null)
|
if (found == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var itemCopy = JsonUtils.DeepCopy(found);
|
var itemCopy = JsonUtils.DeepCopy(found);
|
||||||
itemCopy.Remarks = model.Remarks;
|
itemCopy.Remarks = model.Remarks;
|
||||||
_checkUpdateModel.Replace(found, itemCopy);
|
CheckUpdateModels.Replace(found, itemCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
@@ -9,8 +10,7 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public class ClashConnectionsViewModel : MyReactiveObject
|
public class ClashConnectionsViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private IObservableCollection<ClashConnectionModel> _connectionItems = new ObservableCollectionExtended<ClashConnectionModel>();
|
public IObservableCollection<ClashConnectionModel> ConnectionItems { get; } = new ObservableCollectionExtended<ClashConnectionModel>();
|
||||||
public IObservableCollection<ClashConnectionModel> ConnectionItems => _connectionItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ClashConnectionModel SelectedSource { get; set; }
|
public ClashConnectionModel SelectedSource { get; set; }
|
||||||
@@ -64,12 +64,16 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = _updateView?.Invoke(EViewAction.DispatcherRefreshConnections, ret?.connections);
|
RxApp.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) =>
|
||||||
|
{
|
||||||
|
_ = RefreshConnections(model);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshConnections(List<ConnectionItem>? connections)
|
public async Task RefreshConnections(List<ConnectionItem>? connections)
|
||||||
{
|
{
|
||||||
_connectionItems.Clear();
|
ConnectionItems.Clear();
|
||||||
|
|
||||||
var dtNow = DateTime.Now;
|
var dtNow = DateTime.Now;
|
||||||
var lstModel = new List<ClashConnectionModel>();
|
var lstModel = new List<ClashConnectionModel>();
|
||||||
@@ -99,7 +103,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectionItems.AddRange(lstModel);
|
ConnectionItems.AddRange(lstModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClashConnectionClose(bool all)
|
public async Task ClashConnectionClose(bool all)
|
||||||
@@ -116,7 +120,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_connectionItems.Clear();
|
ConnectionItems.Clear();
|
||||||
}
|
}
|
||||||
await ClashApiManager.Instance.ClashConnectionClose(id);
|
await ClashApiManager.Instance.ClashConnectionClose(id);
|
||||||
await GetClashConnections();
|
await GetClashConnections();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Concurrency;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
@@ -15,11 +17,8 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
private Dictionary<string, ProvidersItem>? _providers;
|
private Dictionary<string, ProvidersItem>? _providers;
|
||||||
private readonly int _delayTimeout = 99999999;
|
private readonly int _delayTimeout = 99999999;
|
||||||
|
|
||||||
private IObservableCollection<ClashProxyModel> _proxyGroups = new ObservableCollectionExtended<ClashProxyModel>();
|
public IObservableCollection<ClashProxyModel> ProxyGroups { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||||
private IObservableCollection<ClashProxyModel> _proxyDetails = new ObservableCollectionExtended<ClashProxyModel>();
|
public IObservableCollection<ClashProxyModel> ProxyDetails { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||||
|
|
||||||
public IObservableCollection<ClashProxyModel> ProxyGroups => _proxyGroups;
|
|
||||||
public IObservableCollection<ClashProxyModel> ProxyDetails => _proxyDetails;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ClashProxyModel SelectedGroup { get; set; }
|
public ClashProxyModel SelectedGroup { get; set; }
|
||||||
@@ -168,11 +167,11 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (refreshUI)
|
if (refreshUI)
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.DispatcherRefreshProxyGroups, null);
|
RxApp.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshProxyGroups()
|
public async Task RefreshProxyGroups()
|
||||||
{
|
{
|
||||||
if (_proxies == null)
|
if (_proxies == null)
|
||||||
{
|
{
|
||||||
@@ -180,7 +179,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
var selectedName = SelectedGroup?.Name;
|
var selectedName = SelectedGroup?.Name;
|
||||||
_proxyGroups.Clear();
|
ProxyGroups.Clear();
|
||||||
|
|
||||||
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
||||||
if (proxyGroups != null && proxyGroups.Count > 0)
|
if (proxyGroups != null && proxyGroups.Count > 0)
|
||||||
@@ -196,7 +195,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_proxyGroups.Add(new ClashProxyModel()
|
ProxyGroups.Add(new ClashProxyModel()
|
||||||
{
|
{
|
||||||
Now = item.now,
|
Now = item.now,
|
||||||
Name = item.name,
|
Name = item.name,
|
||||||
@@ -212,12 +211,12 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var item = _proxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
var item = ProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
||||||
if (item != null && item.Name.IsNotEmpty())
|
if (item != null && item.Name.IsNotEmpty())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_proxyGroups.Add(new ClashProxyModel()
|
ProxyGroups.Add(new ClashProxyModel()
|
||||||
{
|
{
|
||||||
Now = kv.Value.now,
|
Now = kv.Value.now,
|
||||||
Name = kv.Key,
|
Name = kv.Key,
|
||||||
@@ -225,15 +224,15 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_proxyGroups != null && _proxyGroups.Count > 0)
|
if (ProxyGroups != null && ProxyGroups.Count > 0)
|
||||||
{
|
{
|
||||||
if (selectedName != null && _proxyGroups.Any(t => t.Name == selectedName))
|
if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
|
||||||
{
|
{
|
||||||
SelectedGroup = _proxyGroups.FirstOrDefault(t => t.Name == selectedName);
|
SelectedGroup = ProxyGroups.FirstOrDefault(t => t.Name == selectedName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SelectedGroup = _proxyGroups.First();
|
SelectedGroup = ProxyGroups.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -244,7 +243,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private void RefreshProxyDetails(bool c)
|
private void RefreshProxyDetails(bool c)
|
||||||
{
|
{
|
||||||
_proxyDetails.Clear();
|
ProxyDetails.Clear();
|
||||||
if (!c)
|
if (!c)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -297,7 +296,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_proxyDetails.AddRange(lstDetails);
|
ProxyDetails.AddRange(lstDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProxiesItem? TryGetProxy(string name)
|
private ProxiesItem? TryGetProxy(string name)
|
||||||
@@ -359,12 +358,12 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
|
await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
|
||||||
|
|
||||||
selectedProxy.now = nameNode;
|
selectedProxy.now = nameNode;
|
||||||
var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
|
var group = ProxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
|
||||||
if (group != null)
|
if (group != null)
|
||||||
{
|
{
|
||||||
group.Now = nameNode;
|
group.Now = nameNode;
|
||||||
var group2 = JsonUtils.DeepCopy(group);
|
var group2 = JsonUtils.DeepCopy(group);
|
||||||
_proxyGroups.Replace(group, group2);
|
ProxyGroups.Replace(group, group2);
|
||||||
|
|
||||||
SelectedGroup = group2;
|
SelectedGroup = group2;
|
||||||
}
|
}
|
||||||
@@ -373,22 +372,27 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task ProxiesDelayTest(bool blAll = true)
|
private async Task ProxiesDelayTest(bool blAll = true)
|
||||||
{
|
{
|
||||||
ClashApiManager.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) =>
|
ClashApiManager.Instance.ClashProxiesDelayTest(blAll, ProxyDetails.ToList(), async (item, result) =>
|
||||||
{
|
{
|
||||||
if (item == null || result.IsNullOrEmpty())
|
if (item == null || result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateView?.Invoke(EViewAction.DispatcherProxiesDelayTest, new SpeedTestResult() { IndexId = item.Name, Delay = result });
|
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
||||||
|
RxApp.MainThreadScheduler.Schedule(model, (scheduler, model) =>
|
||||||
|
{
|
||||||
|
_ = ProxiesDelayTestResult(model);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProxiesDelayTestResult(SpeedTestResult result)
|
public async Task ProxiesDelayTestResult(SpeedTestResult result)
|
||||||
{
|
{
|
||||||
//UpdateHandler(false, $"{item.name}={result}");
|
//UpdateHandler(false, $"{item.name}={result}");
|
||||||
var detail = _proxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
|
var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
|
||||||
if (detail == null)
|
if (detail == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -410,7 +414,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
detail.Delay = _delayTimeout;
|
detail.Delay = _delayTimeout;
|
||||||
detail.DelayName = string.Empty;
|
detail.DelayName = string.Empty;
|
||||||
}
|
}
|
||||||
_proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
|
ProxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion proxy function
|
#endregion proxy function
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Concurrency;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
using Splat;
|
||||||
@@ -245,7 +246,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region Actions
|
#region Actions
|
||||||
|
|
||||||
private void UpdateHandler(bool notify, string msg)
|
private async Task UpdateHandler(bool notify, string msg)
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.SendMessage(msg);
|
NoticeManager.Instance.SendMessage(msg);
|
||||||
if (notify)
|
if (notify)
|
||||||
@@ -254,86 +255,31 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTaskHandler(bool success, string msg)
|
private async Task UpdateTaskHandler(bool success, string msg)
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.SendMessageEx(msg);
|
NoticeManager.Instance.SendMessageEx(msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
var indexIdOld = _config.IndexId;
|
var indexIdOld = _config.IndexId;
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (indexIdOld != _config.IndexId)
|
if (indexIdOld != _config.IndexId)
|
||||||
{
|
{
|
||||||
_ = Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null);
|
AppEvents.AdjustMainLvColWidthRequested.OnNext(Unit.Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateStatisticsHandler(ServerSpeedItem update)
|
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
if (!_config.UiItem.ShowInTaskbar)
|
if (!_config.UiItem.ShowInTaskbar)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_updateView?.Invoke(EViewAction.DispatcherStatistics, update);
|
AppEvents.DispatcherStatisticsRequested.OnNext(update);
|
||||||
}
|
|
||||||
|
|
||||||
public void SetStatisticsResult(ServerSpeedItem update)
|
|
||||||
{
|
|
||||||
if (_config.GuiItem.DisplayRealTimeSpeed)
|
|
||||||
{
|
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.UpdateStatistics(update);
|
|
||||||
}
|
|
||||||
if (_config.GuiItem.EnableStatistics && (update.ProxyUp + update.ProxyDown) > 0 && DateTime.Now.Second % 9 == 0)
|
|
||||||
{
|
|
||||||
Locator.Current.GetService<ProfilesViewModel>()?.UpdateStatistics(update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task MyAppExitAsync(bool blWindowsShutDown)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logging.SaveLog("MyAppExitAsync Begin");
|
|
||||||
|
|
||||||
await SysProxyHandler.UpdateSysProxy(_config, true);
|
|
||||||
MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString());
|
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
|
||||||
await ProfileExManager.Instance.SaveTo();
|
|
||||||
await StatisticsManager.Instance.SaveTo();
|
|
||||||
await CoreManager.Instance.CoreStop();
|
|
||||||
StatisticsManager.Instance.Close();
|
|
||||||
|
|
||||||
Logging.SaveLog("MyAppExitAsync End");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!blWindowsShutDown)
|
|
||||||
{
|
|
||||||
_updateView?.Invoke(EViewAction.Shutdown, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpgradeApp(string arg)
|
|
||||||
{
|
|
||||||
if (!Utils.UpgradeAppExists(out var upgradeFileName))
|
|
||||||
{
|
|
||||||
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
|
|
||||||
Logging.SaveLog("UpgradeApp does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = ProcUtils.ProcessStart(upgradeFileName, arg, Utils.StartupPath());
|
|
||||||
if (id > 0)
|
|
||||||
{
|
|
||||||
await MyAppExitAsync(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowHideWindow(bool? blShow)
|
public void ShowHideWindow(bool? blShow)
|
||||||
@@ -341,18 +287,15 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
_updateView?.Invoke(EViewAction.ShowHideWindow, blShow);
|
_updateView?.Invoke(EViewAction.ShowHideWindow, blShow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Shutdown(bool byUser)
|
|
||||||
{
|
|
||||||
_updateView?.Invoke(EViewAction.Shutdown, byUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Actions
|
#endregion Actions
|
||||||
|
|
||||||
#region Servers && Groups
|
#region Servers && Groups
|
||||||
|
|
||||||
private void RefreshServers()
|
private async Task RefreshServers()
|
||||||
{
|
{
|
||||||
MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString());
|
AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSubscriptions()
|
private void RefreshSubscriptions()
|
||||||
@@ -384,7 +327,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (ret == true)
|
if (ret == true)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (item.IndexId == _config.IndexId)
|
if (item.IndexId == _config.IndexId)
|
||||||
{
|
{
|
||||||
await Reload();
|
await Reload();
|
||||||
@@ -399,11 +342,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
RefreshSubscriptions();
|
RefreshSubscriptions();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -449,11 +392,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
RefreshSubscriptions();
|
RefreshSubscriptions();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -477,7 +420,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
|
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
|
||||||
{
|
{
|
||||||
await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler);
|
await Task.Run(async () => await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Subscription
|
#endregion Subscription
|
||||||
@@ -526,13 +469,13 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
public async Task RebootAsAdmin()
|
public async Task RebootAsAdmin()
|
||||||
{
|
{
|
||||||
ProcUtils.RebootAsAdmin();
|
ProcUtils.RebootAsAdmin();
|
||||||
await MyAppExitAsync(false);
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ClearServerStatistics()
|
private async Task ClearServerStatistics()
|
||||||
{
|
{
|
||||||
await StatisticsManager.Instance.ClearAllServerStatistics();
|
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OpenTheFileLocation()
|
private async Task OpenTheFileLocation()
|
||||||
@@ -576,7 +519,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
});
|
});
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
|
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
|
||||||
|
|
||||||
_updateView?.Invoke(EViewAction.DispatcherReload, null);
|
RxApp.MainThreadScheduler.Schedule(() => _ = ReloadResult());
|
||||||
|
|
||||||
BlReloadEnabled = true;
|
BlReloadEnabled = true;
|
||||||
if (_hasNextReloadJob)
|
if (_hasNextReloadJob)
|
||||||
@@ -586,7 +529,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadResult()
|
public async Task ReloadResult()
|
||||||
{
|
{
|
||||||
// BlReloadEnabled = true;
|
// BlReloadEnabled = true;
|
||||||
//Locator.Current.GetService<StatusBarViewModel>()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false);
|
//Locator.Current.GetService<StatusBarViewModel>()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false);
|
||||||
@@ -596,7 +539,9 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
|
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ TabMainSelectedIndex = 0; }
|
{
|
||||||
|
TabMainSelectedIndex = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadCore()
|
private async Task LoadCore()
|
||||||
@@ -631,7 +576,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
await new UpdateService().UpdateGeoFileAll(_config, UpdateHandler);
|
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);
|
||||||
await Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
@@ -7,8 +8,8 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public class MsgViewModel : MyReactiveObject
|
public class MsgViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private ConcurrentQueue<string> _queueMsg = new();
|
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||||
private int _numMaxMsg = 500;
|
private readonly int _numMaxMsg = 500;
|
||||||
private bool _lastMsgFilterNotAvailable;
|
private bool _lastMsgFilterNotAvailable;
|
||||||
private bool _blLockShow = false;
|
private bool _blLockShow = false;
|
||||||
|
|
||||||
@@ -34,12 +35,10 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
y => y == true)
|
y => y == true)
|
||||||
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
|
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
|
||||||
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.SendMsgView.ToString()).Subscribe(OnNext);
|
AppEvents.SendMsgViewRequested
|
||||||
}
|
.AsObservable()
|
||||||
|
//.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
private async void OnNext(string x)
|
.Subscribe(async content => await AppendQueueMsg(content));
|
||||||
{
|
|
||||||
await AppendQueueMsg(x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AppendQueueMsg(string msg)
|
private async Task AppendQueueMsg(string msg)
|
||||||
|
|||||||
352
v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs
Normal file
352
v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
|
||||||
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
|
public class ProfilesSelectViewModel : MyReactiveObject
|
||||||
|
{
|
||||||
|
#region private prop
|
||||||
|
|
||||||
|
private string _serverFilter = string.Empty;
|
||||||
|
private Dictionary<string, bool> _dicHeaderSort = new();
|
||||||
|
private string _subIndexId = string.Empty;
|
||||||
|
|
||||||
|
// ConfigType filter state: default include-mode with all types selected
|
||||||
|
private List<EConfigType> _filterConfigTypes = new();
|
||||||
|
|
||||||
|
private bool _filterExclude = false;
|
||||||
|
|
||||||
|
#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; }
|
||||||
|
|
||||||
|
// Include/Exclude filter for ConfigType
|
||||||
|
public List<EConfigType> FilterConfigTypes
|
||||||
|
{
|
||||||
|
get => _filterConfigTypes;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _filterConfigTypes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public bool FilterExclude
|
||||||
|
{
|
||||||
|
get => _filterExclude;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _filterExclude, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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));
|
||||||
|
|
||||||
|
// React to ConfigType filter changes
|
||||||
|
this.WhenAnyValue(x => x.FilterExclude)
|
||||||
|
.Skip(1)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.FilterConfigTypes)
|
||||||
|
.Skip(1)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
_ = Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Init()
|
||||||
|
{
|
||||||
|
SelectedProfile = new();
|
||||||
|
SelectedSub = new();
|
||||||
|
|
||||||
|
// Default: include mode with all ConfigTypes selected
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FilterExclude = false;
|
||||||
|
FilterConfigTypes = Enum.GetValues(typeof(EConfigType)).Cast<EConfigType>().ToList();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
FilterConfigTypes = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
await RefreshSubscriptions();
|
||||||
|
await RefreshServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Init
|
||||||
|
|
||||||
|
#region Actions
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
lstModel = (from t in lstModel
|
||||||
|
select new ProfileItemModel
|
||||||
|
{
|
||||||
|
IndexId = t.IndexId,
|
||||||
|
ConfigType = t.ConfigType,
|
||||||
|
Remarks = t.Remarks,
|
||||||
|
Address = t.Address,
|
||||||
|
Port = t.Port,
|
||||||
|
Security = t.Security,
|
||||||
|
Network = t.Network,
|
||||||
|
StreamSecurity = t.StreamSecurity,
|
||||||
|
Subid = t.Subid,
|
||||||
|
SubRemarks = t.SubRemarks,
|
||||||
|
IsActive = t.IndexId == _config.IndexId,
|
||||||
|
}).OrderBy(t => t.Sort).ToList();
|
||||||
|
|
||||||
|
// Apply ConfigType filter (include or exclude)
|
||||||
|
if (FilterConfigTypes != null && FilterConfigTypes.Count > 0)
|
||||||
|
{
|
||||||
|
if (FilterExclude)
|
||||||
|
{
|
||||||
|
lstModel = lstModel.Where(t => !FilterConfigTypes.Contains(t.ConfigType)).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lstModel = lstModel.Where(t => FilterConfigTypes.Contains(t.ConfigType)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lstModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<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<List<ProfileItem>?> GetProfileItems()
|
||||||
|
{
|
||||||
|
if (SelectedProfiles == null || SelectedProfiles.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var lst = new List<ProfileItem>();
|
||||||
|
foreach (var sp in SelectedProfiles)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sp?.IndexId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
lst.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lst.Count == 0)
|
||||||
|
{
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SortServer(string colName)
|
||||||
|
{
|
||||||
|
if (colName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prop = typeof(ProfileItemModel).GetProperty(colName);
|
||||||
|
if (prop == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dicHeaderSort.TryAdd(colName, true);
|
||||||
|
var asc = _dicHeaderSort[colName];
|
||||||
|
|
||||||
|
var comparer = Comparer<object?>.Create((a, b) =>
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(a, b))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a is null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b is null)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.GetType() == b.GetType() && a is IComparable ca)
|
||||||
|
{
|
||||||
|
return ca.CompareTo(b);
|
||||||
|
}
|
||||||
|
return string.Compare(a.ToString(), b.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
});
|
||||||
|
|
||||||
|
object? KeySelector(ProfileItemModel x)
|
||||||
|
{
|
||||||
|
return prop.GetValue(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<ProfileItemModel> sorted = asc
|
||||||
|
? ProfileItems.OrderBy(KeySelector, comparer)
|
||||||
|
: ProfileItems.OrderByDescending(KeySelector, comparer);
|
||||||
|
|
||||||
|
var list = sorted.ToList();
|
||||||
|
ProfileItems.Clear();
|
||||||
|
ProfileItems.AddRange(list);
|
||||||
|
|
||||||
|
_dicHeaderSort[colName] = !asc;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Servers && Groups
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
// External setter for ConfigType filter
|
||||||
|
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
|
||||||
|
{
|
||||||
|
FilterConfigTypes = types?.Distinct().ToList() ?? new List<EConfigType>();
|
||||||
|
FilterExclude = exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public API
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
@@ -22,13 +23,9 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
private IObservableCollection<ProfileItemModel> _profileItems = new ObservableCollectionExtended<ProfileItemModel>();
|
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||||
public IObservableCollection<ProfileItemModel> ProfileItems => _profileItems;
|
|
||||||
|
|
||||||
private IObservableCollection<SubItem> _subItems = new ObservableCollectionExtended<SubItem>();
|
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||||
public IObservableCollection<SubItem> SubItems => _subItems;
|
|
||||||
|
|
||||||
private IObservableCollection<ComboItem> _servers = new ObservableCollectionExtended<ComboItem>();
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItemModel SelectedProfile { get; set; }
|
public ProfileItemModel SelectedProfile { get; set; }
|
||||||
@@ -41,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
|
||||||
@@ -118,15 +109,10 @@ 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)
|
||||||
.Subscribe(c => ServerFilterChanged(c));
|
.Subscribe(async c => await ServerFilterChanged(c));
|
||||||
|
|
||||||
//servers delete
|
//servers delete
|
||||||
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -247,10 +233,19 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion WhenAnyValue && ReactiveCommand
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
if (_updateView != null)
|
#region AppEvents
|
||||||
{
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext);
|
AppEvents.ProfilesRefreshRequested
|
||||||
}
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
AppEvents.DispatcherStatisticsRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async result => await UpdateStatistics(result));
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
@@ -260,27 +255,21 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = new();
|
SelectedProfile = new();
|
||||||
SelectedSub = new();
|
SelectedSub = new();
|
||||||
SelectedMoveToGroup = new();
|
SelectedMoveToGroup = new();
|
||||||
SelectedServer = new();
|
|
||||||
|
|
||||||
await RefreshSubscriptions();
|
await RefreshSubscriptions();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Init
|
#endregion Init
|
||||||
|
|
||||||
#region Actions
|
#region Actions
|
||||||
|
|
||||||
private async void OnNext(string x)
|
|
||||||
{
|
|
||||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reload()
|
private void Reload()
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSpeedTestResult(SpeedTestResult result)
|
public async Task SetSpeedTestResult(SpeedTestResult result)
|
||||||
{
|
{
|
||||||
if (result.IndexId.IsNullOrEmpty())
|
if (result.IndexId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
@@ -288,7 +277,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
NoticeManager.Instance.Enqueue(result.Delay);
|
NoticeManager.Instance.Enqueue(result.Delay);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
|
var item = ProfileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -307,11 +296,18 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
//_profileItems.Replace(item, JsonUtils.DeepCopy(item));
|
//_profileItems.Replace(item, JsonUtils.DeepCopy(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatistics(ServerSpeedItem update)
|
public async Task UpdateStatistics(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
|
if (!_config.GuiItem.EnableStatistics
|
||||||
|
|| (update.ProxyUp + update.ProxyDown) <= 0
|
||||||
|
|| DateTime.Now.Second % 3 != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = _profileItems.FirstOrDefault(it => it.IndexId == update.IndexId);
|
var item = ProfileItems.FirstOrDefault(it => it.IndexId == update.IndexId);
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
item.TodayDown = Utils.HumanFy(update.TodayDown);
|
item.TodayDown = Utils.HumanFy(update.TodayDown);
|
||||||
@@ -336,11 +332,6 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AutofitColumnWidthAsync()
|
|
||||||
{
|
|
||||||
await _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Actions
|
#endregion Actions
|
||||||
|
|
||||||
#region Servers && Groups
|
#region Servers && Groups
|
||||||
@@ -353,12 +344,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
_config.SubIndexId = SelectedSub?.Id;
|
_config.SubIndexId = SelectedSub?.Id;
|
||||||
|
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
{
|
{
|
||||||
if (!c)
|
if (!c)
|
||||||
{
|
{
|
||||||
@@ -367,22 +358,24 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
_serverFilter = ServerFilter;
|
_serverFilter = ServerFilter;
|
||||||
if (_serverFilter.IsNullOrEmpty())
|
if (_serverFilter.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshServers()
|
public async Task RefreshServers()
|
||||||
{
|
{
|
||||||
MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString());
|
AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshServersBiz()
|
private async Task RefreshServersBiz()
|
||||||
{
|
{
|
||||||
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
||||||
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
||||||
|
|
||||||
_profileItems.Clear();
|
ProfileItems.Clear();
|
||||||
_profileItems.AddRange(lstModel);
|
ProfileItems.AddRange(lstModel);
|
||||||
if (lstModel.Count > 0)
|
if (lstModel.Count > 0)
|
||||||
{
|
{
|
||||||
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||||
@@ -395,25 +388,27 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = lstModel.First();
|
SelectedProfile = lstModel.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshSubscriptions()
|
public async Task RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
_subItems.Clear();
|
SubItems.Clear();
|
||||||
|
|
||||||
_subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||||
|
|
||||||
foreach (var item in await AppManager.Instance.SubItems())
|
foreach (var item in await AppManager.Instance.SubItems())
|
||||||
{
|
{
|
||||||
_subItems.Add(item);
|
SubItems.Add(item);
|
||||||
}
|
}
|
||||||
if (_config.SubIndexId != null && _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null)
|
if (_config.SubIndexId != null && SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null)
|
||||||
{
|
{
|
||||||
SelectedSub = _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId);
|
SelectedSub = SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SelectedSub = _subItems.First();
|
SelectedSub = SubItems.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +509,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (ret == true)
|
if (ret == true)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (item.IndexId == _config.IndexId)
|
if (item.IndexId == _config.IndexId)
|
||||||
{
|
{
|
||||||
Reload();
|
Reload();
|
||||||
@@ -537,11 +532,11 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
await ConfigHandler.RemoveServers(_config, lstSelected);
|
await ConfigHandler.RemoveServers(_config, lstSelected);
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
if (lstSelected.Count == _profileItems.Count)
|
if (lstSelected.Count == ProfileItems.Count)
|
||||||
{
|
{
|
||||||
_profileItems.Clear();
|
ProfileItems.Clear();
|
||||||
}
|
}
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (exists)
|
if (exists)
|
||||||
{
|
{
|
||||||
Reload();
|
Reload();
|
||||||
@@ -553,7 +548,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId);
|
var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId);
|
||||||
if (tuple.Item1 > 0 || tuple.Item2 > 0)
|
if (tuple.Item1 > 0 || tuple.Item2 > 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
|
||||||
@@ -568,7 +563,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (await ConfigHandler.CopyServer(_config, lstSelected) == 0)
|
if (await ConfigHandler.CopyServer(_config, lstSelected) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,24 +596,11 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
|
if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -652,7 +634,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (ret?.Data?.ToString() == _config.IndexId)
|
if (ret?.Data?.ToString() == _config.IndexId)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -675,13 +657,13 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_dicHeaderSort[colName] = !asc;
|
_dicHeaderSort[colName] = !asc;
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveInvalidServerResult()
|
public async Task RemoveInvalidServerResult()
|
||||||
{
|
{
|
||||||
var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId);
|
var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId);
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +684,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id);
|
await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id);
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
|
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
SelectedMoveToGroup = null;
|
SelectedMoveToGroup = null;
|
||||||
SelectedMoveToGroup = new();
|
SelectedMoveToGroup = new();
|
||||||
}
|
}
|
||||||
@@ -723,18 +705,18 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0)
|
if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem)
|
public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem)
|
||||||
{
|
{
|
||||||
var targetIndex = _profileItems.IndexOf(targetItem);
|
var targetIndex = ProfileItems.IndexOf(targetItem);
|
||||||
if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex)
|
if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex)
|
||||||
{
|
{
|
||||||
if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0)
|
if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,7 +725,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (actionType == ESpeedActionType.Mixedtest)
|
if (actionType == ESpeedActionType.Mixedtest)
|
||||||
{
|
{
|
||||||
SelectedProfiles = _profileItems;
|
SelectedProfiles = ProfileItems;
|
||||||
}
|
}
|
||||||
var lstSelected = await GetProfileItems(false);
|
var lstSelected = await GetProfileItems(false);
|
||||||
if (lstSelected == null)
|
if (lstSelected == null)
|
||||||
@@ -751,7 +733,14 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_speedtestService ??= new SpeedtestService(_config, (SpeedTestResult result) => _updateView?.Invoke(EViewAction.DispatcherSpeedTest, result));
|
_speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) =>
|
||||||
|
{
|
||||||
|
RxApp.MainThreadScheduler.Schedule(result, (scheduler, result) =>
|
||||||
|
{
|
||||||
|
_ = SetSpeedTestResult(result);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
_speedtestService?.RunLoop(actionType, lstSelected);
|
_speedtestService?.RunLoop(actionType, lstSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItem SelectedRouting { get; set; }
|
public RoutingItem SelectedRouting { get; set; }
|
||||||
|
|
||||||
private IObservableCollection<RulesItemModel> _rulesItems = new ObservableCollectionExtended<RulesItemModel>();
|
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
|
||||||
public IObservableCollection<RulesItemModel> RulesItems => _rulesItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RulesItemModel SelectedSource { get; set; }
|
public RulesItemModel SelectedSource { get; set; }
|
||||||
@@ -101,7 +100,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public void RefreshRulesItems()
|
public void RefreshRulesItems()
|
||||||
{
|
{
|
||||||
_rulesItems.Clear();
|
RulesItems.Clear();
|
||||||
|
|
||||||
foreach (var item in _rules)
|
foreach (var item in _rules)
|
||||||
{
|
{
|
||||||
@@ -118,7 +117,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
Enabled = item.Enabled,
|
Enabled = item.Enabled,
|
||||||
Remarks = item.Remarks,
|
Remarks = item.Remarks,
|
||||||
};
|
};
|
||||||
_rulesItems.Add(it);
|
RulesItems.Add(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
#region Reactive
|
#region Reactive
|
||||||
|
|
||||||
private IObservableCollection<RoutingItemModel> _routingItems = new ObservableCollectionExtended<RoutingItemModel>();
|
public IObservableCollection<RoutingItemModel> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItemModel>();
|
||||||
public IObservableCollection<RoutingItemModel> RoutingItems => _routingItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItemModel SelectedSource { get; set; }
|
public RoutingItemModel SelectedSource { get; set; }
|
||||||
@@ -82,7 +81,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshRoutingItems()
|
public async Task RefreshRoutingItems()
|
||||||
{
|
{
|
||||||
_routingItems.Clear();
|
RoutingItems.Clear();
|
||||||
|
|
||||||
var routings = await AppManager.Instance.RoutingItems();
|
var routings = await AppManager.Instance.RoutingItems();
|
||||||
foreach (var item in routings)
|
foreach (var item in routings)
|
||||||
@@ -98,7 +97,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox,
|
CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox,
|
||||||
Sort = item.Sort,
|
Sort = item.Sort,
|
||||||
};
|
};
|
||||||
_routingItems.Add(it);
|
RoutingItems.Add(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
@@ -11,11 +13,9 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
private IObservableCollection<RoutingItem> _routingItems = new ObservableCollectionExtended<RoutingItem>();
|
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
|
||||||
public IObservableCollection<RoutingItem> RoutingItems => _routingItems;
|
|
||||||
|
|
||||||
private IObservableCollection<ComboItem> _servers = new ObservableCollectionExtended<ComboItem>();
|
public IObservableCollection<ComboItem> Servers { get; } = new ObservableCollectionExtended<ComboItem>();
|
||||||
public IObservableCollection<ComboItem> Servers => _servers;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItem SelectedRouting { get; set; }
|
public RoutingItem SelectedRouting { get; set; }
|
||||||
@@ -197,10 +197,20 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion WhenAnyValue && ReactiveCommand
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
#region AppEvents
|
||||||
|
|
||||||
if (updateView != null)
|
if (updateView != null)
|
||||||
{
|
{
|
||||||
InitUpdateView(updateView);
|
InitUpdateView(updateView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppEvents.DispatcherStatisticsRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async result => await UpdateStatistics(result));
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,15 +226,13 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
if (_updateView != null)
|
if (_updateView != null)
|
||||||
{
|
{
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext);
|
AppEvents.ProfilesRefreshRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz()); //.DisposeWith(_disposables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnNext(string x)
|
|
||||||
{
|
|
||||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CopyProxyCmdToClipboard()
|
private async Task CopyProxyCmdToClipboard()
|
||||||
{
|
{
|
||||||
var cmd = Utils.IsWindows() ? "set" : "export";
|
var cmd = Utils.IsWindows() ? "set" : "export";
|
||||||
@@ -263,7 +271,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
await service.UpdateSubscriptionProcess("", blProxy);
|
await service.UpdateSubscriptionProcess("", blProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshServersBiz()
|
private async Task RefreshServersBiz()
|
||||||
{
|
{
|
||||||
await RefreshServersMenu();
|
await RefreshServersMenu();
|
||||||
|
|
||||||
@@ -285,7 +293,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
|
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
|
||||||
|
|
||||||
_servers.Clear();
|
Servers.Clear();
|
||||||
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
||||||
{
|
{
|
||||||
BlServers = false;
|
BlServers = false;
|
||||||
@@ -299,7 +307,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
string name = it.GetSummary();
|
string name = it.GetSummary();
|
||||||
|
|
||||||
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
||||||
_servers.Add(item);
|
Servers.Add(item);
|
||||||
if (_config.IndexId == it.IndexId)
|
if (_config.IndexId == it.IndexId)
|
||||||
{
|
{
|
||||||
SelectedServer = item;
|
SelectedServer = item;
|
||||||
@@ -332,15 +340,24 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting);
|
await TestServerAvailabilitySub(ResUI.Speedtesting);
|
||||||
|
|
||||||
var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck);
|
var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck);
|
||||||
|
|
||||||
NoticeManager.Instance.SendMessageEx(msg);
|
NoticeManager.Instance.SendMessageEx(msg);
|
||||||
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg);
|
await TestServerAvailabilitySub(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TestServerAvailabilityResult(string msg)
|
private async Task TestServerAvailabilitySub(string msg)
|
||||||
|
{
|
||||||
|
RxApp.MainThreadScheduler.Schedule(msg, (scheduler, msg) =>
|
||||||
|
{
|
||||||
|
_ = TestServerAvailabilityResult(msg);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TestServerAvailabilityResult(string msg)
|
||||||
{
|
{
|
||||||
RunningInfoDisplay = msg;
|
RunningInfoDisplay = msg;
|
||||||
}
|
}
|
||||||
@@ -378,13 +395,13 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshRoutingsMenu()
|
public async Task RefreshRoutingsMenu()
|
||||||
{
|
{
|
||||||
_routingItems.Clear();
|
RoutingItems.Clear();
|
||||||
|
|
||||||
BlRouting = true;
|
BlRouting = true;
|
||||||
var routings = await AppManager.Instance.RoutingItems();
|
var routings = await AppManager.Instance.RoutingItems();
|
||||||
foreach (var item in routings)
|
foreach (var item in routings)
|
||||||
{
|
{
|
||||||
_routingItems.Add(item);
|
RoutingItems.Add(item);
|
||||||
if (item.IsActive)
|
if (item.IsActive)
|
||||||
{
|
{
|
||||||
SelectedRouting = item;
|
SelectedRouting = item;
|
||||||
@@ -509,8 +526,13 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatistics(ServerSpeedItem update)
|
public async Task UpdateStatistics(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
|
if (!_config.GuiItem.DisplayRealTimeSpeed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config.IsRunningCore(ECoreType.sing_box))
|
if (_config.IsRunningCore(ECoreType.sing_box))
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public class SubSettingViewModel : MyReactiveObject
|
public class SubSettingViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private IObservableCollection<SubItem> _subItems = new ObservableCollectionExtended<SubItem>();
|
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||||
public IObservableCollection<SubItem> SubItems => _subItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedSource { get; set; }
|
public SubItem SelectedSource { get; set; }
|
||||||
@@ -60,8 +59,8 @@ public class SubSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshSubItems()
|
public async Task RefreshSubItems()
|
||||||
{
|
{
|
||||||
_subItems.Clear();
|
SubItems.Clear();
|
||||||
_subItems.AddRange(await AppManager.Instance.SubItems());
|
SubItems.AddRange(await AppManager.Instance.SubItems());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EditSubAsync(bool blNew)
|
public async Task EditSubAsync(bool blNew)
|
||||||
|
|||||||
@@ -74,11 +74,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
private async void MenuExit_Click(object? sender, EventArgs e)
|
private async void MenuExit_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
await AppManager.Instance.AppExitAsync(false);
|
||||||
if (service != null)
|
AppManager.Instance.Shutdown(true);
|
||||||
{
|
|
||||||
await service.MyAppExitAsync(true);
|
|
||||||
}
|
|
||||||
service?.Shutdown(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,8 @@
|
|||||||
<Style Selector="ScrollViewer">
|
<Style Selector="ScrollViewer">
|
||||||
<Setter Property="AllowAutoHide" Value="False" />
|
<Setter Property="AllowAutoHide" Value="False" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TabControl">
|
||||||
|
<Setter Property="Theme" Value="{StaticResource LineTabControl}" />
|
||||||
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|||||||
@@ -23,14 +23,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||||
|
|||||||
@@ -23,14 +23,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
@@ -24,25 +23,6 @@ public partial class CheckUpdateView : ReactiveUserControl<CheckUpdateViewModel>
|
|||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
{
|
{
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case EViewAction.DispatcherCheckUpdate:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.UpdateViewResult((CheckUpdateModel)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherCheckUpdateFinished:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.UpdateFinishedResult((bool)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Reactive.Disposables;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
@@ -31,17 +30,6 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
|
|||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
{
|
{
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case EViewAction.DispatcherRefreshConnections:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.RefreshConnections((List<ConnectionItem>?)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Splat;
|
using Splat;
|
||||||
@@ -40,23 +39,6 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
|
|||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
{
|
{
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case EViewAction.DispatcherRefreshProxyGroups:
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.RefreshProxyGroups(),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherProxiesDelayTest:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.ProxiesDelayTestResult((SpeedTestResult)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,14 +24,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@
|
|||||||
x:Name="cmbDirectDNS"
|
x:Name="cmbDirectDNS"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding DirectDNS, Mode=TwoWay}" />
|
Text="{Binding DirectDNS, Mode=TwoWay}" />
|
||||||
|
|
||||||
@@ -75,7 +73,7 @@
|
|||||||
x:Name="cmbRemoteDNS"
|
x:Name="cmbRemoteDNS"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding RemoteDNS, Mode=TwoWay}" />
|
Text="{Binding RemoteDNS, Mode=TwoWay}" />
|
||||||
|
|
||||||
@@ -89,7 +87,7 @@
|
|||||||
x:Name="cmbSBResolverDNS"
|
x:Name="cmbSBResolverDNS"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding SingboxOutboundsResolveDNS, Mode=TwoWay}" />
|
Text="{Binding SingboxOutboundsResolveDNS, Mode=TwoWay}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -110,7 +108,7 @@
|
|||||||
x:Name="cmbSBFinalResolverDNS"
|
x:Name="cmbSBFinalResolverDNS"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding SingboxFinalResolveDNS, Mode=TwoWay}" />
|
Text="{Binding SingboxFinalResolveDNS, Mode=TwoWay}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -331,8 +329,7 @@
|
|||||||
<Button
|
<Button
|
||||||
x:Name="btnImportDefConfig4V2rayCompatible"
|
x:Name="btnImportDefConfig4V2rayCompatible"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
|
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}" />
|
||||||
Cursor="Hand" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -415,8 +412,7 @@
|
|||||||
<Button
|
<Button
|
||||||
x:Name="btnImportDefConfig4SingboxCompatible"
|
x:Name="btnImportDefConfig4SingboxCompatible"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
|
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}" />
|
||||||
Cursor="Hand" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public partial class FullConfigTemplateWindow : WindowBase<FullConfigTemplateVie
|
|||||||
{
|
{
|
||||||
ProcUtils.ProcessStart("https://github.com/2dust/v2rayN/wiki/Description-of-some-ui#%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E6%A8%A1%E6%9D%BF%E8%AE%BE%E7%BD%AE");
|
ProcUtils.ProcessStart("https://github.com/2dust/v2rayN/wiki/Description-of-some-ui#%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E6%A8%A1%E6%9D%BF%E8%AE%BE%E7%BD%AE");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
btnCancel.Focus();
|
btnCancel.Focus();
|
||||||
|
|||||||
@@ -28,14 +28,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
|
|||||||
|
|
||||||
return res.ToString();
|
return res.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
btnCancel.Focus();
|
btnCancel.Focus();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
@@ -39,7 +40,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||||
menuClose.Click += MenuClose_Click;
|
menuClose.Click += MenuClose_Click;
|
||||||
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.SendSnackMsg.ToString()).Subscribe(DelegateSnackMsg);
|
|
||||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel));
|
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel));
|
||||||
|
|
||||||
@@ -136,6 +136,24 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(disposables);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppEvents.SendSnackMsgRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async content => await DelegateSnackMsg(content))
|
||||||
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.AppExitRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => StorageUI())
|
||||||
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.ShutdownRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(content => Shutdown(content))
|
||||||
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Utils.IsWindows())
|
if (Utils.IsWindows())
|
||||||
@@ -156,7 +174,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
menuAddServerViaScan.IsVisible = false;
|
menuAddServerViaScan.IsVisible = false;
|
||||||
|
|
||||||
AddHelpMenuItem();
|
AddHelpMenuItem();
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Event
|
#region Event
|
||||||
@@ -168,11 +185,9 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
DispatcherPriority.Default);
|
DispatcherPriority.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DelegateSnackMsg(string content)
|
private async Task DelegateSnackMsg(string content)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
_manager?.Show(new Notification(null, content, NotificationType.Information));
|
||||||
_manager?.Show(new Notification(null, content, NotificationType.Information)),
|
|
||||||
DispatcherPriority.Normal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
@@ -213,33 +228,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
DispatcherPriority.Default);
|
DispatcherPriority.Default);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.DispatcherStatistics:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.SetStatisticsResult((ServerSpeedItem)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherReload:
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.ReloadResult(),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.Shutdown:
|
|
||||||
if (obj != null && _blCloseByUser == false)
|
|
||||||
{
|
|
||||||
_blCloseByUser = (bool)obj;
|
|
||||||
}
|
|
||||||
StorageUI();
|
|
||||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
||||||
{
|
|
||||||
HotkeyManager.Instance.Dispose();
|
|
||||||
desktop.Shutdown();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.ScanScreenTask:
|
case EViewAction.ScanScreenTask:
|
||||||
await ScanScreenTaskAsync();
|
await ScanScreenTaskAsync();
|
||||||
break;
|
break;
|
||||||
@@ -255,12 +243,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
await ViewModel.AddServerViaClipboardAsync(clipboardData);
|
await ViewModel.AddServerViaClipboardAsync(clipboardData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.AdjustMainLvColWidth:
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
Locator.Current.GetService<ProfilesViewModel>()?.AutofitColumnWidthAsync(),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
@@ -300,10 +282,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case WindowCloseReason.ApplicationShutdown or WindowCloseReason.OSShutdown:
|
case WindowCloseReason.ApplicationShutdown or WindowCloseReason.OSShutdown:
|
||||||
if (ViewModel != null)
|
await AppManager.Instance.AppExitAsync(false);
|
||||||
{
|
|
||||||
await ViewModel.MyAppExitAsync(true);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,9 +377,21 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
|
|
||||||
_blCloseByUser = true;
|
_blCloseByUser = true;
|
||||||
StorageUI();
|
StorageUI();
|
||||||
if (ViewModel != null)
|
|
||||||
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Shutdown(bool obj)
|
||||||
|
{
|
||||||
|
if (obj is bool b && _blCloseByUser == false)
|
||||||
{
|
{
|
||||||
await ViewModel.MyAppExitAsync(false);
|
_blCloseByUser = b;
|
||||||
|
}
|
||||||
|
StorageUI();
|
||||||
|
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
HotkeyManager.Instance.Dispose();
|
||||||
|
desktop.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +453,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StorageUI(string? n = null)
|
private void StorageUI()
|
||||||
{
|
{
|
||||||
ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
|
ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
|
||||||
|
|
||||||
|
|||||||
@@ -24,14 +24,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
|||||||
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
|
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
btnCancel.Focus();
|
btnCancel.Focus();
|
||||||
|
|||||||
136
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml
Normal file
136
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="v2rayN.Desktop.Views.ProfilesSelectWindow"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||||
|
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||||
|
Title="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Width="800"
|
||||||
|
Height="450"
|
||||||
|
x:DataType="vms:ProfilesSelectViewModel"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<DockPanel Margin="8">
|
||||||
|
<!-- Bottom buttons -->
|
||||||
|
<StackPanel
|
||||||
|
Margin="4"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
DockPanel.Dock="Bottom"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnSave"
|
||||||
|
Width="100"
|
||||||
|
Click="BtnSave_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbConfirm}" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnCancel"
|
||||||
|
Width="100"
|
||||||
|
Margin="8,0"
|
||||||
|
Content="{x:Static resx:ResUI.TbCancel}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<DockPanel>
|
||||||
|
<!-- Top tools -->
|
||||||
|
<WrapPanel Margin="4" DockPanel.Dock="Top">
|
||||||
|
<ListBox
|
||||||
|
x:Name="lstGroup"
|
||||||
|
Margin="4,0"
|
||||||
|
DisplayMemberBinding="{Binding Remarks}"
|
||||||
|
ItemsSource="{Binding SubItems}">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="btnAutofitColumnWidth"
|
||||||
|
Width="32"
|
||||||
|
Height="32"
|
||||||
|
Margin="8,0"
|
||||||
|
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
|
||||||
|
<Button.Content>
|
||||||
|
<PathIcon Data="{StaticResource building_fit}" />
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtServerFilter"
|
||||||
|
Width="200"
|
||||||
|
Margin="8,0"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Text="{Binding ServerFilter, Mode=TwoWay}"
|
||||||
|
Watermark="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<!-- Profiles grid -->
|
||||||
|
<DataGrid
|
||||||
|
x:Name="lstProfiles"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
BorderThickness="1"
|
||||||
|
CanUserReorderColumns="True"
|
||||||
|
CanUserResizeColumns="True"
|
||||||
|
GridLinesVisibility="All"
|
||||||
|
HeadersVisibility="All"
|
||||||
|
IsReadOnly="True"
|
||||||
|
ItemsSource="{Binding ProfileItems}"
|
||||||
|
SelectionMode="Single">
|
||||||
|
<DataGrid.KeyBindings>
|
||||||
|
<KeyBinding Command="{Binding SelectFinish}" Gesture="Enter" />
|
||||||
|
</DataGrid.KeyBindings>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="80"
|
||||||
|
Binding="{Binding ConfigType}"
|
||||||
|
Header="{x:Static resx:ResUI.LvServiceType}"
|
||||||
|
Tag="ConfigType" />
|
||||||
|
|
||||||
|
<DataGridTemplateColumn SortMemberPath="Remarks" Tag="Remarks">
|
||||||
|
<DataGridTemplateColumn.Header>
|
||||||
|
<TextBlock Text="{x:Static resx:ResUI.LvRemarks}" />
|
||||||
|
</DataGridTemplateColumn.Header>
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="8,0" Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Remarks}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="120"
|
||||||
|
Binding="{Binding Address}"
|
||||||
|
Header="{x:Static resx:ResUI.LvAddress}"
|
||||||
|
Tag="Address" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="60"
|
||||||
|
Binding="{Binding Port}"
|
||||||
|
Header="{x:Static resx:ResUI.LvPort}"
|
||||||
|
Tag="Port" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding Network}"
|
||||||
|
Header="{x:Static resx:ResUI.LvTransportProtocol}"
|
||||||
|
Tag="Network" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding StreamSecurity}"
|
||||||
|
Header="{x:Static resx:ResUI.LvTLS}"
|
||||||
|
Tag="StreamSecurity" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding SubRemarks}"
|
||||||
|
Header="{x:Static resx:ResUI.LvSubscription}"
|
||||||
|
Tag="SubRemarks" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</Window>
|
||||||
195
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs
Normal file
195
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
using System.Reactive.Disposables;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ServiceLib.Manager;
|
||||||
|
|
||||||
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
|
public partial class ProfilesSelectWindow : ReactiveWindow<ProfilesSelectViewModel>
|
||||||
|
{
|
||||||
|
private static Config _config;
|
||||||
|
|
||||||
|
public Task<ProfileItem?> ProfileItem => GetProfileItem();
|
||||||
|
public Task<List<ProfileItem>?> ProfileItems => GetProfileItems();
|
||||||
|
private bool _allowMultiSelect = false;
|
||||||
|
|
||||||
|
public ProfilesSelectWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_config = AppManager.Instance.Config;
|
||||||
|
|
||||||
|
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
||||||
|
txtServerFilter.KeyDown += TxtServerFilter_KeyDown;
|
||||||
|
lstProfiles.KeyDown += LstProfiles_KeyDown;
|
||||||
|
lstProfiles.SelectionChanged += LstProfiles_SelectionChanged;
|
||||||
|
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
|
||||||
|
lstProfiles.Sorting += LstProfiles_Sorting;
|
||||||
|
lstProfiles.DoubleTapped += LstProfiles_DoubleTapped;
|
||||||
|
|
||||||
|
ViewModel = new ProfilesSelectViewModel(UpdateViewHandler);
|
||||||
|
DataContext = ViewModel;
|
||||||
|
|
||||||
|
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.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCancel.Click += (s, e) => Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AllowMultiSelect(bool allow)
|
||||||
|
{
|
||||||
|
_allowMultiSelect = allow;
|
||||||
|
if (allow)
|
||||||
|
{
|
||||||
|
lstProfiles.SelectionMode = DataGridSelectionMode.Extended;
|
||||||
|
lstProfiles.SelectedItems.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lstProfiles.SelectionMode = DataGridSelectionMode.Single;
|
||||||
|
if (lstProfiles.SelectedItems.Count > 0)
|
||||||
|
{
|
||||||
|
var first = lstProfiles.SelectedItems[0];
|
||||||
|
lstProfiles.SelectedItems.Clear();
|
||||||
|
lstProfiles.SelectedItem = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose ConfigType filter controls to callers
|
||||||
|
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
|
||||||
|
=> ViewModel?.SetConfigTypeFilter(types, exclude);
|
||||||
|
|
||||||
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case EViewAction.CloseWindow:
|
||||||
|
Close(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_SelectionChanged(object? sender, 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.Index + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_DoubleTapped(object? sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
// 忽略表头区域的双击
|
||||||
|
if (e.Source is Control src)
|
||||||
|
{
|
||||||
|
if (src.FindAncestorOfType<DataGridColumnHeader>() != null)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅当在数据行或其子元素上双击时才触发选择
|
||||||
|
if (src.FindAncestorOfType<DataGridRow>() != null)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
||||||
|
{
|
||||||
|
// 自定义排序,防止默认行为导致误触发
|
||||||
|
e.Handled = true;
|
||||||
|
if (ViewModel != null && e.Column?.Tag?.ToString() != null)
|
||||||
|
{
|
||||||
|
ViewModel.SortServer(e.Column.Tag.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_KeyDown(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.KeyModifiers is KeyModifiers.Control or KeyModifiers.Meta)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.A)
|
||||||
|
{
|
||||||
|
if (_allowMultiSelect)
|
||||||
|
{
|
||||||
|
lstProfiles.SelectAll();
|
||||||
|
}
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AutofitColumnWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutofitColumnWidth()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var col in lstProfiles.Columns)
|
||||||
|
{
|
||||||
|
col.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TxtServerFilter_KeyDown(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.RefreshServers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProfileItem?> GetProfileItem()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItem();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ProfileItem>?> GetProfileItems()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItems();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Trigger selection finalize when Confirm is clicked
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
@@ -96,11 +97,22 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.AppExitRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => StorageUI())
|
||||||
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.AdjustMainLvColWidthRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => AutofitColumnWidth())
|
||||||
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
RestoreUI();
|
RestoreUI();
|
||||||
ViewModel?.RefreshServers();
|
ViewModel?.RefreshServers();
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
||||||
@@ -127,13 +139,6 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
await AvaUtils.SetClipboardData(this, (string)obj);
|
await AvaUtils.SetClipboardData(this, (string)obj);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.AdjustMainLvColWidth:
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
AutofitColumnWidth(),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.ProfilesFocus:
|
case EViewAction.ProfilesFocus:
|
||||||
lstProfiles.Focus();
|
lstProfiles.Focus();
|
||||||
break;
|
break;
|
||||||
@@ -177,21 +182,8 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
return false;
|
return false;
|
||||||
return await new SubEditWindow((SubItem)obj).ShowDialog<bool>(_window);
|
return await new SubEditWindow((SubItem)obj).ShowDialog<bool>(_window);
|
||||||
|
|
||||||
case EViewAction.DispatcherSpeedTest:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.SetSpeedTestResult((SpeedTestResult)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherRefreshServersBiz:
|
case EViewAction.DispatcherRefreshServersBiz:
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(RefreshServersBiz, DispatcherPriority.Default);
|
||||||
{
|
|
||||||
_ = RefreshServersBiz();
|
|
||||||
},
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,13 +201,8 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
await DialogHost.Show(dialog);
|
await DialogHost.Show(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshServersBiz()
|
public void RefreshServersBiz()
|
||||||
{
|
{
|
||||||
if (ViewModel != null)
|
|
||||||
{
|
|
||||||
await ViewModel.RefreshServersBiz();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lstProfiles.SelectedIndex >= 0)
|
if (lstProfiles.SelectedIndex >= 0)
|
||||||
{
|
{
|
||||||
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
|
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
|
||||||
@@ -421,7 +408,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StorageUI(string? n = null)
|
private void StorageUI()
|
||||||
{
|
{
|
||||||
List<ColumnItem> lvColumnItem = new();
|
List<ColumnItem> lvColumnItem = new();
|
||||||
foreach (var item2 in lstProfiles.Columns)
|
foreach (var item2 in lstProfiles.Columns)
|
||||||
|
|||||||
@@ -54,13 +54,22 @@
|
|||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding SelectedSource.OutboundTag, Mode=TwoWay}" />
|
Text="{Binding SelectedSource.OutboundTag, Mode=TwoWay}" />
|
||||||
<TextBlock
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
Orientation="Horizontal"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center">
|
||||||
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
<Button
|
||||||
|
x:Name="btnSelectProfile"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Click="BtnSelectProfile_Click" />
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
@@ -167,14 +176,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -93,4 +93,19 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
|
|||||||
{
|
{
|
||||||
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
|
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectProfile_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||||
|
if (result == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
cmbOutboundTag.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,22 +27,20 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
DockPanel.Dock="Bottom"
|
DockPanel.Dock="Bottom"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
@@ -54,8 +52,8 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.LvRemarks}" />
|
Text="{x:Static resx:ResUI.LvRemarks}" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -67,27 +65,27 @@
|
|||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="{StaticResource Margin4}"
|
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.LvSort}" />
|
Text="{x:Static resx:ResUI.LvSort}" />
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtSort"
|
x:Name="txtSort"
|
||||||
Width="100"
|
Width="100"
|
||||||
HorizontalAlignment="Left"
|
Margin="{StaticResource Margin4}"
|
||||||
Margin="{StaticResource Margin4}" />
|
HorizontalAlignment="Left" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbdomainStrategy}" />
|
Text="{x:Static resx:ResUI.TbdomainStrategy}" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@@ -98,8 +96,8 @@
|
|||||||
Width="200"
|
Width="200"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
|
Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbdomainStrategy4Singbox"
|
x:Name="cmbdomainStrategy4Singbox"
|
||||||
@@ -110,17 +108,17 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.LvUrl}" />
|
Text="{x:Static resx:ResUI.LvUrl}" />
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtUrl"
|
x:Name="txtUrl"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="600"
|
Width="600"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="{StaticResource Margin4}"
|
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -150,8 +148,8 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
Margin="{StaticResource Margin4}"
|
||||||
Margin="{StaticResource Margin4}">
|
VerticalAlignment="Center">
|
||||||
<HyperlinkButton Classes="WithIcon" Click="linkCustomRulesetPath4Singbox">
|
<HyperlinkButton Classes="WithIcon" Click="linkCustomRulesetPath4Singbox">
|
||||||
<TextBlock Text="{x:Static resx:ResUI.LvCustomRulesetPath4Singbox}" />
|
<TextBlock Text="{x:Static resx:ResUI.LvCustomRulesetPath4Singbox}" />
|
||||||
</HyperlinkButton>
|
</HyperlinkButton>
|
||||||
@@ -161,9 +159,9 @@
|
|||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="600"
|
Width="600"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="{StaticResource Margin4}"
|
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnBrowseCustomRulesetPath4Singbox"
|
x:Name="btnBrowseCustomRulesetPath4Singbox"
|
||||||
|
|||||||
@@ -35,14 +35,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
@@ -132,10 +130,6 @@
|
|||||||
Width="*"
|
Width="*"
|
||||||
Binding="{Binding Url}"
|
Binding="{Binding Url}"
|
||||||
Header="{x:Static resx:ResUI.LvUrl}" />
|
Header="{x:Static resx:ResUI.LvUrl}" />
|
||||||
<DataGridTextColumn
|
|
||||||
Width="300"
|
|
||||||
Binding="{Binding CustomIcon}"
|
|
||||||
Header="{x:Static resx:ResUI.LvCustomIcon}" />
|
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
btnCancel.Focus();
|
btnCancel.Focus();
|
||||||
|
|||||||
@@ -56,20 +56,6 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
|
|||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case EViewAction.DispatcherServerAvailability:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.TestServerAvailabilityResult((string)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherRefreshServersBiz:
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ViewModel?.RefreshServersBiz(),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherRefreshIcon:
|
case EViewAction.DispatcherRefreshIcon:
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,14 +22,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||||
@@ -206,6 +204,12 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Click="BtnSelectPrevProfile_Click" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="10"
|
Grid.Row="10"
|
||||||
@@ -220,6 +224,12 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Click="BtnSelectNextProfile_Click" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="11"
|
Grid.Row="11"
|
||||||
|
|||||||
@@ -59,4 +59,34 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
|||||||
{
|
{
|
||||||
txtRemarks.Focus();
|
txtRemarks.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||||
|
if (result == 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();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||||
|
if (result == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
txtNextProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
|
|||||||
menuClose_Click(null, null);
|
menuClose_Click(null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
lstSubscription.Focus();
|
lstSubscription.Focus();
|
||||||
|
|||||||
@@ -24,14 +24,12 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True" />
|
IsDefault="True" />
|
||||||
<Button
|
<Button
|
||||||
x:Name="btnCancel"
|
x:Name="btnCancel"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="True" />
|
IsCancel="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -43,7 +42,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace v2rayN.Views;
|
namespace v2rayN.Views;
|
||||||
@@ -24,27 +22,6 @@ public partial class CheckUpdateView
|
|||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
{
|
{
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case EViewAction.DispatcherCheckUpdate:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.UpdateViewResult((CheckUpdateModel)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherCheckUpdateFinished:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.UpdateFinishedResult((bool)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Threading;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace v2rayN.Views;
|
namespace v2rayN.Views;
|
||||||
@@ -33,18 +32,6 @@ public partial class ClashConnectionsView
|
|||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
{
|
{
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case EViewAction.DispatcherRefreshConnections:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.RefreshConnections((List<ConnectionItem>?)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Splat;
|
using Splat;
|
||||||
|
|
||||||
@@ -41,26 +39,6 @@ public partial class ClashProxiesView
|
|||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
{
|
{
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case EViewAction.DispatcherRefreshProxyGroups:
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.RefreshProxyGroups();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherProxiesDelayTest:
|
|
||||||
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.ProxiesDelayTestResult((SpeedTestResult)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -82,7 +80,7 @@
|
|||||||
x:Name="cmbDirectDNS"
|
x:Name="cmbDirectDNS"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
Style="{StaticResource DefComboBox}" />
|
Style="{StaticResource DefComboBox}" />
|
||||||
@@ -98,7 +96,7 @@
|
|||||||
x:Name="cmbRemoteDNS"
|
x:Name="cmbRemoteDNS"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
Style="{StaticResource DefComboBox}" />
|
Style="{StaticResource DefComboBox}" />
|
||||||
@@ -114,7 +112,7 @@
|
|||||||
x:Name="cmbSBResolverDNS"
|
x:Name="cmbSBResolverDNS"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
Style="{StaticResource DefComboBox}" />
|
Style="{StaticResource DefComboBox}" />
|
||||||
@@ -138,7 +136,7 @@
|
|||||||
x:Name="cmbSBFinalResolverDNS"
|
x:Name="cmbSBFinalResolverDNS"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="300"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
Style="{StaticResource DefComboBox}" />
|
Style="{StaticResource DefComboBox}" />
|
||||||
@@ -396,7 +394,6 @@
|
|||||||
x:Name="btnImportDefConfig4V2rayCompatible"
|
x:Name="btnImportDefConfig4V2rayCompatible"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
|
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
|
||||||
Cursor="Hand"
|
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -489,7 +486,6 @@
|
|||||||
x:Name="btnImportDefConfig4SingboxCompatible"
|
x:Name="btnImportDefConfig4SingboxCompatible"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
|
Content="{x:Static resx:ResUI.TbSettingDnsImportDefConfig}"
|
||||||
Cursor="Hand"
|
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -41,7 +40,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
@@ -36,7 +37,6 @@ public partial class MainWindow
|
|||||||
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
||||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||||
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.SendSnackMsg.ToString()).Subscribe(DelegateSnackMsg);
|
|
||||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel));
|
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel));
|
||||||
|
|
||||||
@@ -133,6 +133,24 @@ public partial class MainWindow
|
|||||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(disposables);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppEvents.SendSnackMsgRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async content => await DelegateSnackMsg(content))
|
||||||
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.AppExitRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => StorageUI())
|
||||||
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.ShutdownRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(content => Shutdown(content))
|
||||||
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
||||||
@@ -144,7 +162,6 @@ public partial class MainWindow
|
|||||||
|
|
||||||
AddHelpMenuItem();
|
AddHelpMenuItem();
|
||||||
WindowsManager.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
|
WindowsManager.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Event
|
#region Event
|
||||||
@@ -157,12 +174,9 @@ public partial class MainWindow
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DelegateSnackMsg(string content)
|
private async Task DelegateSnackMsg(string content)
|
||||||
{
|
{
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
MainSnackbar.MessageQueue?.Enqueue(content);
|
||||||
{
|
|
||||||
MainSnackbar.MessageQueue?.Enqueue(content);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
@@ -204,29 +218,6 @@ public partial class MainWindow
|
|||||||
}), DispatcherPriority.Normal);
|
}), DispatcherPriority.Normal);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.DispatcherStatistics:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.SetStatisticsResult((ServerSpeedItem)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherReload:
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.ReloadResult();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.Shutdown:
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
Application.Current.Shutdown();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.ScanScreenTask:
|
case EViewAction.ScanScreenTask:
|
||||||
await ScanScreenTaskAsync();
|
await ScanScreenTaskAsync();
|
||||||
break;
|
break;
|
||||||
@@ -242,13 +233,6 @@ public partial class MainWindow
|
|||||||
ViewModel?.AddServerViaClipboardAsync(clipboardData);
|
ViewModel?.AddServerViaClipboardAsync(clipboardData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.AdjustMainLvColWidth:
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
Locator.Current.GetService<ProfilesViewModel>()?.AutofitColumnWidthAsync();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
@@ -281,7 +265,12 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
Logging.SaveLog("Current_SessionEnding");
|
Logging.SaveLog("Current_SessionEnding");
|
||||||
StorageUI();
|
StorageUI();
|
||||||
await ViewModel?.MyAppExitAsync(true);
|
await AppManager.Instance.AppExitAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Shutdown(bool obj)
|
||||||
|
{
|
||||||
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
|
private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||||
@@ -423,7 +412,7 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StorageUI(string? n = null)
|
private void StorageUI()
|
||||||
{
|
{
|
||||||
ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
|
ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
156
v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml
Normal file
156
v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<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: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="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Width="800"
|
||||||
|
Height="450"
|
||||||
|
x:TypeArguments="vms:ProfilesSelectViewModel"
|
||||||
|
Style="{StaticResource WindowGlobal}"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<DockPanel Margin="{StaticResource Margin8}">
|
||||||
|
<StackPanel
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
DockPanel.Dock="Bottom"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnSave"
|
||||||
|
Width="100"
|
||||||
|
Click="BtnSave_Click"
|
||||||
|
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"
|
||||||
|
SelectionMode="Single"
|
||||||
|
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}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</base:WindowBase>
|
||||||
194
v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs
Normal file
194
v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ServiceLib.Manager;
|
||||||
|
using v2rayN.Base;
|
||||||
|
|
||||||
|
namespace v2rayN.Views;
|
||||||
|
|
||||||
|
public partial class ProfilesSelectWindow
|
||||||
|
{
|
||||||
|
private static Config _config;
|
||||||
|
|
||||||
|
public Task<ProfileItem?> ProfileItem => GetProfileItem();
|
||||||
|
public Task<List<ProfileItem>?> ProfileItems => GetProfileItems();
|
||||||
|
private bool _allowMultiSelect = false;
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AllowMultiSelect(bool allow)
|
||||||
|
{
|
||||||
|
_allowMultiSelect = allow;
|
||||||
|
if (allow)
|
||||||
|
{
|
||||||
|
lstProfiles.SelectionMode = DataGridSelectionMode.Extended;
|
||||||
|
lstProfiles.SelectedItems.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lstProfiles.SelectionMode = DataGridSelectionMode.Single;
|
||||||
|
if (lstProfiles.SelectedItems.Count > 0)
|
||||||
|
{
|
||||||
|
var first = lstProfiles.SelectedItems[0];
|
||||||
|
lstProfiles.SelectedItems.Clear();
|
||||||
|
lstProfiles.SelectedItem = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose ConfigType filter controls to callers
|
||||||
|
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
|
||||||
|
=> ViewModel?.SetConfigTypeFilter(types, exclude);
|
||||||
|
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
if (!_allowMultiSelect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProfileItem?> GetProfileItem()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItem();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ProfileItem>?> GetProfileItems()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItems();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Trigger selection finalize when Confirm is clicked
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Event
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
@@ -28,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;
|
||||||
|
|
||||||
@@ -90,11 +91,22 @@ public partial class ProfilesView
|
|||||||
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.AppExitRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => StorageUI())
|
||||||
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.AdjustMainLvColWidthRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => AutofitColumnWidth())
|
||||||
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
RestoreUI();
|
RestoreUI();
|
||||||
ViewModel?.RefreshServers();
|
ViewModel?.RefreshServers();
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Event
|
#region Event
|
||||||
@@ -109,13 +121,6 @@ public partial class ProfilesView
|
|||||||
WindowsUtils.SetClipboardData((string)obj);
|
WindowsUtils.SetClipboardData((string)obj);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.AdjustMainLvColWidth:
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
AutofitColumnWidth();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.ProfilesFocus:
|
case EViewAction.ProfilesFocus:
|
||||||
lstProfiles.Focus();
|
lstProfiles.Focus();
|
||||||
break;
|
break;
|
||||||
@@ -158,20 +163,8 @@ public partial class ProfilesView
|
|||||||
return false;
|
return false;
|
||||||
return (new SubEditWindow((SubItem)obj)).ShowDialog() ?? false;
|
return (new SubEditWindow((SubItem)obj)).ShowDialog() ?? false;
|
||||||
|
|
||||||
case EViewAction.DispatcherSpeedTest:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.SetSpeedTestResult((SpeedTestResult)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherRefreshServersBiz:
|
case EViewAction.DispatcherRefreshServersBiz:
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
Application.Current?.Dispatcher.Invoke(RefreshServersBiz, DispatcherPriority.Normal);
|
||||||
{
|
|
||||||
_ = RefreshServersBiz();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,20 +183,15 @@ public partial class ProfilesView
|
|||||||
await DialogHost.Show(dialog, "RootDialog");
|
await DialogHost.Show(dialog, "RootDialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshServersBiz()
|
public void RefreshServersBiz()
|
||||||
{
|
{
|
||||||
if (ViewModel != null)
|
|
||||||
{
|
|
||||||
await ViewModel.RefreshServersBiz();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lstProfiles.SelectedIndex > 0)
|
if (lstProfiles.SelectedIndex > 0)
|
||||||
{
|
{
|
||||||
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
|
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -377,7 +365,7 @@ public partial class ProfilesView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StorageUI(string? n = null)
|
private void StorageUI()
|
||||||
{
|
{
|
||||||
List<ColumnItem> lvColumnItem = new();
|
List<ColumnItem> lvColumnItem = new();
|
||||||
foreach (var t in lstProfiles.Columns)
|
foreach (var t in lstProfiles.Columns)
|
||||||
|
|||||||
@@ -72,14 +72,24 @@
|
|||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
MaxDropDownHeight="1000"
|
MaxDropDownHeight="1000"
|
||||||
Style="{StaticResource DefComboBox}" />
|
Style="{StaticResource DefComboBox}" />
|
||||||
<TextBlock
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Orientation="Horizontal">
|
||||||
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
<Button
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Click="BtnSelectProfile_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
@@ -198,7 +208,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -206,7 +215,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -88,4 +88,18 @@ public partial class RoutingRuleDetailsWindow
|
|||||||
{
|
{
|
||||||
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
|
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectProfile_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
if (selectWindow.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
cmbOutboundTag.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -84,7 +83,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
x:Name="btnSave"
|
x:Name="btnSave"
|
||||||
Width="100"
|
Width="100"
|
||||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||||
Cursor="Hand"
|
|
||||||
IsDefault="True"
|
IsDefault="True"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
<Button
|
<Button
|
||||||
@@ -69,7 +68,6 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Content="{x:Static resx:ResUI.TbCancel}"
|
Content="{x:Static resx:ResUI.TbCancel}"
|
||||||
Cursor="Hand"
|
|
||||||
IsCancel="true"
|
IsCancel="true"
|
||||||
Style="{StaticResource DefButton}" />
|
Style="{StaticResource DefButton}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user