1
0
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:
wangdage12
2026-06-28 18:19:35 +08:00
committed by GitHub
11 changed files with 435 additions and 5 deletions
@@ -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>
@@ -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=&#xE774;}">
<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=&#xE968;}">
<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=&#xE71B;}">
<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=&#xE8B7;}">
<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=&#xE768;}">
<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);