296 lines
13 KiB
Dart
296 lines
13 KiB
Dart
import 'package:dartx/dartx.dart';
|
|
import 'package:flutter/material.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/widget/adaptive_icon.dart';
|
|
import 'package:hiddify/core/widget/tip_card.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';
|
|
import 'package:hiddify/utils/utils.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:humanizer/humanizer.dart';
|
|
|
|
class ConfigOptionsPage extends HookConsumerWidget {
|
|
const ConfigOptionsPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final t = ref.watch(translationsProvider);
|
|
|
|
String experimental(String txt) {
|
|
return "$txt (${t.settings.experimental})";
|
|
}
|
|
|
|
return Scaffold(
|
|
body: CustomScrollView(
|
|
slivers: [
|
|
NestedAppBar(
|
|
title: Text(t.settings.config.pageTitle),
|
|
actions: [
|
|
PopupMenuButton(
|
|
icon: Icon(AdaptiveIcon(context).more),
|
|
itemBuilder: (context) {
|
|
return [
|
|
PopupMenuItem(
|
|
onTap: ref
|
|
.read(configOptionNotifierProvider.notifier)
|
|
.exportJsonToClipboard,
|
|
child: Text(t.general.addToClipboard),
|
|
),
|
|
PopupMenuItem(
|
|
child: Text(t.settings.config.resetBtn),
|
|
onTap: () async {
|
|
await ref
|
|
.read(configOptionNotifierProvider.notifier)
|
|
.resetOption();
|
|
},
|
|
),
|
|
];
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SliverList.list(
|
|
children: [
|
|
TipCard(message: t.settings.experimentalMsg),
|
|
ChoicePreferenceWidget(
|
|
selected: ref.watch(ConfigOptions.logLevel),
|
|
preferences: ref.watch(ConfigOptions.logLevel.notifier),
|
|
choices: LogLevel.choices,
|
|
title: t.settings.config.logLevel,
|
|
presentChoice: (value) => value.name.toUpperCase(),
|
|
),
|
|
const SettingsDivider(),
|
|
SettingsSection(t.settings.config.section.route),
|
|
SwitchListTile(
|
|
title: Text(experimental(t.settings.config.bypassLan)),
|
|
value: ref.watch(ConfigOptions.bypassLan),
|
|
onChanged: ref.watch(ConfigOptions.bypassLan.notifier).update,
|
|
),
|
|
SwitchListTile(
|
|
title: Text(t.settings.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.settings.config.ipv6Mode,
|
|
presentChoice: (value) => value.present(t),
|
|
),
|
|
const SettingsDivider(),
|
|
SettingsSection(t.settings.config.section.dns),
|
|
ValuePreferenceWidget(
|
|
value: ref.watch(ConfigOptions.remoteDnsAddress),
|
|
preferences: ref.watch(ConfigOptions.remoteDnsAddress.notifier),
|
|
title: t.settings.config.remoteDnsAddress,
|
|
),
|
|
ChoicePreferenceWidget(
|
|
selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy),
|
|
preferences:
|
|
ref.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
|
|
choices: DomainStrategy.values,
|
|
title: t.settings.config.remoteDnsDomainStrategy,
|
|
presentChoice: (value) => value.displayName,
|
|
),
|
|
ValuePreferenceWidget(
|
|
value: ref.watch(ConfigOptions.directDnsAddress),
|
|
preferences: ref.watch(ConfigOptions.directDnsAddress.notifier),
|
|
title: t.settings.config.directDnsAddress,
|
|
),
|
|
ChoicePreferenceWidget(
|
|
selected: ref.watch(ConfigOptions.directDnsDomainStrategy),
|
|
preferences:
|
|
ref.watch(ConfigOptions.directDnsDomainStrategy.notifier),
|
|
choices: DomainStrategy.values,
|
|
title: t.settings.config.directDnsDomainStrategy,
|
|
presentChoice: (value) => value.displayName,
|
|
),
|
|
SwitchListTile(
|
|
title: Text(t.settings.config.enableDnsRouting),
|
|
value: ref.watch(ConfigOptions.enableDnsRouting),
|
|
onChanged:
|
|
ref.watch(ConfigOptions.enableDnsRouting.notifier).update,
|
|
),
|
|
const SettingsDivider(),
|
|
SettingsSection(experimental(t.settings.config.section.mux)),
|
|
SwitchListTile(
|
|
title: Text(t.settings.config.enableMux),
|
|
value: ref.watch(ConfigOptions.enableMux),
|
|
onChanged: ref.watch(ConfigOptions.enableMux.notifier).update,
|
|
),
|
|
ChoicePreferenceWidget(
|
|
selected: ref.watch(ConfigOptions.muxProtocol),
|
|
preferences: ref.watch(ConfigOptions.muxProtocol.notifier),
|
|
choices: MuxProtocol.values,
|
|
title: t.settings.config.muxProtocol,
|
|
presentChoice: (value) => value.name,
|
|
),
|
|
ValuePreferenceWidget(
|
|
value: ref.watch(ConfigOptions.muxMaxStreams),
|
|
preferences: ref.watch(ConfigOptions.muxMaxStreams.notifier),
|
|
title: t.settings.config.muxMaxStreams,
|
|
inputToValue: int.tryParse,
|
|
digitsOnly: true,
|
|
),
|
|
const SettingsDivider(),
|
|
SettingsSection(t.settings.config.section.inbound),
|
|
ChoicePreferenceWidget(
|
|
selected: ref.watch(ConfigOptions.serviceMode),
|
|
preferences: ref.watch(ConfigOptions.serviceMode.notifier),
|
|
choices: ServiceMode.choices,
|
|
title: t.settings.config.serviceMode,
|
|
presentChoice: (value) => value.present(t),
|
|
),
|
|
SwitchListTile(
|
|
title: Text(t.settings.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.settings.config.tunImplementation,
|
|
presentChoice: (value) => value.name,
|
|
),
|
|
ValuePreferenceWidget(
|
|
value: ref.watch(ConfigOptions.mixedPort),
|
|
preferences: ref.watch(ConfigOptions.mixedPort.notifier),
|
|
title: t.settings.config.mixedPort,
|
|
inputToValue: int.tryParse,
|
|
digitsOnly: true,
|
|
validateInput: isPort,
|
|
),
|
|
ValuePreferenceWidget(
|
|
value: ref.watch(ConfigOptions.localDnsPort),
|
|
preferences: ref.watch(ConfigOptions.localDnsPort.notifier),
|
|
title: t.settings.config.localDnsPort,
|
|
inputToValue: int.tryParse,
|
|
digitsOnly: true,
|
|
validateInput: isPort,
|
|
),
|
|
SwitchListTile(
|
|
title: Text(
|
|
experimental(t.settings.config.allowConnectionFromLan),
|
|
),
|
|
value: ref.watch(ConfigOptions.allowConnectionFromLan),
|
|
onChanged: ref
|
|
.read(ConfigOptions.allowConnectionFromLan.notifier)
|
|
.update,
|
|
),
|
|
const SettingsDivider(),
|
|
SettingsSection(t.settings.config.section.tlsTricks),
|
|
SwitchListTile(
|
|
title: Text(experimental(t.settings.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.settings.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.settings.config.tlsFragmentSleep,
|
|
inputToValue: OptionalRange.tryParse,
|
|
presentValue: (value) => value.present(t),
|
|
formatInputValue: (value) => value.format(),
|
|
),
|
|
SwitchListTile(
|
|
title: Text(
|
|
experimental(t.settings.config.enableTlsMixedSniCase),
|
|
),
|
|
value: ref.watch(ConfigOptions.enableTlsMixedSniCase),
|
|
onChanged: ref
|
|
.watch(ConfigOptions.enableTlsMixedSniCase.notifier)
|
|
.update,
|
|
),
|
|
SwitchListTile(
|
|
title: Text(experimental(t.settings.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.settings.config.tlsPaddingSize,
|
|
inputToValue: OptionalRange.tryParse,
|
|
presentValue: (value) => value.format(),
|
|
formatInputValue: (value) => value.format(),
|
|
),
|
|
const SettingsDivider(),
|
|
SettingsSection(experimental(t.settings.config.section.warp)),
|
|
const WarpOptionsTiles(),
|
|
const SettingsDivider(),
|
|
SettingsSection(t.settings.config.section.misc),
|
|
ValuePreferenceWidget(
|
|
value: ref.watch(ConfigOptions.connectionTestUrl),
|
|
preferences:
|
|
ref.watch(ConfigOptions.connectionTestUrl.notifier),
|
|
title: t.settings.config.connectionTestUrl,
|
|
),
|
|
ListTile(
|
|
title: Text(t.settings.config.urlTestInterval),
|
|
subtitle: Text(
|
|
ref
|
|
.watch(ConfigOptions.urlTestInterval)
|
|
.toApproximateTime(isRelativeToNow: false),
|
|
),
|
|
onTap: () async {
|
|
final urlTestInterval = await SettingsSliderDialog(
|
|
title: t.settings.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.settings.config.clashApiPort,
|
|
validateInput: isPort,
|
|
digitsOnly: true,
|
|
inputToValue: int.tryParse,
|
|
),
|
|
const Gap(24),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|