mirror of
https://github.com/wangdage12/Snap.Hutao.git
synced 2026-07-31 04:10:25 +08:00
Merge pull request #41 from a1204507929/feature/proxy-settings
添加网络代理设置功能
This commit is contained in:
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>Snap.Hutao.SourceGeneration</PackageId>
|
<PackageId>Snap.Hutao.SourceGeneration</PackageId>
|
||||||
<Version>1.3.14</Version>
|
<Version>1.3.15</Version>
|
||||||
<Authors>DGP Studio</Authors>
|
<Authors>DGP Studio</Authors>
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
<DevelopmentDependency>true</DevelopmentDependency>
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
|
|||||||
+3
-1
@@ -4,6 +4,7 @@
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Snap.Hutao.Core.IO.Http;
|
using Snap.Hutao.Core.IO.Http;
|
||||||
using Snap.Hutao.Core.IO.Http.Proxy;
|
using Snap.Hutao.Core.IO.Http.Proxy;
|
||||||
|
using Snap.Hutao.Service;
|
||||||
using Snap.Hutao.Service.Game.Package.Advanced;
|
using Snap.Hutao.Service.Game.Package.Advanced;
|
||||||
using Snap.Hutao.Web.Hoyolab;
|
using Snap.Hutao.Web.Hoyolab;
|
||||||
using Snap.Hutao.Win32;
|
using Snap.Hutao.Win32;
|
||||||
@@ -36,10 +37,11 @@ internal static partial class ServiceCollectionExtension
|
|||||||
{
|
{
|
||||||
SocketsHttpHandler typedHandler = Unsafe.As<SocketsHttpHandler>(handler);
|
SocketsHttpHandler typedHandler = Unsafe.As<SocketsHttpHandler>(handler);
|
||||||
typedHandler.UseProxy = true;
|
typedHandler.UseProxy = true;
|
||||||
typedHandler.Proxy = HttpProxyUsingSystemProxy.Instance;
|
typedHandler.Proxy = provider.GetRequiredService<HutaoWebProxy>();
|
||||||
})
|
})
|
||||||
.AddHttpMessageHandler<RetryHttpHandler>();
|
.AddHttpMessageHandler<RetryHttpHandler>();
|
||||||
})
|
})
|
||||||
|
.AddSingleton(sp => new HutaoWebProxy(sp.GetRequiredService<AppOptions>(), HttpProxyUsingSystemProxy.Instance))
|
||||||
.AddHttpClients();
|
.AddHttpClients();
|
||||||
|
|
||||||
services
|
services
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
|
using Snap.Hutao.Service;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.IO.Http.Proxy;
|
||||||
|
|
||||||
|
internal sealed class HutaoWebProxy : IWebProxy
|
||||||
|
{
|
||||||
|
private static readonly IWebProxy NoProxy = new DirectWebProxy();
|
||||||
|
private readonly AppOptions appOptions;
|
||||||
|
private readonly HttpProxyUsingSystemProxy systemProxy;
|
||||||
|
|
||||||
|
public HutaoWebProxy(AppOptions appOptions, HttpProxyUsingSystemProxy systemProxy)
|
||||||
|
{
|
||||||
|
this.appOptions = appOptions;
|
||||||
|
this.systemProxy = systemProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICredentials? Credentials
|
||||||
|
{
|
||||||
|
get => InnerProxy.Credentials;
|
||||||
|
set => InnerProxy.Credentials = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DisplayProxyUri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!appOptions.ProxyEnabled.Value)
|
||||||
|
{
|
||||||
|
return "DIRECT";
|
||||||
|
}
|
||||||
|
|
||||||
|
return appOptions.ProxyType.Value switch
|
||||||
|
{
|
||||||
|
ProxyType.SystemProxy => systemProxy.DisplayProxyUri,
|
||||||
|
ProxyType.Http => string.IsNullOrEmpty(appOptions.ProxyAddress.Value)
|
||||||
|
? systemProxy.DisplayProxyUri
|
||||||
|
: $"http://{appOptions.ProxyAddress.Value}:{appOptions.ProxyPort.Value}",
|
||||||
|
ProxyType.Socks5 => string.IsNullOrEmpty(appOptions.ProxyAddress.Value)
|
||||||
|
? systemProxy.DisplayProxyUri
|
||||||
|
: $"socks5://{appOptions.ProxyAddress.Value}:{appOptions.ProxyPort.Value}",
|
||||||
|
ProxyType.None => "DIRECT",
|
||||||
|
_ => systemProxy.DisplayProxyUri,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IWebProxy InnerProxy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!appOptions.ProxyEnabled.Value)
|
||||||
|
{
|
||||||
|
return NoProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return appOptions.ProxyType.Value switch
|
||||||
|
{
|
||||||
|
ProxyType.SystemProxy => systemProxy,
|
||||||
|
ProxyType.Http => CreateHttpProxy(),
|
||||||
|
ProxyType.Socks5 => CreateSocks5Proxy(),
|
||||||
|
ProxyType.None => NoProxy,
|
||||||
|
_ => systemProxy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri? GetProxy(Uri destination)
|
||||||
|
{
|
||||||
|
return InnerProxy.GetProxy(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBypassed(Uri host)
|
||||||
|
{
|
||||||
|
return InnerProxy.IsBypassed(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IWebProxy CreateHttpProxy()
|
||||||
|
{
|
||||||
|
string address = appOptions.ProxyAddress.Value;
|
||||||
|
int port = appOptions.ProxyPort.Value;
|
||||||
|
|
||||||
|
if (!IsValidProxyAddress(address) || !IsValidProxyPort(port))
|
||||||
|
{
|
||||||
|
return systemProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebProxy webProxy = new()
|
||||||
|
{
|
||||||
|
Address = new Uri($"http://{address}:{port}"),
|
||||||
|
BypassProxyOnLocal = false,
|
||||||
|
UseDefaultCredentials = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return webProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IWebProxy CreateSocks5Proxy()
|
||||||
|
{
|
||||||
|
string address = appOptions.ProxyAddress.Value;
|
||||||
|
int port = appOptions.ProxyPort.Value;
|
||||||
|
|
||||||
|
if (!IsValidProxyAddress(address) || !IsValidProxyPort(port))
|
||||||
|
{
|
||||||
|
return systemProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Socks5WebProxy(address, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidProxyAddress(string address)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(address) &&
|
||||||
|
(Uri.CheckHostName(address) != UriHostNameType.Unknown ||
|
||||||
|
System.Net.IPAddress.TryParse(address, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidProxyPort(int port)
|
||||||
|
{
|
||||||
|
return port is >= 1 and <= 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DirectWebProxy : IWebProxy
|
||||||
|
{
|
||||||
|
public ICredentials? Credentials { get; set; }
|
||||||
|
|
||||||
|
public Uri? GetProxy(Uri destination) => destination;
|
||||||
|
|
||||||
|
public bool IsBypassed(Uri host) => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Socks5WebProxy : IWebProxy
|
||||||
|
{
|
||||||
|
private readonly string address;
|
||||||
|
private readonly int port;
|
||||||
|
|
||||||
|
public Socks5WebProxy(string address, int port)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICredentials? Credentials { get; set; }
|
||||||
|
|
||||||
|
public Uri? GetProxy(Uri destination)
|
||||||
|
{
|
||||||
|
return new Uri($"socks5://{address}:{port}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBypassed(Uri host)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,4 +152,10 @@ internal static class SettingKeys
|
|||||||
public const string CompactWebView2WindowInactiveOpacity = "Snap::Hutao::Web::WebView::Compact::InactiveOpacity";
|
public const string CompactWebView2WindowInactiveOpacity = "Snap::Hutao::Web::WebView::Compact::InactiveOpacity";
|
||||||
public const string CompactWebView2WindowPreviousSourceUrl = "Snap::Hutao::Web::WebView::Compact::PreviousSourceUrl";
|
public const string CompactWebView2WindowPreviousSourceUrl = "Snap::Hutao::Web::WebView::Compact::PreviousSourceUrl";
|
||||||
public const string WebView2VideoFastForwardOrRewindSeconds = "Snap::Hutao::Web::WebView::Video::FastForwardOrRewind::Seconds";
|
public const string WebView2VideoFastForwardOrRewindSeconds = "Snap::Hutao::Web::WebView::Video::FastForwardOrRewind::Seconds";
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
public const string ProxyType = "Snap::Hutao::Web::Proxy::Type";
|
||||||
|
public const string ProxyAddress = "Snap::Hutao::Web::Proxy::Address";
|
||||||
|
public const string ProxyPort = "Snap::Hutao::Web::Proxy::Port";
|
||||||
|
public const string ProxyEnabled = "Snap::Hutao::Web::Proxy::Enabled";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Intrinsic;
|
||||||
|
|
||||||
|
[ExtendedEnum]
|
||||||
|
internal enum ProxyType
|
||||||
|
{
|
||||||
|
[LocalizationKey(nameof(SH.ServiceProxyTypeNone))]
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
[LocalizationKey(nameof(SH.ServiceProxyTypeSystemProxy))]
|
||||||
|
SystemProxy = 1,
|
||||||
|
|
||||||
|
[LocalizationKey(nameof(SH.ServiceProxyTypeHttp))]
|
||||||
|
Http = 2,
|
||||||
|
|
||||||
|
[LocalizationKey(nameof(SH.ServiceProxyTypeSocks5))]
|
||||||
|
Socks5 = 3,
|
||||||
|
}
|
||||||
@@ -875,6 +875,18 @@
|
|||||||
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
|
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
|
||||||
<value>无背景图片</value>
|
<value>无背景图片</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceProxyTypeNone" xml:space="preserve">
|
||||||
|
<value>无代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceProxyTypeSystemProxy" xml:space="preserve">
|
||||||
|
<value>系统代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceProxyTypeHttp" xml:space="preserve">
|
||||||
|
<value>HTTP 代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServiceProxyTypeSocks5" xml:space="preserve">
|
||||||
|
<value>SOCKS5 代理</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceCloseButtonBehaviorTypeExit" xml:space="preserve">
|
<data name="ServiceCloseButtonBehaviorTypeExit" xml:space="preserve">
|
||||||
<value>退出胡桃</value>
|
<value>退出胡桃</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2255,6 +2267,24 @@
|
|||||||
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
||||||
<value>删除转换服务器游戏客户端缓存</value>
|
<value>删除转换服务器游戏客户端缓存</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewModelSettingProxySaveSuccess" xml:space="preserve">
|
||||||
|
<value>代理设置已保存</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewModelSettingProxyTesting" xml:space="preserve">
|
||||||
|
<value>正在测试代理连接...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewModelSettingProxyTestSuccess" xml:space="preserve">
|
||||||
|
<value>可用 [{0}ms]</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewModelSettingProxyTestFailed" xml:space="preserve">
|
||||||
|
<value>不可用</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewModelSettingProxyTestAction" xml:space="preserve">
|
||||||
|
<value>测试</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewModelSettingProxySaveAction" xml:space="preserve">
|
||||||
|
<value>保存</value>
|
||||||
|
</data>
|
||||||
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
||||||
<value>已使用磁盘空间:{0}</value>
|
<value>已使用磁盘空间:{0}</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -3330,6 +3360,33 @@
|
|||||||
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
||||||
<value>无感验证</value>
|
<value>无感验证</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyHeader" xml:space="preserve">
|
||||||
|
<value>网络代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyEnabledDescription" xml:space="preserve">
|
||||||
|
<value>启用自定义代理设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyEnabledHeader" xml:space="preserve">
|
||||||
|
<value>启用代理</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyTypeDescription" xml:space="preserve">
|
||||||
|
<value>选择代理类型</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyTypeHeader" xml:space="preserve">
|
||||||
|
<value>代理类型</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyAddressDescription" xml:space="preserve">
|
||||||
|
<value>代理服务器地址</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyAddressHeader" xml:space="preserve">
|
||||||
|
<value>代理地址</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyPortDescription" xml:space="preserve">
|
||||||
|
<value>代理服务器端口</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingProxyPortHeader" xml:space="preserve">
|
||||||
|
<value>代理端口</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||||
<value>选择想要获取公告的游戏服务器</value>
|
<value>选择想要获取公告的游戏服务器</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.UI.Xaml;
|
|||||||
using Snap.Hutao.Core.Property;
|
using Snap.Hutao.Core.Property;
|
||||||
using Snap.Hutao.Core.Setting;
|
using Snap.Hutao.Core.Setting;
|
||||||
using Snap.Hutao.Model;
|
using Snap.Hutao.Model;
|
||||||
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
using Snap.Hutao.Service.BackgroundImage;
|
using Snap.Hutao.Service.BackgroundImage;
|
||||||
using Snap.Hutao.UI.Xaml.Media.Backdrop;
|
using Snap.Hutao.UI.Xaml.Media.Backdrop;
|
||||||
@@ -49,6 +50,8 @@ internal sealed partial class AppOptions : DbStoreOptions
|
|||||||
|
|
||||||
public ImmutableArray<NameValue<BridgeShareSaveType>> BridgeShareSaveTypes { get; } = ImmutableCollectionsNameValue.FromEnum<BridgeShareSaveType>(type => type.GetLocalizedDescription(SH.ResourceManager, CultureInfo.CurrentCulture) ?? string.Empty);
|
public ImmutableArray<NameValue<BridgeShareSaveType>> BridgeShareSaveTypes { get; } = ImmutableCollectionsNameValue.FromEnum<BridgeShareSaveType>(type => type.GetLocalizedDescription(SH.ResourceManager, CultureInfo.CurrentCulture) ?? string.Empty);
|
||||||
|
|
||||||
|
public ImmutableArray<NameValue<ProxyType>> ProxyTypes { get; } = ImmutableCollectionsNameValue.FromEnum<ProxyType>(static @enum => @enum.GetLocalizedDescription(SH.ResourceManager, CultureInfo.CurrentCulture) ?? string.Empty);
|
||||||
|
|
||||||
public ImmutableArray<NameValue<LastWindowCloseBehavior>> LastWindowCloseBehaviors { get; } = ImmutableCollectionsNameValue.FromEnum<LastWindowCloseBehavior>(static @enum => @enum.GetLocalizedDescription(SH.ResourceManager, CultureInfo.CurrentCulture) ?? string.Empty);
|
public ImmutableArray<NameValue<LastWindowCloseBehavior>> LastWindowCloseBehaviors { get; } = ImmutableCollectionsNameValue.FromEnum<LastWindowCloseBehavior>(static @enum => @enum.GetLocalizedDescription(SH.ResourceManager, CultureInfo.CurrentCulture) ?? string.Empty);
|
||||||
|
|
||||||
[field: MaybeNull]
|
[field: MaybeNull]
|
||||||
@@ -83,4 +86,16 @@ internal sealed partial class AppOptions : DbStoreOptions
|
|||||||
|
|
||||||
[field: MaybeNull]
|
[field: MaybeNull]
|
||||||
public IObservableProperty<LastWindowCloseBehavior> LastWindowCloseBehavior { get => field ??= CreateProperty(SettingKeys.LastWindowCloseBehavior, Service.LastWindowCloseBehavior.EnsureNotifyIconCreated); }
|
public IObservableProperty<LastWindowCloseBehavior> LastWindowCloseBehavior { get => field ??= CreateProperty(SettingKeys.LastWindowCloseBehavior, Service.LastWindowCloseBehavior.EnsureNotifyIconCreated); }
|
||||||
|
|
||||||
|
[field: MaybeNull]
|
||||||
|
public IObservableProperty<ProxyType> ProxyType { get => field ??= CreateProperty(SettingKeys.ProxyType, Model.Intrinsic.ProxyType.SystemProxy); }
|
||||||
|
|
||||||
|
[field: MaybeNull]
|
||||||
|
public IObservableProperty<string> ProxyAddress { get => field ??= CreateProperty(SettingKeys.ProxyAddress, string.Empty); }
|
||||||
|
|
||||||
|
[field: MaybeNull]
|
||||||
|
public IObservableProperty<int> ProxyPort { get => field ??= CreateProperty(SettingKeys.ProxyPort, 1080); }
|
||||||
|
|
||||||
|
[field: MaybeNull]
|
||||||
|
public IObservableProperty<bool> ProxyEnabled { get => field ??= CreateProperty(SettingKeys.ProxyEnabled, false); }
|
||||||
}
|
}
|
||||||
@@ -210,6 +210,78 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<!-- 网络代理 -->
|
||||||
|
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Proxy}">
|
||||||
|
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||||
|
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||||
|
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingProxyHeader}"/>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
Description="{shuxm:ResourceString Name=ViewPageSettingProxyEnabledDescription}"
|
||||||
|
Header="{shuxm:ResourceString Name=ViewPageSettingProxyEnabledHeader}"
|
||||||
|
HeaderIcon="{shuxm:FontIcon Glyph=}">
|
||||||
|
<ToggleSwitch
|
||||||
|
MinWidth="120"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsEnabled="{Binding IsTestingProxy, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||||
|
IsOn="{Binding AppOptions.ProxyEnabled.Value, Mode=TwoWay}"/>
|
||||||
|
</cwc:SettingsCard>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
Description="{shuxm:ResourceString Name=ViewPageSettingProxyTypeDescription}"
|
||||||
|
Header="{shuxm:ResourceString Name=ViewPageSettingProxyTypeHeader}"
|
||||||
|
HeaderIcon="{shuxm:FontIcon Glyph=}">
|
||||||
|
<shuxc:SizeRestrictedContentControl>
|
||||||
|
<ComboBox
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
ItemsSource="{Binding AppOptions.ProxyTypes}"
|
||||||
|
IsEnabled="{Binding IsTestingProxy, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||||
|
SelectedItem="{Binding SelectedProxyType, Mode=TwoWay}"/>
|
||||||
|
</shuxc:SizeRestrictedContentControl>
|
||||||
|
</cwc:SettingsCard>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
Description="{shuxm:ResourceString Name=ViewPageSettingProxyAddressDescription}"
|
||||||
|
Header="{shuxm:ResourceString Name=ViewPageSettingProxyAddressHeader}"
|
||||||
|
HeaderIcon="{shuxm:FontIcon Glyph=}">
|
||||||
|
<TextBox
|
||||||
|
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsEnabled="{Binding IsTestingProxy, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||||
|
Text="{Binding AppOptions.ProxyAddress.Value, Mode=TwoWay}"/>
|
||||||
|
</cwc:SettingsCard>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
Description="{shuxm:ResourceString Name=ViewPageSettingProxyPortDescription}"
|
||||||
|
Header="{shuxm:ResourceString Name=ViewPageSettingProxyPortHeader}"
|
||||||
|
HeaderIcon="{shuxm:FontIcon Glyph=}">
|
||||||
|
<NumberBox
|
||||||
|
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
AcceptsExpression="False"
|
||||||
|
IsEnabled="{Binding IsTestingProxy, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||||
|
Maximum="65535"
|
||||||
|
Minimum="1"
|
||||||
|
Value="{Binding AppOptions.ProxyPort.Value, Mode=TwoWay}"/>
|
||||||
|
</cwc:SettingsCard>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
Header="{shuxm:ResourceString Name=ViewModelSettingProxyTestAction}"
|
||||||
|
HeaderIcon="{shuxm:FontIcon Glyph=}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Button
|
||||||
|
Command="{Binding TestProxyCommand}"
|
||||||
|
Content="{shuxm:ResourceString Name=ViewModelSettingProxyTestAction}"
|
||||||
|
IsEnabled="{Binding IsTestingProxy, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"/>
|
||||||
|
<Button
|
||||||
|
Command="{Binding SaveProxyCommand}"
|
||||||
|
Content="{shuxm:ResourceString Name=ViewModelSettingProxySaveAction}"
|
||||||
|
IsEnabled="{Binding IsTestingProxy, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"/>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding ProxyTestResult}"
|
||||||
|
Visibility="{Binding ProxyTestResult, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</cwc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- 外观 -->
|
<!-- 外观 -->
|
||||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Appearance}">
|
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Appearance}">
|
||||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ internal sealed partial class FeedbackViewModel : Abstraction.ViewModel
|
|||||||
[GeneratedConstructor]
|
[GeneratedConstructor]
|
||||||
public partial FeedbackViewModel(IServiceProvider serviceProvider);
|
public partial FeedbackViewModel(IServiceProvider serviceProvider);
|
||||||
|
|
||||||
public static HttpProxyUsingSystemProxy DynamicHttpProxy { get => HttpProxyUsingSystemProxy.Instance; }
|
public partial HutaoWebProxy DynamicHttpProxy { get; }
|
||||||
|
|
||||||
public partial RuntimeOptions RuntimeOptions { get; }
|
public partial RuntimeOptions RuntimeOptions { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.IO.Http.Proxy;
|
||||||
|
using Snap.Hutao.Model;
|
||||||
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
|
using Snap.Hutao.Service;
|
||||||
|
using Snap.Hutao.Service.Notification;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.ViewModel.Setting;
|
||||||
|
|
||||||
|
[BindableCustomPropertyProvider]
|
||||||
|
[Service(ServiceLifetime.Scoped)]
|
||||||
|
internal sealed partial class SettingProxyViewModel : Abstraction.ViewModel
|
||||||
|
{
|
||||||
|
private readonly ITaskContext taskContext;
|
||||||
|
private readonly IMessenger messenger;
|
||||||
|
private readonly HutaoWebProxy hutaoWebProxy;
|
||||||
|
|
||||||
|
[GeneratedConstructor]
|
||||||
|
public partial SettingProxyViewModel(IServiceProvider serviceProvider);
|
||||||
|
|
||||||
|
public partial AppOptions AppOptions { get; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string? ProxyTestResult { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool IsTestingProxy { get; set; }
|
||||||
|
|
||||||
|
public NameValue<ProxyType>? SelectedProxyType
|
||||||
|
{
|
||||||
|
get => field ??= AppOptions.ProxyTypes.Single(t => t.Value == AppOptions.ProxyType.Value);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref field, value) && value is not null)
|
||||||
|
{
|
||||||
|
AppOptions.ProxyType.Value = value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("SaveProxyCommand")]
|
||||||
|
private void SaveProxy()
|
||||||
|
{
|
||||||
|
messenger.Send(InfoBarMessage.Success(SH.ViewModelSettingProxySaveSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("TestProxyCommand")]
|
||||||
|
private async Task TestProxyAsync()
|
||||||
|
{
|
||||||
|
IsTestingProxy = true;
|
||||||
|
ProxyTestResult = SH.ViewModelSettingProxyTesting;
|
||||||
|
|
||||||
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using HttpClient httpClient = new(new SocketsHttpHandler
|
||||||
|
{
|
||||||
|
Proxy = hutaoWebProxy,
|
||||||
|
UseProxy = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://hut.ao").ConfigureAwait(false);
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
ProxyTestResult = SH.FormatViewModelSettingProxyTestSuccess(stopwatch.ElapsedMilliseconds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProxyTestResult = SH.ViewModelSettingProxyTestFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
ProxyTestResult = SH.ViewModelSettingProxyTestFailed;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsTestingProxy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,8 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel, INavigat
|
|||||||
|
|
||||||
public partial SettingWebViewViewModel WebView { get; }
|
public partial SettingWebViewViewModel WebView { get; }
|
||||||
|
|
||||||
|
public partial SettingProxyViewModel Proxy { get; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string? UpdateInfo { get; set; }
|
public partial string? UpdateInfo { get; set; }
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel, INavigat
|
|||||||
|
|
||||||
protected override ValueTask<bool> LoadOverrideAsync(CancellationToken token)
|
protected override ValueTask<bool> LoadOverrideAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
MakeSubViewModel([Geetest, Appearance, Storage, HotKey, Home, Game, GachaLog, WebView]);
|
MakeSubViewModel([Geetest, Appearance, Storage, HotKey, Home, Game, GachaLog, WebView, Proxy]);
|
||||||
|
|
||||||
Storage.CacheFolderView = new(taskContext, HutaoRuntime.LocalCacheDirectory);
|
Storage.CacheFolderView = new(taskContext, HutaoRuntime.LocalCacheDirectory);
|
||||||
Storage.DataFolderView = new(taskContext, HutaoRuntime.DataDirectory);
|
Storage.DataFolderView = new(taskContext, HutaoRuntime.DataDirectory);
|
||||||
|
|||||||
Reference in New Issue
Block a user