Backup before removing hiddify references
This commit is contained in:
@@ -3,7 +3,6 @@ import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/core/model/optional_range.dart';
|
||||
import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||
|
||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||
import 'package:hiddify/core/utils/json_converters.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
@@ -423,6 +422,27 @@ abstract class ConfigOptions {
|
||||
(ref) async {
|
||||
// final region = ref.watch(Preferences.region);
|
||||
final rules = <SingboxRule>[];
|
||||
|
||||
// Добавляем правила для исключенных доменов
|
||||
final excludedDomains = ref.watch(excludedDomainsListProvider);
|
||||
if (excludedDomains.isNotEmpty) {
|
||||
final domainsString = excludedDomains.map((domain) {
|
||||
if (domain.startsWith('.')) {
|
||||
// Зона домена (например, .com) - используем domain: prefix
|
||||
return 'domain:$domain';
|
||||
} else {
|
||||
// Конкретный домен (например, site.com) - используем domain: для поддомена
|
||||
return 'domain:.$domain';
|
||||
}
|
||||
}).join(',');
|
||||
|
||||
rules.add(
|
||||
SingboxRule(
|
||||
domains: domainsString,
|
||||
outbound: RuleOutbound.bypass,
|
||||
),
|
||||
);
|
||||
}
|
||||
// final rules = switch (region) {
|
||||
// Region.ir => [
|
||||
// const SingboxRule(
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/model/optional_range.dart';
|
||||
import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
|
||||
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
||||
import 'package:hiddify/core/widget/tip_card.dart';
|
||||
@@ -12,9 +9,7 @@ import 'package:hiddify/features/common/confirmation_dialogs.dart';
|
||||
import 'package:hiddify/features/common/nested_app_bar.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
||||
import 'package:hiddify/features/config_option/overview/warp_options_widgets.dart';
|
||||
import 'package:hiddify/features/config_option/widget/preference_tile.dart';
|
||||
import 'package:hiddify/features/log/model/log_level.dart';
|
||||
import 'package:hiddify/features/settings/widgets/sections_widgets.dart';
|
||||
import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||
@@ -22,56 +17,15 @@ import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:humanizer/humanizer.dart';
|
||||
|
||||
enum ConfigOptionSection {
|
||||
warp,
|
||||
fragment;
|
||||
|
||||
static final _warpKey = GlobalKey(debugLabel: "warp-section-key");
|
||||
static final _fragmentKey = GlobalKey(debugLabel: "fragment-section-key");
|
||||
|
||||
GlobalKey get key => switch (this) {
|
||||
ConfigOptionSection.warp => _warpKey,
|
||||
ConfigOptionSection.fragment => _fragmentKey,
|
||||
};
|
||||
}
|
||||
|
||||
class ConfigOptionsPage extends HookConsumerWidget {
|
||||
ConfigOptionsPage({super.key, String? section}) : section = section != null ? ConfigOptionSection.values.byName(section) : null;
|
||||
|
||||
final ConfigOptionSection? section;
|
||||
const ConfigOptionsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
final scrollController = useScrollController();
|
||||
|
||||
useMemoized(
|
||||
() {
|
||||
if (section != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
final box = section!.key.currentContext?.findRenderObject() as RenderBox?;
|
||||
final offset = box?.localToGlobal(Offset.zero);
|
||||
if (offset == null) return;
|
||||
final height = scrollController.offset + offset.dy - MediaQueryData.fromView(View.of(context)).padding.top - kToolbarHeight;
|
||||
scrollController.animateTo(
|
||||
height,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.decelerate,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
String experimental(String txt) {
|
||||
return "$txt (${t.settings.experimental})";
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
NestedAppBar(
|
||||
@@ -131,76 +85,71 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
TipCard(message: t.settings.experimentalMsg),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.logLevel),
|
||||
preferences: ref.watch(ConfigOptions.logLevel.notifier),
|
||||
choices: LogLevel.choices,
|
||||
title: t.config.logLevel,
|
||||
presentChoice: (value) => value.name.toUpperCase(),
|
||||
),
|
||||
|
||||
const SettingsDivider(),
|
||||
SettingsSection(t.config.section.route),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.region),
|
||||
preferences: ref.watch(ConfigOptions.region.notifier),
|
||||
choices: Region.values,
|
||||
title: t.settings.general.region,
|
||||
presentChoice: (value) => value.present(t),
|
||||
onChanged: (val) => ref.watch(ConfigOptions.directDnsAddress.notifier).reset(),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(experimental(t.config.blockAds)),
|
||||
value: ref.watch(ConfigOptions.blockAds),
|
||||
onChanged: ref.watch(ConfigOptions.blockAds.notifier).update,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(experimental(t.config.bypassLan)),
|
||||
value: ref.watch(ConfigOptions.bypassLan),
|
||||
onChanged: ref.watch(ConfigOptions.bypassLan.notifier).update,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.resolveDestination),
|
||||
value: ref.watch(ConfigOptions.resolveDestination),
|
||||
onChanged: ref.watch(ConfigOptions.resolveDestination.notifier).update,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.ipv6Mode),
|
||||
preferences: ref.watch(ConfigOptions.ipv6Mode.notifier),
|
||||
choices: IPv6Mode.values,
|
||||
title: t.config.ipv6Mode,
|
||||
presentChoice: (value) => value.present(t),
|
||||
// Варианты маршрутизации - раскрывающаяся секция
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
leading: const Icon(Icons.route),
|
||||
title: Text(t.config.section.route),
|
||||
initiallyExpanded: false,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: Text(t.config.resolveDestination),
|
||||
value: ref.watch(ConfigOptions.resolveDestination),
|
||||
onChanged: ref.watch(ConfigOptions.resolveDestination.notifier).update,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.ipv6Mode),
|
||||
preferences: ref.watch(ConfigOptions.ipv6Mode.notifier),
|
||||
choices: IPv6Mode.values,
|
||||
title: t.config.ipv6Mode,
|
||||
presentChoice: (value) => value.present(t),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SettingsDivider(),
|
||||
SettingsSection(t.config.section.dns),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.remoteDnsAddress),
|
||||
preferences: ref.watch(ConfigOptions.remoteDnsAddress.notifier),
|
||||
title: t.config.remoteDnsAddress,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy),
|
||||
preferences: ref.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
|
||||
choices: DomainStrategy.values,
|
||||
title: t.config.remoteDnsDomainStrategy,
|
||||
presentChoice: (value) => value.displayName,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.directDnsAddress),
|
||||
preferences: ref.watch(ConfigOptions.directDnsAddress.notifier),
|
||||
title: t.config.directDnsAddress,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.directDnsDomainStrategy),
|
||||
preferences: ref.watch(ConfigOptions.directDnsDomainStrategy.notifier),
|
||||
choices: DomainStrategy.values,
|
||||
title: t.config.directDnsDomainStrategy,
|
||||
presentChoice: (value) => value.displayName,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.enableDnsRouting),
|
||||
value: ref.watch(ConfigOptions.enableDnsRouting),
|
||||
onChanged: ref.watch(ConfigOptions.enableDnsRouting.notifier).update,
|
||||
// Параметры DNS - раскрывающаяся секция
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
leading: const Icon(Icons.dns),
|
||||
title: Text(t.config.section.dns),
|
||||
initiallyExpanded: false,
|
||||
children: [
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.remoteDnsAddress),
|
||||
preferences: ref.watch(ConfigOptions.remoteDnsAddress.notifier),
|
||||
title: t.config.remoteDnsAddress,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy),
|
||||
preferences: ref.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
|
||||
choices: DomainStrategy.values,
|
||||
title: t.config.remoteDnsDomainStrategy,
|
||||
presentChoice: (value) => value.displayName,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.directDnsAddress),
|
||||
preferences: ref.watch(ConfigOptions.directDnsAddress.notifier),
|
||||
title: t.config.directDnsAddress,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.directDnsDomainStrategy),
|
||||
preferences: ref.watch(ConfigOptions.directDnsDomainStrategy.notifier),
|
||||
choices: DomainStrategy.values,
|
||||
title: t.config.directDnsDomainStrategy,
|
||||
presentChoice: (value) => value.displayName,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.enableDnsRouting),
|
||||
value: ref.watch(ConfigOptions.enableDnsRouting),
|
||||
onChanged: ref.watch(ConfigOptions.enableDnsRouting.notifier).update,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// const SettingsDivider(),
|
||||
// SettingsSection(experimental(t.config.section.mux)),
|
||||
@@ -226,144 +175,103 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
||||
// digitsOnly: true,
|
||||
// ),
|
||||
const SettingsDivider(),
|
||||
SettingsSection(t.config.section.inbound),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.serviceMode),
|
||||
preferences: ref.watch(ConfigOptions.serviceMode.notifier),
|
||||
choices: ServiceMode.choices,
|
||||
title: t.config.serviceMode,
|
||||
presentChoice: (value) => value.present(t),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.strictRoute),
|
||||
value: ref.watch(ConfigOptions.strictRoute),
|
||||
onChanged: ref.watch(ConfigOptions.strictRoute.notifier).update,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.tunImplementation),
|
||||
preferences: ref.watch(ConfigOptions.tunImplementation.notifier),
|
||||
choices: TunImplementation.values,
|
||||
title: t.config.tunImplementation,
|
||||
presentChoice: (value) => value.name,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.mixedPort),
|
||||
preferences: ref.watch(ConfigOptions.mixedPort.notifier),
|
||||
title: t.config.mixedPort,
|
||||
inputToValue: int.tryParse,
|
||||
digitsOnly: true,
|
||||
validateInput: isPort,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.tproxyPort),
|
||||
preferences: ref.watch(ConfigOptions.tproxyPort.notifier),
|
||||
title: t.config.tproxyPort,
|
||||
inputToValue: int.tryParse,
|
||||
digitsOnly: true,
|
||||
validateInput: isPort,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.localDnsPort),
|
||||
preferences: ref.watch(ConfigOptions.localDnsPort.notifier),
|
||||
title: t.config.localDnsPort,
|
||||
inputToValue: int.tryParse,
|
||||
digitsOnly: true,
|
||||
validateInput: isPort,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(
|
||||
experimental(t.config.allowConnectionFromLan),
|
||||
// Входящие параметры - раскрывающаяся секция
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
leading: const Icon(Icons.input),
|
||||
title: Text(t.config.section.inbound),
|
||||
initiallyExpanded: false,
|
||||
children: [
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.serviceMode),
|
||||
preferences: ref.watch(ConfigOptions.serviceMode.notifier),
|
||||
choices: ServiceMode.choices,
|
||||
title: t.config.serviceMode,
|
||||
presentChoice: (value) => value.present(t),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.strictRoute),
|
||||
value: ref.watch(ConfigOptions.strictRoute),
|
||||
onChanged: ref.watch(ConfigOptions.strictRoute.notifier).update,
|
||||
),
|
||||
ChoicePreferenceWidget(
|
||||
selected: ref.watch(ConfigOptions.tunImplementation),
|
||||
preferences: ref.watch(ConfigOptions.tunImplementation.notifier),
|
||||
choices: TunImplementation.values,
|
||||
title: t.config.tunImplementation,
|
||||
presentChoice: (value) => value.name,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.mixedPort),
|
||||
preferences: ref.watch(ConfigOptions.mixedPort.notifier),
|
||||
title: t.config.mixedPort,
|
||||
inputToValue: int.tryParse,
|
||||
digitsOnly: true,
|
||||
validateInput: isPort,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.tproxyPort),
|
||||
preferences: ref.watch(ConfigOptions.tproxyPort.notifier),
|
||||
title: t.config.tproxyPort,
|
||||
inputToValue: int.tryParse,
|
||||
digitsOnly: true,
|
||||
validateInput: isPort,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.localDnsPort),
|
||||
preferences: ref.watch(ConfigOptions.localDnsPort.notifier),
|
||||
title: t.config.localDnsPort,
|
||||
inputToValue: int.tryParse,
|
||||
digitsOnly: true,
|
||||
validateInput: isPort,
|
||||
),
|
||||
],
|
||||
),
|
||||
value: ref.watch(ConfigOptions.allowConnectionFromLan),
|
||||
onChanged: ref.read(ConfigOptions.allowConnectionFromLan.notifier).update,
|
||||
),
|
||||
const SettingsDivider(),
|
||||
SettingsSection(
|
||||
experimental(t.config.section.tlsTricks),
|
||||
key: ConfigOptionSection._fragmentKey,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.enableTlsFragment),
|
||||
value: ref.watch(ConfigOptions.enableTlsFragment),
|
||||
onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.tlsFragmentSize),
|
||||
preferences: ref.watch(ConfigOptions.tlsFragmentSize.notifier),
|
||||
title: t.config.tlsFragmentSize,
|
||||
inputToValue: OptionalRange.tryParse,
|
||||
presentValue: (value) => value.present(t),
|
||||
formatInputValue: (value) => value.format(),
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.tlsFragmentSleep),
|
||||
preferences: ref.watch(ConfigOptions.tlsFragmentSleep.notifier),
|
||||
title: t.config.tlsFragmentSleep,
|
||||
inputToValue: OptionalRange.tryParse,
|
||||
presentValue: (value) => value.present(t),
|
||||
formatInputValue: (value) => value.format(),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.enableTlsMixedSniCase),
|
||||
value: ref.watch(ConfigOptions.enableTlsMixedSniCase),
|
||||
onChanged: ref.watch(ConfigOptions.enableTlsMixedSniCase.notifier).update,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(t.config.enableTlsPadding),
|
||||
value: ref.watch(ConfigOptions.enableTlsPadding),
|
||||
onChanged: ref.watch(ConfigOptions.enableTlsPadding.notifier).update,
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.tlsPaddingSize),
|
||||
preferences: ref.watch(ConfigOptions.tlsPaddingSize.notifier),
|
||||
title: t.config.tlsPaddingSize,
|
||||
inputToValue: OptionalRange.tryParse,
|
||||
presentValue: (value) => value.format(),
|
||||
formatInputValue: (value) => value.format(),
|
||||
),
|
||||
const SettingsDivider(),
|
||||
SettingsSection(experimental(t.config.section.warp)),
|
||||
WarpOptionsTiles(key: ConfigOptionSection._warpKey),
|
||||
const SettingsDivider(),
|
||||
SettingsSection(t.config.section.misc),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.connectionTestUrl),
|
||||
preferences: ref.watch(ConfigOptions.connectionTestUrl.notifier),
|
||||
title: t.config.connectionTestUrl,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(t.config.urlTestInterval),
|
||||
subtitle: Text(
|
||||
ref.watch(ConfigOptions.urlTestInterval).toApproximateTime(isRelativeToNow: false),
|
||||
// Разное - раскрывающаяся секция
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
leading: const Icon(Icons.more_horiz),
|
||||
title: Text(t.config.section.misc),
|
||||
initiallyExpanded: false,
|
||||
children: [
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.connectionTestUrl),
|
||||
preferences: ref.watch(ConfigOptions.connectionTestUrl.notifier),
|
||||
title: t.config.connectionTestUrl,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(t.config.urlTestInterval),
|
||||
subtitle: Text(
|
||||
ref.watch(ConfigOptions.urlTestInterval).toApproximateTime(isRelativeToNow: false),
|
||||
),
|
||||
onTap: () async {
|
||||
final urlTestInterval = await SettingsSliderDialog(
|
||||
title: t.config.urlTestInterval,
|
||||
initialValue: ref.watch(ConfigOptions.urlTestInterval).inMinutes.coerceIn(0, 60).toDouble(),
|
||||
onReset: ref.read(ConfigOptions.urlTestInterval.notifier).reset,
|
||||
min: 1,
|
||||
max: 60,
|
||||
divisions: 60,
|
||||
labelGen: (value) => Duration(minutes: value.toInt()).toApproximateTime(isRelativeToNow: false),
|
||||
).show(context);
|
||||
if (urlTestInterval == null) return;
|
||||
await ref.read(ConfigOptions.urlTestInterval.notifier).update(Duration(minutes: urlTestInterval.toInt()));
|
||||
},
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.clashApiPort),
|
||||
preferences: ref.watch(ConfigOptions.clashApiPort.notifier),
|
||||
title: t.config.clashApiPort,
|
||||
validateInput: isPort,
|
||||
digitsOnly: true,
|
||||
inputToValue: int.tryParse,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
final urlTestInterval = await SettingsSliderDialog(
|
||||
title: t.config.urlTestInterval,
|
||||
initialValue: ref.watch(ConfigOptions.urlTestInterval).inMinutes.coerceIn(0, 60).toDouble(),
|
||||
onReset: ref.read(ConfigOptions.urlTestInterval.notifier).reset,
|
||||
min: 1,
|
||||
max: 60,
|
||||
divisions: 60,
|
||||
labelGen: (value) => Duration(minutes: value.toInt()).toApproximateTime(isRelativeToNow: false),
|
||||
).show(context);
|
||||
if (urlTestInterval == null) return;
|
||||
await ref.read(ConfigOptions.urlTestInterval.notifier).update(Duration(minutes: urlTestInterval.toInt()));
|
||||
},
|
||||
),
|
||||
ValuePreferenceWidget(
|
||||
value: ref.watch(ConfigOptions.clashApiPort),
|
||||
preferences: ref.watch(ConfigOptions.clashApiPort.notifier),
|
||||
title: t.config.clashApiPort,
|
||||
validateInput: isPort,
|
||||
digitsOnly: true,
|
||||
inputToValue: int.tryParse,
|
||||
),
|
||||
|
||||
SwitchListTile(
|
||||
title: Text(experimental(t.config.useXrayCoreWhenPossible.Label)),
|
||||
subtitle: Text(t.config.useXrayCoreWhenPossible.Description),
|
||||
value: ref.watch(ConfigOptions.useXrayCoreWhenPossible),
|
||||
onChanged: ref.watch(ConfigOptions.useXrayCoreWhenPossible.notifier).update,
|
||||
),
|
||||
const Gap(24),
|
||||
],
|
||||
|
||||
@@ -5,13 +5,105 @@ import 'package:hiddify/core/localization/translations.dart';
|
||||
import 'package:hiddify/core/router/router.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
|
||||
import 'package:hiddify/features/config_option/overview/config_options_page.dart';
|
||||
import 'package:hiddify/features/settings/experimental_features_page.dart';
|
||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class QuickSettingsModal extends HookConsumerWidget {
|
||||
const QuickSettingsModal({super.key});
|
||||
|
||||
void _showHelpDialog(BuildContext context, Translations t) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(FluentIcons.info_24_regular, size: 28),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(t.config.quickSettings)),
|
||||
],
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHelpSection(
|
||||
icon: FluentIcons.server_20_filled,
|
||||
title: 'Прокси / VPN',
|
||||
description: 'Выберите режим подключения:\n\n'
|
||||
'• Прокси — использует локальный прокси-сервер для перенаправления трафика приложений\n'
|
||||
'• VPN — создает виртуальную частную сеть для защиты всего трафика устройства',
|
||||
),
|
||||
const Divider(height: 24),
|
||||
_buildHelpSection(
|
||||
icon: FluentIcons.shield_20_filled,
|
||||
title: t.config.setupWarp,
|
||||
description: 'Технология Cloudflare WARP для дополнительной защиты:\n\n'
|
||||
'• Шифрует DNS-запросы\n'
|
||||
'• Скрывает ваш IP-адрес\n'
|
||||
'• Улучшает скорость подключения в некоторых регионах\n'
|
||||
'• Требует первоначальной настройки с получением лицензионного ключа',
|
||||
),
|
||||
const Divider(height: 24),
|
||||
_buildHelpSection(
|
||||
icon: FluentIcons.shield_keyhole_20_filled,
|
||||
title: t.config.enableTlsFragment,
|
||||
description: 'Разбивает TLS-пакеты на фрагменты для обхода блокировок:\n\n'
|
||||
'• Помогает обойти DPI (глубокую проверку пакетов)\n'
|
||||
'• Работает на уровне TLS-рукопожатия\n'
|
||||
'• Может немного замедлить начальное соединение\n'
|
||||
'• Эффективно против систем блокировки на основе анализа SNI',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(t.window.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHelpSection({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String description,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
@@ -21,6 +113,28 @@ class QuickSettingsModal extends HookConsumerWidget {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Заголовок с кнопкой помощи
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 8, 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
t.config.quickSettings,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(FluentIcons.question_circle_24_regular),
|
||||
onPressed: () => _showHelpDialog(context, t),
|
||||
tooltip: 'Справка',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SegmentedButton(
|
||||
@@ -42,31 +156,25 @@ class QuickSettingsModal extends HookConsumerWidget {
|
||||
),
|
||||
const Gap(8),
|
||||
if (warpPrefaceCompleted)
|
||||
GestureDetector(
|
||||
onLongPress: () {
|
||||
ConfigOptionsRoute(section: ConfigOptionSection.warp.name).go(context);
|
||||
},
|
||||
child: SwitchListTile(
|
||||
value: ref.watch(ConfigOptions.enableWarp),
|
||||
onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update,
|
||||
title: Text(t.config.enableWarp),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: ref.watch(ConfigOptions.enableWarp),
|
||||
onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update,
|
||||
title: Text(t.config.enableWarp),
|
||||
)
|
||||
else
|
||||
ListTile(
|
||||
title: Text(t.config.setupWarp),
|
||||
trailing: const Icon(FluentIcons.chevron_right_24_regular),
|
||||
onTap: () => ConfigOptionsRoute(section: ConfigOptionSection.warp.name).go(context),
|
||||
),
|
||||
GestureDetector(
|
||||
onLongPress: () {
|
||||
ConfigOptionsRoute(section: ConfigOptionSection.fragment.name).go(context);
|
||||
},
|
||||
child: SwitchListTile(
|
||||
value: ref.watch(ConfigOptions.enableTlsFragment),
|
||||
onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
|
||||
title: Text(t.config.enableTlsFragment),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ExperimentalFeaturesPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: ref.watch(ConfigOptions.enableTlsFragment),
|
||||
onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
|
||||
title: Text(t.config.enableTlsFragment),
|
||||
),
|
||||
// SwitchListTile(
|
||||
// value: ref.watch(ConfigOptions.enableMux),
|
||||
|
||||
Reference in New Issue
Block a user