Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54c67d6f1 | ||
|
|
b49486cc23 | ||
|
|
b95830b3d5 | ||
|
|
8e0c5cb9aa | ||
|
|
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 |
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, xdg-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,11 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ===== Require Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ====
|
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
|
||||||
if [[ -r /etc/os-release ]]; then
|
if [[ -r /etc/os-release ]]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
case "$ID" in
|
case "$ID" in
|
||||||
rhel|rocky|almalinux|centos|ubuntu|debian)
|
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
|
||||||
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -390,25 +390,30 @@ download_mihomo() {
|
|||||||
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Move geo files to a unified path: outroot/bin/xray/
|
# Move geo files to a unified path: outroot/bin
|
||||||
unify_geo_layout() {
|
unify_geo_layout() {
|
||||||
local outroot="$1"
|
local outroot="$1"
|
||||||
mkdir -p "$outroot/bin/xray"
|
mkdir -p "$outroot/bin"
|
||||||
local srcs=( \
|
local names=( \
|
||||||
"$outroot/bin/geosite.dat" \
|
"geosite.dat" \
|
||||||
"$outroot/bin/geoip.dat" \
|
"geoip.dat" \
|
||||||
"$outroot/bin/geoip-only-cn-private.dat" \
|
"geoip-only-cn-private.dat" \
|
||||||
"$outroot/bin/Country.mmdb" \
|
"Country.mmdb" \
|
||||||
"$outroot/bin/geoip.metadb" \
|
"geoip.metadb" \
|
||||||
)
|
)
|
||||||
for s in "${srcs[@]}"; do
|
for n in "${names[@]}"; do
|
||||||
if [[ -f "$s" ]]; then
|
# If file exists under bin/xray/, move it up to bin/
|
||||||
mv -f "$s" "$outroot/bin/xray/$(basename "$s")"
|
if [[ -f "$outroot/bin/xray/$n" ]]; then
|
||||||
|
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
|
||||||
|
fi
|
||||||
|
# If file already in bin/, leave it as-is
|
||||||
|
if [[ -f "$outroot/bin/$n" ]]; then
|
||||||
|
:
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download geo/rule assets; then unify to bin/xray/
|
# Download geo/rule assets; then unify to bin/
|
||||||
download_geo_assets() {
|
download_geo_assets() {
|
||||||
local outroot="$1"
|
local outroot="$1"
|
||||||
local bin_dir="$outroot/bin"
|
local bin_dir="$outroot/bin"
|
||||||
@@ -442,7 +447,7 @@ download_geo_assets() {
|
|||||||
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Unify to bin/xray/
|
# Unify to bin/
|
||||||
unify_geo_layout "$outroot"
|
unify_geo_layout "$outroot"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +485,7 @@ download_v2rayn_bundle() {
|
|||||||
rm -rf "$nested_dir"
|
rm -rf "$nested_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Unify to bin/xray/
|
# Unify to bin/
|
||||||
unify_geo_layout "$outroot"
|
unify_geo_layout "$outroot"
|
||||||
|
|
||||||
echo "[+] Bundle extracted to $outroot"
|
echo "[+] Bundle extracted to $outroot"
|
||||||
@@ -610,7 +615,7 @@ Source0: __PKGROOT__.tar.gz
|
|||||||
|
|
||||||
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
||||||
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
||||||
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL
|
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
|
||||||
|
|
||||||
%description
|
%description
|
||||||
v2rayN Linux for Red Hat Enterprise Linux
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
@@ -629,25 +634,13 @@ https://github.com/2dust/v2rayN
|
|||||||
install -dm0755 %{buildroot}/opt/v2rayN
|
install -dm0755 %{buildroot}/opt/v2rayN
|
||||||
cp -a * %{buildroot}/opt/v2rayN/
|
cp -a * %{buildroot}/opt/v2rayN/
|
||||||
|
|
||||||
# Launcher (prefer native ELF first, then DLL fallback; also create Geo symlinks for the user)
|
# Launcher (prefer native ELF first, then DLL fallback)
|
||||||
install -dm0755 %{buildroot}%{_bindir}
|
install -dm0755 %{buildroot}%{_bindir}
|
||||||
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
DIR="/opt/v2rayN"
|
DIR="/opt/v2rayN"
|
||||||
|
|
||||||
# --- Symlink GEO files into user's XDG dir (first-run convenience) ---
|
|
||||||
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
||||||
USR_GEO_DIR="$XDG_DATA_HOME/v2rayN/bin"
|
|
||||||
SYS_XRAY_DIR="$DIR/bin/xray"
|
|
||||||
mkdir -p "$USR_GEO_DIR"
|
|
||||||
for f in geosite.dat geoip.dat geoip-only-cn-private.dat Country.mmdb; do
|
|
||||||
if [[ -f "$SYS_XRAY_DIR/$f" && ! -e "$USR_GEO_DIR/$f" ]]; then
|
|
||||||
ln -s "$SYS_XRAY_DIR/$f" "$USR_GEO_DIR/$f" || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
# --- end GEO ---
|
|
||||||
|
|
||||||
# Prefer native apphost
|
# Prefer native apphost
|
||||||
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
||||||
|
|
||||||
|
|||||||
@@ -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.4</Version>
|
<Version>7.14.9</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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
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,23 +185,23 @@ 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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}");
|
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);
|
||||||
@@ -208,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -125,15 +125,15 @@ public sealed class CoreInfoManager
|
|||||||
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",
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ 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;
|
||||||
@@ -63,7 +63,7 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
UpdateFunc(false, ResUI.CheckServerSettings);
|
await UpdateFunc(false, ResUI.CheckServerSettings);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,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);
|
||||||
|
|
||||||
@@ -91,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()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,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);
|
||||||
@@ -216,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
|
||||||
@@ -230,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
9
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -3030,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>
|
||||||
|
|||||||
@@ -1512,4 +1512,7 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1512,4 +1512,7 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1512,4 +1512,7 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1512,4 +1512,7 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1509,4 +1509,7 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>开始解析和处理订阅内容</value>
|
<value>开始解析和处理订阅内容</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>选择配置文件</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1509,4 +1509,7 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>開始解析和處理訂閱內容</value>
|
<value>開始解析和處理訂閱內容</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -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;
|
||||||
@@ -234,6 +235,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||||
}
|
}
|
||||||
|
await RefreshServers();
|
||||||
|
|
||||||
BlReloadEnabled = true;
|
BlReloadEnabled = true;
|
||||||
await Reload();
|
await Reload();
|
||||||
@@ -245,7 +247,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 +256,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 +288,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 +328,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 +343,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 +393,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
|
||||||
@@ -526,13 +470,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()
|
||||||
@@ -544,7 +488,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else if (Utils.IsLinux())
|
else if (Utils.IsLinux())
|
||||||
{
|
{
|
||||||
ProcUtils.ProcessStart("nautilus", path);
|
ProcUtils.ProcessStart("xdg-open", path);
|
||||||
}
|
}
|
||||||
else if (Utils.IsOSX())
|
else if (Utils.IsOSX())
|
||||||
{
|
{
|
||||||
@@ -576,7 +520,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 +530,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 +540,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 +577,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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,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}" />
|
||||||
|
|
||||||
@@ -73,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}" />
|
||||||
|
|
||||||
@@ -87,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
|
||||||
@@ -108,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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
await ViewModel.MyAppExitAsync(false);
|
if (obj is bool b && _blCloseByUser == 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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
128
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml
Normal file
128
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<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="{StaticResource MarginLr4}"
|
||||||
|
DisplayMemberBinding="{Binding Remarks}"
|
||||||
|
ItemsSource="{Binding SubItems}"
|
||||||
|
Theme="{DynamicResource ButtonRadioGroupListBox}">
|
||||||
|
<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" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="120"
|
||||||
|
Binding="{Binding Remarks}"
|
||||||
|
Header="{x:Static resx:ResUI.LvRemarks}"
|
||||||
|
Tag="Remarks" />
|
||||||
|
<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,21 @@ 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();
|
|
||||||
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 +138,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 +181,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 +200,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 +407,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">
|
||||||
|
<Button
|
||||||
|
x:Name="btnSelectProfile"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Click="BtnSelectProfile_Click" />
|
||||||
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,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(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -204,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"
|
||||||
@@ -218,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();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,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}" />
|
||||||
@@ -96,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}" />
|
||||||
@@ -112,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}" />
|
||||||
@@ -136,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}" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
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,21 @@ 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();
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Event
|
#region Event
|
||||||
@@ -109,13 +120,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 +162,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 +182,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 +364,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"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<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}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,22 +77,6 @@ public partial class StatusBarView
|
|||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case EViewAction.DispatcherServerAvailability:
|
|
||||||
if (obj is null)
|
|
||||||
return false;
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.TestServerAvailabilityResult((string)obj);
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherRefreshServersBiz:
|
|
||||||
Application.Current?.Dispatcher.Invoke((() =>
|
|
||||||
{
|
|
||||||
ViewModel?.RefreshServersBiz();
|
|
||||||
}), DispatcherPriority.Normal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.DispatcherRefreshIcon:
|
case EViewAction.DispatcherRefreshIcon:
|
||||||
Application.Current?.Dispatcher.Invoke((async () =>
|
Application.Current?.Dispatcher.Invoke((async () =>
|
||||||
{
|
{
|
||||||
@@ -113,9 +97,7 @@ public partial class StatusBarView
|
|||||||
private async void menuExit_Click(object sender, RoutedEventArgs e)
|
private async void menuExit_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
tbNotify.Dispose();
|
tbNotify.Dispose();
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
if (service != null)
|
|
||||||
await service.MyAppExitAsync(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void txtRunningInfoDisplay_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
private void txtRunningInfoDisplay_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
|
|||||||
@@ -259,6 +259,14 @@
|
|||||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||||
AcceptsReturn="True"
|
AcceptsReturn="True"
|
||||||
Style="{StaticResource MyOutlinedTextBox}" />
|
Style="{StaticResource MyOutlinedTextBox}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Click="BtnSelectPrevProfile_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="10"
|
Grid.Row="10"
|
||||||
@@ -276,6 +284,14 @@
|
|||||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||||
AcceptsReturn="True"
|
AcceptsReturn="True"
|
||||||
Style="{StaticResource MyOutlinedTextBox}" />
|
Style="{StaticResource MyOutlinedTextBox}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Click="BtnSelectNextProfile_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="11"
|
Grid.Row="11"
|
||||||
|
|||||||
@@ -54,4 +54,32 @@ public partial class SubEditWindow
|
|||||||
{
|
{
|
||||||
txtRemarks.Focus();
|
txtRemarks.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectPrevProfile_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)
|
||||||
|
{
|
||||||
|
txtPrevProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectNextProfile_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)
|
||||||
|
{
|
||||||
|
txtNextProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user