mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-15 02:20:19 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccb0ffb3b6 | ||
|
|
1fa1246c0b | ||
|
|
14cc37d07a | ||
|
|
c7519f8ea7 | ||
|
|
0c796a157b | ||
|
|
a9824fe6ec | ||
|
|
f18758d4bf | ||
|
|
c1a009a409 |
@@ -335,9 +335,9 @@ jobs:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
|
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
|
||||||
QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/debian13-loong64.qcow2
|
QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/debian13-loong64.qcow2
|
||||||
EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-code.fd
|
EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-code.fd
|
||||||
EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-vars.fd
|
EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-vars.fd
|
||||||
QCOW2_IMAGE: debian13-loong64.qcow2
|
QCOW2_IMAGE: debian13-loong64.qcow2
|
||||||
EFI_CODE: edk2-loongarch64-code.fd
|
EFI_CODE: edk2-loongarch64-code.fd
|
||||||
EFI_VARS: edk2-loongarch64-vars.fd
|
EFI_VARS: edk2-loongarch64-vars.fd
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.22.2</Version>
|
<Version>7.22.3</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.16" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.16" />
|
||||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.1.4" />
|
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.1.7" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" />
|
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for country code utilities
|
||||||
|
/// </summary>
|
||||||
|
public static class CountryExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Country code to emoji flag mapping for common countries
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, string> CountryEmojiMap = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
// Asia
|
||||||
|
{ "CN", "🇨🇳" }, // China
|
||||||
|
{ "HK", "🇭🇰" }, // Hong Kong
|
||||||
|
{ "TW", "🇹🇼" }, // Taiwan
|
||||||
|
{ "JP", "🇯🇵" }, // Japan
|
||||||
|
{ "SG", "🇸🇬" }, // Singapore
|
||||||
|
{ "KR", "🇰🇷" }, // South Korea
|
||||||
|
{ "TH", "🇹🇭" }, // Thailand
|
||||||
|
{ "VN", "🇻🇳" }, // Vietnam
|
||||||
|
{ "ID", "🇮🇩" }, // Indonesia
|
||||||
|
{ "PH", "🇵🇭" }, // Philippines
|
||||||
|
{ "MY", "🇲🇾" }, // Malaysia
|
||||||
|
{ "IN", "🇮🇳" }, // India
|
||||||
|
{ "PK", "🇵🇰" }, // Pakistan
|
||||||
|
{ "BD", "🇧🇩" }, // Bangladesh
|
||||||
|
{ "LK", "🇱🇰" }, // Sri Lanka
|
||||||
|
{ "KH", "🇰🇭" }, // Cambodia
|
||||||
|
{ "LA", "🇱🇦" }, // Laos
|
||||||
|
{ "MM", "🇲🇲" }, // Myanmar
|
||||||
|
|
||||||
|
// Americas
|
||||||
|
{ "US", "🇺🇸" }, // United States
|
||||||
|
{ "CA", "🇨🇦" }, // Canada
|
||||||
|
{ "MX", "🇲🇽" }, // Mexico
|
||||||
|
{ "BR", "🇧🇷" }, // Brazil
|
||||||
|
{ "AR", "🇦🇷" }, // Argentina
|
||||||
|
{ "CL", "🇨🇱" }, // Chile
|
||||||
|
{ "CO", "🇨🇴" }, // Colombia
|
||||||
|
|
||||||
|
// Europe
|
||||||
|
{ "GB", "🇬🇧" }, // United Kingdom
|
||||||
|
{ "DE", "🇩🇪" }, // Germany
|
||||||
|
{ "FR", "🇫🇷" }, // France
|
||||||
|
{ "IT", "🇮🇹" }, // Italy
|
||||||
|
{ "ES", "🇪🇸" }, // Spain
|
||||||
|
{ "RU", "🇷🇺" }, // Russia
|
||||||
|
{ "NL", "🇳🇱" }, // Netherlands
|
||||||
|
{ "CH", "🇨🇭" }, // Switzerland
|
||||||
|
{ "SE", "🇸🇪" }, // Sweden
|
||||||
|
{ "NO", "🇳🇴" }, // Norway
|
||||||
|
{ "DK", "🇩🇰" }, // Denmark
|
||||||
|
{ "FI", "🇫🇮" }, // Finland
|
||||||
|
{ "PL", "🇵🇱" }, // Poland
|
||||||
|
{ "CZ", "🇨🇿" }, // Czech Republic
|
||||||
|
{ "AT", "🇦🇹" }, // Austria
|
||||||
|
{ "GR", "🇬🇷" }, // Greece
|
||||||
|
{ "PT", "🇵🇹" }, // Portugal
|
||||||
|
{ "TR", "🇹🇷" }, // Turkey
|
||||||
|
{ "UA", "🇺🇦" }, // Ukraine
|
||||||
|
{ "RO", "🇷🇴" }, // Romania
|
||||||
|
|
||||||
|
// Middle East & Central Asia
|
||||||
|
{ "AE", "🇦🇪" }, // United Arab Emirates
|
||||||
|
{ "SA", "🇸🇦" }, // Saudi Arabia
|
||||||
|
{ "IL", "🇮🇱" }, // Israel
|
||||||
|
{ "KZ", "🇰🇿" }, // Kazakhstan
|
||||||
|
|
||||||
|
// Oceania
|
||||||
|
{ "AU", "🇦🇺" }, // Australia
|
||||||
|
{ "NZ", "🇳🇿" }, // New Zealand
|
||||||
|
|
||||||
|
// Africa
|
||||||
|
{ "ZA", "🇿🇦" }, // South Africa
|
||||||
|
{ "EG", "🇪🇬" }, // Egypt
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts country code to flag emoji using predefined mapping
|
||||||
|
/// Example: "US" -> "🇺🇸", "CN" -> "🇨🇳"
|
||||||
|
/// </summary>
|
||||||
|
public static string? CountryToEmoji(this string? countryCode)
|
||||||
|
{
|
||||||
|
if (countryCode.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CountryEmojiMap.TryGetValue(countryCode, out var emoji) ? emoji : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ public enum EServerColName
|
|||||||
SubRemarks,
|
SubRemarks,
|
||||||
DelayVal,
|
DelayVal,
|
||||||
SpeedVal,
|
SpeedVal,
|
||||||
|
IpInfo,
|
||||||
|
|
||||||
TodayDown,
|
TodayDown,
|
||||||
TodayUp,
|
TodayUp,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public static class AppEvents
|
|||||||
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
||||||
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
||||||
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
||||||
|
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
||||||
|
|
||||||
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
||||||
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
||||||
|
|||||||
@@ -207,6 +207,10 @@ public class Global
|
|||||||
|
|
||||||
public const string NaiveQuicProtocolShare = "naive+quic://";
|
public const string NaiveQuicProtocolShare = "naive+quic://";
|
||||||
|
|
||||||
|
public const string SOCKS5Protocol = "socks5://";
|
||||||
|
|
||||||
|
public const string SOCKS4Protocol = "socks4://";
|
||||||
|
|
||||||
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
||||||
{
|
{
|
||||||
{ EConfigType.VMess, "vmess://" },
|
{ EConfigType.VMess, "vmess://" },
|
||||||
|
|||||||
@@ -923,6 +923,7 @@ public static class ConfigHandler
|
|||||||
Delay = t33?.Delay ?? 0,
|
Delay = t33?.Delay ?? 0,
|
||||||
Speed = t33?.Speed ?? 0,
|
Speed = t33?.Speed ?? 0,
|
||||||
Sort = t33?.Sort ?? 0,
|
Sort = t33?.Sort ?? 0,
|
||||||
|
IpInfo = t33?.IpInfo ?? string.Empty,
|
||||||
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
|
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
|
||||||
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
|
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
|
||||||
TotalDown = (t22?.TotalDown ?? 0).ToString("D16"),
|
TotalDown = (t22?.TotalDown ?? 0).ToString("D16"),
|
||||||
@@ -943,6 +944,7 @@ public static class ConfigHandler
|
|||||||
EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(),
|
EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(),
|
||||||
EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(),
|
EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(),
|
||||||
EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(),
|
EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(),
|
||||||
|
EServerColName.IpInfo => lstProfile.OrderBy(t => t.IpInfo).ToList(),
|
||||||
EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(),
|
EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(),
|
||||||
EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
|
EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
|
||||||
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(),
|
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(),
|
||||||
@@ -963,6 +965,7 @@ public static class ConfigHandler
|
|||||||
EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(),
|
EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(),
|
||||||
EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(),
|
EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(),
|
||||||
EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(),
|
EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(),
|
||||||
|
EServerColName.IpInfo => lstProfile.OrderByDescending(t => t.IpInfo).ToList(),
|
||||||
EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(),
|
EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(),
|
||||||
EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
|
EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
|
||||||
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(),
|
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(),
|
||||||
|
|||||||
@@ -4,53 +4,41 @@ public static class ConnectionHandler
|
|||||||
{
|
{
|
||||||
private static readonly string _tag = "ConnectionHandler";
|
private static readonly string _tag = "ConnectionHandler";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs ping and IP checks and returns a formatted result string.
|
||||||
|
/// </summary>
|
||||||
public static async Task<string> RunAvailabilityCheck()
|
public static async Task<string> RunAvailabilityCheck()
|
||||||
{
|
{
|
||||||
var time = await GetRealPingTimeInfo();
|
var time = await GetRealPingTimeInfo();
|
||||||
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
|
var ip = time > 0 ? await GetIPInfo() : Global.None;
|
||||||
|
|
||||||
return string.Format(ResUI.TestMeOutput, time, ip);
|
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets IP information using the default local proxy.
|
||||||
|
/// </summary>
|
||||||
private static async Task<string?> GetIPInfo()
|
private static async Task<string?> GetIPInfo()
|
||||||
{
|
{
|
||||||
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
|
var webProxy = await GetWebProxy();
|
||||||
if (url.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadHandle = new DownloadService();
|
var ipInfo = await GetIPInfo(webProxy);
|
||||||
var result = await downloadHandle.TryDownloadString(url, true, "");
|
return ipInfo?.ToString() ?? Global.None;
|
||||||
if (result == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
|
|
||||||
if (ipInfo == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
|
|
||||||
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code;
|
|
||||||
|
|
||||||
return $"({country ?? "unknown"}) {ip}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Measures real ping time using configured test URL.
|
||||||
|
/// </summary>
|
||||||
private static async Task<int> GetRealPingTimeInfo()
|
private static async Task<int> GetRealPingTimeInfo()
|
||||||
{
|
{
|
||||||
var responseTime = -1;
|
var responseTime = -1;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
var webProxy = await GetWebProxy();
|
||||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
|
|
||||||
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
|
||||||
|
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
responseTime = await GetRealPingTime(url, webProxy, 10);
|
responseTime = await GetRealPingTime(webProxy, 10);
|
||||||
if (responseTime > 0)
|
if (responseTime > 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -66,8 +54,21 @@ public static class ConnectionHandler
|
|||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
/// <summary>
|
||||||
|
/// Creates local SOCKS proxy instance.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<WebProxy?> GetWebProxy()
|
||||||
{
|
{
|
||||||
|
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
|
return new WebProxy($"socks5://{Global.Loopback}:{port}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Measures response time by sending HTTP requests through proxy.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<int> GetRealPingTime(IWebProxy? webProxy, int downloadTimeout)
|
||||||
|
{
|
||||||
|
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||||
var responseTime = -1;
|
var responseTime = -1;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -95,4 +96,41 @@ public static class ConnectionHandler
|
|||||||
}
|
}
|
||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets IP and country information through specified proxy.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<IpInfoResult?> GetIPInfo(IWebProxy? webProxy)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
|
||||||
|
if (url.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadHandle = new DownloadService();
|
||||||
|
var result = await downloadHandle.TryDownloadString(url, webProxy, "");
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
|
||||||
|
if (ipInfo == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
|
||||||
|
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code ?? "unknown";
|
||||||
|
|
||||||
|
return new IpInfoResult(country, ip);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ public class FmtHandler
|
|||||||
{
|
{
|
||||||
return ShadowsocksFmt.Resolve(str, out msg);
|
return ShadowsocksFmt.Resolve(str, out msg);
|
||||||
}
|
}
|
||||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS]))
|
else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])
|
||||||
|
|| str.StartsWith(Global.SOCKS5Protocol)
|
||||||
|
|| str.StartsWith(Global.SOCKS4Protocol))
|
||||||
{
|
{
|
||||||
return SocksFmt.Resolve(str, out msg);
|
return SocksFmt.Resolve(str, out msg);
|
||||||
}
|
}
|
||||||
@@ -65,7 +67,8 @@ public class FmtHandler
|
|||||||
{
|
{
|
||||||
return VLESSFmt.Resolve(str, out msg);
|
return VLESSFmt.Resolve(str, out msg);
|
||||||
}
|
}
|
||||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare))
|
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2])
|
||||||
|
|| str.StartsWith(Global.Hysteria2ProtocolShare))
|
||||||
{
|
{
|
||||||
return Hysteria2Fmt.Resolve(str, out msg);
|
return Hysteria2Fmt.Resolve(str, out msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,12 +99,17 @@ public class SocksFmt : BaseFmt
|
|||||||
};
|
};
|
||||||
// parse base64 UserInfo
|
// parse base64 UserInfo
|
||||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||||
var userInfo = Utils.Base64Decode(rawUserInfo);
|
if (rawUserInfo.IsNotEmpty())
|
||||||
var userInfoParts = userInfo.Split([':'], 2);
|
|
||||||
if (userInfoParts.Length == 2)
|
|
||||||
{
|
{
|
||||||
item.Username = userInfoParts.First();
|
var userInfoParts = rawUserInfo.Contains(':')
|
||||||
item.Password = userInfoParts[1];
|
? rawUserInfo.Split(":", 2)
|
||||||
|
: Utils.Base64Decode(rawUserInfo).Split(":", 2);
|
||||||
|
|
||||||
|
if (userInfoParts.Length == 2)
|
||||||
|
{
|
||||||
|
item.Username = userInfoParts.First();
|
||||||
|
item.Password = userInfoParts.Last();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ public class ProfileExManager
|
|||||||
IndexIdEnqueue(indexId);
|
IndexIdEnqueue(indexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetTestIpInfo(string indexId, string ipInfo)
|
||||||
|
{
|
||||||
|
var profileEx = GetProfileExItem(indexId);
|
||||||
|
|
||||||
|
profileEx.IpInfo = ipInfo;
|
||||||
|
IndexIdEnqueue(indexId);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetSort(string indexId, int sort)
|
public void SetSort(string indexId, int sort)
|
||||||
{
|
{
|
||||||
var profileEx = GetProfileExItem(indexId);
|
var profileEx = GetProfileExItem(indexId);
|
||||||
|
|||||||
@@ -142,5 +142,10 @@ public class TaskManager
|
|||||||
await _updateFunc?.Invoke(false, msg);
|
await _updateFunc?.Invoke(false, msg);
|
||||||
}
|
}
|
||||||
NoticeManager.Instance.Enqueue(string.Join("\n", msgs));
|
NoticeManager.Instance.Enqueue(string.Join("\n", msgs));
|
||||||
|
|
||||||
|
if (msgs.Count > 0)
|
||||||
|
{
|
||||||
|
AppEvents.HasUpdateNotified.Publish(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,3 +17,12 @@ public class LocationInfo
|
|||||||
{
|
{
|
||||||
public string? country_code { get; set; }
|
public string? country_code { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly record struct IpInfoResult(string Country, string? Ip)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var emoji = Country.CountryToEmoji();
|
||||||
|
return $"{emoji}({Country}) {Ip}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public class ProfileItemModel : ReactiveObject
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public string SpeedVal { get; set; }
|
public string SpeedVal { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string IpInfo { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string TodayUp { get; set; }
|
public string TodayUp { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ public class SpeedTestResult
|
|||||||
public string? Delay { get; set; }
|
public string? Delay { get; set; }
|
||||||
|
|
||||||
public string? Speed { get; set; }
|
public string? Speed { get; set; }
|
||||||
|
|
||||||
|
public string? IpInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ public class ProfileExItem
|
|||||||
public decimal Speed { get; set; }
|
public decimal Speed { get; set; }
|
||||||
public int Sort { get; set; }
|
public int Sort { get; set; }
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
|
public string? IpInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+18
@@ -564,6 +564,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 IP Info 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string LvTestIpInfo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LvTestIpInfo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Speed (MB/s) 的本地化字符串。
|
/// 查找类似 Speed (MB/s) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1311,6 +1320,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 New Update 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuNewUpdate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuNewUpdate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Open the storage location 的本地化字符串。
|
/// 查找类似 Open the storage location 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>Not Support</value>
|
<value>Not Support</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP Info</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>New Update</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1737,4 +1737,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>Not Support</value>
|
<value>Not Support</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP Info</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>New Update</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>Not Support</value>
|
<value>Not Support</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP Info</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>New Update</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>Not Support</value>
|
<value>Not Support</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP Info</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>New Update</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1740,4 +1740,10 @@
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>Not Support</value>
|
<value>Not Support</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP Info</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>New Update</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1737,4 +1737,10 @@
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>不支持</value>
|
<value>不支持</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP 信息</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>有更新</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1737,4 +1737,10 @@
|
|||||||
<data name="MsgNotSupport" xml:space="preserve">
|
<data name="MsgNotSupport" xml:space="preserve">
|
||||||
<value>不支援</value>
|
<value>不支援</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LvTestIpInfo" xml:space="preserve">
|
||||||
|
<value>IP 資訊</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
|
<value>有更新</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -3,7 +3,7 @@ using System.Net.Http.Headers;
|
|||||||
namespace ServiceLib.Services;
|
namespace ServiceLib.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///Download
|
/// Download
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DownloadService
|
public class DownloadService
|
||||||
{
|
{
|
||||||
@@ -13,7 +13,10 @@ 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, Func<bool, string, Task> updateFunc)
|
/// <summary>
|
||||||
|
/// Downloads data with the specified proxy and reports progress messages.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int> DownloadDataAsync(string url, IWebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -36,6 +39,9 @@ public class DownloadService
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a file and reports progress through events.
|
||||||
|
/// </summary>
|
||||||
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
|
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -64,6 +70,9 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets redirect target URL without following redirects automatically.
|
||||||
|
/// </summary>
|
||||||
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
|
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
|
||||||
{
|
{
|
||||||
var webRequestHandler = new SocketsHttpHandler
|
var webRequestHandler = new SocketsHttpHandler
|
||||||
@@ -86,11 +95,23 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to download string content using proxy switch setting.
|
||||||
|
/// </summary>
|
||||||
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent)
|
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent)
|
||||||
|
{
|
||||||
|
var webProxy = await GetWebProxy(blProxy);
|
||||||
|
return await TryDownloadString(url, webProxy, userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to download string content with a specified proxy.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string?> TryDownloadString(string url, IWebProxy? webProxy, string userAgent)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15);
|
var result1 = await DownloadStringAsync(url, webProxy, userAgent, 15);
|
||||||
if (result1.IsNotEmpty())
|
if (result1.IsNotEmpty())
|
||||||
{
|
{
|
||||||
return result1;
|
return result1;
|
||||||
@@ -108,7 +129,7 @@ public class DownloadService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15);
|
var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, 15);
|
||||||
if (result2.IsNotEmpty())
|
if (result2.IsNotEmpty())
|
||||||
{
|
{
|
||||||
return result2;
|
return result2;
|
||||||
@@ -128,14 +149,12 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DownloadString
|
/// Downloads string content via HttpClient.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
private async Task<string?> DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, int timeout)
|
||||||
private async Task<string?> DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var webProxy = await GetWebProxy(blProxy);
|
|
||||||
var client = new HttpClient(new SocketsHttpHandler()
|
var client = new HttpClient(new SocketsHttpHandler()
|
||||||
{
|
{
|
||||||
Proxy = webProxy,
|
Proxy = webProxy,
|
||||||
@@ -172,15 +191,12 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DownloadString
|
/// Downloads string content via DownloaderHelper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
private async Task<string?> DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, int timeout)
|
||||||
private async Task<string?> DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var webProxy = await GetWebProxy(blProxy);
|
|
||||||
|
|
||||||
if (userAgent.IsNullOrEmpty())
|
if (userAgent.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
userAgent = Utils.GetVersion(false);
|
userAgent = Utils.GetVersion(false);
|
||||||
@@ -200,6 +216,9 @@ public class DownloadService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates local SOCKS proxy when proxy switch is enabled.
|
||||||
|
/// </summary>
|
||||||
private async Task<WebProxy?> GetWebProxy(bool blProxy)
|
private async Task<WebProxy?> GetWebProxy(bool blProxy)
|
||||||
{
|
{
|
||||||
if (!blProxy)
|
if (!blProxy)
|
||||||
@@ -215,6 +234,9 @@ public class DownloadService
|
|||||||
return new WebProxy($"socks5://{Global.Loopback}:{port}");
|
return new WebProxy($"socks5://{Global.Loopback}:{port}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the specified TCP endpoint is reachable.
|
||||||
|
/// </summary>
|
||||||
private async Task<bool> SocketCheck(string ip, int port)
|
private async Task<bool> SocketCheck(string ip, int port)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -392,10 +392,23 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
private async Task<int> DoRealPing(ServerTestItem it)
|
private async Task<int> DoRealPing(ServerTestItem it)
|
||||||
{
|
{
|
||||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||||
var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
var responseTime = await ConnectionHandler.GetRealPingTime(webProxy, 10);
|
||||||
|
|
||||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||||
await UpdateFunc(it.IndexId, responseTime.ToString());
|
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||||
|
|
||||||
|
if (responseTime > 0)
|
||||||
|
{
|
||||||
|
var ipInfo = await ConnectionHandler.GetIPInfo(webProxy);
|
||||||
|
var ipStr = ipInfo?.ToString() ?? Global.None;
|
||||||
|
ProfileExManager.Instance.SetTestIpInfo(it.IndexId, ipStr);
|
||||||
|
await UpdateIpInfoFunc(it.IndexId, ipStr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await UpdateIpInfoFunc(it.IndexId, ResUI.SpeedtestingSkip);
|
||||||
|
}
|
||||||
|
|
||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,4 +504,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
ProfileExManager.Instance.SetTestMessage(indexId, speed);
|
ProfileExManager.Instance.SetTestMessage(indexId, speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateIpInfoFunc(string indexId, string ip)
|
||||||
|
{
|
||||||
|
await _updateFunc?.Invoke(new() { IndexId = indexId, IpInfo = ip });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
[Reactive] public bool BlIsWindows { get; set; }
|
[Reactive] public bool BlIsWindows { get; set; }
|
||||||
|
|
||||||
|
[Reactive] public bool BlNewUpdate { get; set; }
|
||||||
|
|
||||||
#endregion Menu
|
#endregion Menu
|
||||||
|
|
||||||
#region Init
|
#region Init
|
||||||
@@ -251,6 +253,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||||
|
|
||||||
|
AppEvents.HasUpdateNotified
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.Subscribe(async bl => BlNewUpdate = bl);
|
||||||
|
|
||||||
#endregion AppEvents
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
|
|||||||
@@ -303,6 +303,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
item.SpeedVal = result.Speed ?? string.Empty;
|
item.SpeedVal = result.Speed ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
if (result.IpInfo.IsNotEmpty())
|
||||||
|
{
|
||||||
|
item.IpInfo = result.IpInfo ?? string.Empty;
|
||||||
|
}
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,6 +441,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
Speed = t33?.Speed ?? 0,
|
Speed = t33?.Speed ?? 0,
|
||||||
DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty,
|
DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty,
|
||||||
SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty,
|
SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty,
|
||||||
|
IpInfo = t33?.IpInfo ?? string.Empty,
|
||||||
TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown),
|
TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown),
|
||||||
TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp),
|
TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp),
|
||||||
TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown),
|
TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown),
|
||||||
|
|||||||
@@ -102,6 +102,13 @@
|
|||||||
|
|
||||||
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" />
|
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" />
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="btnNewUpdate"
|
||||||
|
Margin="{StaticResource MarginLr8}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Content="{x:Static resx:ResUI.menuNewUpdate}"
|
||||||
|
IsVisible="False" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<view:StatusBarView DockPanel.Dock="Bottom" />
|
<view:StatusBarView DockPanel.Dock="Bottom" />
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
|
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
|
||||||
menuPromotion.Click += MenuPromotion_Click;
|
menuPromotion.Click += MenuPromotion_Click;
|
||||||
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
||||||
|
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
||||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||||
menuClose.Click += MenuClose_Click;
|
menuClose.Click += MenuClose_Click;
|
||||||
|
|
||||||
@@ -102,6 +103,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
|
||||||
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
|
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
|
||||||
|
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.IsVisible).DisposeWith(disposables);
|
||||||
|
|
||||||
switch (_config.UiItem.MainGirdOrientation)
|
switch (_config.UiItem.MainGirdOrientation)
|
||||||
{
|
{
|
||||||
@@ -367,6 +369,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
{
|
{
|
||||||
_checkUpdateView ??= new CheckUpdateView();
|
_checkUpdateView ??= new CheckUpdateView();
|
||||||
DialogHost.Show(_checkUpdateView);
|
DialogHost.Show(_checkUpdateView);
|
||||||
|
|
||||||
|
AppEvents.HasUpdateNotified.Publish(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MenuBackupAndRestore_Click(object? sender, RoutedEventArgs e)
|
private void MenuBackupAndRestore_Click(object? sender, RoutedEventArgs e)
|
||||||
|
|||||||
@@ -277,6 +277,12 @@
|
|||||||
Header="{x:Static resx:ResUI.LvTestSpeed}"
|
Header="{x:Static resx:ResUI.LvTestSpeed}"
|
||||||
Tag="SpeedVal" />
|
Tag="SpeedVal" />
|
||||||
|
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding IpInfo}"
|
||||||
|
Header="{x:Static resx:ResUI.LvTestIpInfo}"
|
||||||
|
Tag="IpInfo" />
|
||||||
|
|
||||||
<DataGridTextColumn
|
<DataGridTextColumn
|
||||||
Width="100"
|
Width="100"
|
||||||
Binding="{Binding TodayUp}"
|
Binding="{Binding TodayUp}"
|
||||||
|
|||||||
@@ -425,10 +425,14 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
|
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
|
||||||
item2.DisplayIndex = displayIndex++;
|
item2.DisplayIndex = displayIndex++;
|
||||||
}
|
}
|
||||||
if (item.Name.ToLower().StartsWith("to"))
|
if (item.Name.StartsWith("to", StringComparison.CurrentCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
item2.IsVisible = _config.GuiItem.EnableStatistics;
|
item2.IsVisible = _config.GuiItem.EnableStatistics;
|
||||||
}
|
}
|
||||||
|
if (item.Name.Equals("IpInfo", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
item2.IsVisible = _config.SpeedTestItem.IPAPIUrl.IsNotEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,6 +313,13 @@
|
|||||||
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
|
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
|
||||||
<ContentControl x:Name="pbTheme" />
|
<ContentControl x:Name="pbTheme" />
|
||||||
</materialDesign:PopupBox>
|
</materialDesign:PopupBox>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="btnNewUpdate"
|
||||||
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
|
Content="{x:Static resx:ResUI.menuNewUpdate}"
|
||||||
|
Style="{StaticResource DefButton}"
|
||||||
|
Visibility="Hidden" />
|
||||||
</ToolBar>
|
</ToolBar>
|
||||||
</ToolBarTray>
|
</ToolBarTray>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public partial class MainWindow
|
|||||||
menuPromotion.Click += MenuPromotion_Click;
|
menuPromotion.Click += MenuPromotion_Click;
|
||||||
menuClose.Click += MenuClose_Click;
|
menuClose.Click += MenuClose_Click;
|
||||||
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
||||||
|
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
||||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||||
|
|
||||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||||
@@ -102,6 +103,8 @@ public partial class MainWindow
|
|||||||
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
|
||||||
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
|
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.Visibility).DisposeWith(disposables);
|
||||||
|
|
||||||
switch (_config.UiItem.MainGirdOrientation)
|
switch (_config.UiItem.MainGirdOrientation)
|
||||||
{
|
{
|
||||||
case EGirdOrientation.Horizontal:
|
case EGirdOrientation.Horizontal:
|
||||||
@@ -363,6 +366,8 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
_checkUpdateView ??= new CheckUpdateView();
|
_checkUpdateView ??= new CheckUpdateView();
|
||||||
DialogHost.Show(_checkUpdateView, "RootDialog");
|
DialogHost.Show(_checkUpdateView, "RootDialog");
|
||||||
|
|
||||||
|
AppEvents.HasUpdateNotified.Publish(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MenuBackupAndRestore_Click(object sender, RoutedEventArgs e)
|
private void MenuBackupAndRestore_Click(object sender, RoutedEventArgs e)
|
||||||
|
|||||||
@@ -345,6 +345,14 @@
|
|||||||
Binding="{Binding TodayUp}"
|
Binding="{Binding TodayUp}"
|
||||||
ExName="TodayUp"
|
ExName="TodayUp"
|
||||||
Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" />
|
Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" />
|
||||||
|
|
||||||
|
<base:MyDGTextColumn
|
||||||
|
x:Name="colIpInfo"
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding IpInfo}"
|
||||||
|
ExName="IpInfo"
|
||||||
|
Header="{x:Static resx:ResUI.LvTestIpInfo}" />
|
||||||
|
|
||||||
<base:MyDGTextColumn
|
<base:MyDGTextColumn
|
||||||
x:Name="colTodayDown"
|
x:Name="colTodayDown"
|
||||||
Width="100"
|
Width="100"
|
||||||
|
|||||||
@@ -378,10 +378,14 @@ public partial class ProfilesView
|
|||||||
item2.Width = item.Width;
|
item2.Width = item.Width;
|
||||||
item2.DisplayIndex = displayIndex++;
|
item2.DisplayIndex = displayIndex++;
|
||||||
}
|
}
|
||||||
if (item.Name.ToLower().StartsWith("to"))
|
if (item.Name.StartsWith("to", StringComparison.CurrentCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden;
|
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden;
|
||||||
}
|
}
|
||||||
|
if (item.Name.Equals("IpInfo", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
item2.Visibility = _config.SpeedTestItem.IPAPIUrl.IsNotEmpty() ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user