Change mapping and bug fixes
This commit is contained in:
@@ -15,11 +15,9 @@ DioHttpClient httpClient(HttpClientRef ref) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
configOptionNotifierProvider,
|
configOptionNotifierProvider.selectAsync((data) => data.mixedPort),
|
||||||
(_, next) {
|
(_, next) async {
|
||||||
if (next case AsyncData(value: final options)) {
|
client.setProxyPort(await next);
|
||||||
client.setProxyPort(options.mixedPort);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
);
|
);
|
||||||
|
|||||||
54
lib/core/model/optional_range.dart
Normal file
54
lib/core/model/optional_range.dart
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
|
import 'package:dartx/dartx.dart';
|
||||||
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
|
|
||||||
|
part 'optional_range.mapper.dart';
|
||||||
|
|
||||||
|
@MappableClass()
|
||||||
|
class OptionalRange with OptionalRangeMappable {
|
||||||
|
const OptionalRange({this.min, this.max});
|
||||||
|
|
||||||
|
final int? min;
|
||||||
|
final int? max;
|
||||||
|
|
||||||
|
String format() => [min, max].whereNotNull().join("-");
|
||||||
|
String present(TranslationsEn t) =>
|
||||||
|
format().isEmpty ? t.general.notSet : format();
|
||||||
|
|
||||||
|
factory OptionalRange._fromString(
|
||||||
|
String input, {
|
||||||
|
bool allowEmpty = true,
|
||||||
|
}) =>
|
||||||
|
switch (input.split("-")) {
|
||||||
|
[final String val] when val.isEmpty && allowEmpty =>
|
||||||
|
const OptionalRange(),
|
||||||
|
[final String min] => OptionalRange(min: int.parse(min)),
|
||||||
|
[final String min, final String max] => OptionalRange(
|
||||||
|
min: int.parse(min),
|
||||||
|
max: int.parse(max),
|
||||||
|
),
|
||||||
|
_ => throw Exception("Invalid range: $input"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static OptionalRange? tryParse(
|
||||||
|
String input, {
|
||||||
|
bool allowEmpty = false,
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
return OptionalRange._fromString(input);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionalRangeJsonMapper extends SimpleMapper<OptionalRange> {
|
||||||
|
const OptionalRangeJsonMapper();
|
||||||
|
|
||||||
|
@override
|
||||||
|
OptionalRange decode(dynamic value) =>
|
||||||
|
OptionalRange._fromString(value as String);
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic encode(OptionalRange self) => self.format();
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import 'package:dartx/dartx.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
|
||||||
|
|
||||||
part 'range.freezed.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class RangeWithOptionalCeil with _$RangeWithOptionalCeil {
|
|
||||||
const RangeWithOptionalCeil._();
|
|
||||||
|
|
||||||
const factory RangeWithOptionalCeil({
|
|
||||||
int? min,
|
|
||||||
int? max,
|
|
||||||
}) = _RangeWithOptionalCeil;
|
|
||||||
|
|
||||||
String format() => [min, max].whereNotNull().join("-");
|
|
||||||
String present(TranslationsEn t) =>
|
|
||||||
format().isEmpty ? t.general.notSet : format();
|
|
||||||
|
|
||||||
factory RangeWithOptionalCeil._fromString(
|
|
||||||
String input, {
|
|
||||||
bool allowEmpty = true,
|
|
||||||
}) =>
|
|
||||||
switch (input.split("-")) {
|
|
||||||
[final String val] when val.isEmpty && allowEmpty =>
|
|
||||||
const RangeWithOptionalCeil(),
|
|
||||||
[final String min] => RangeWithOptionalCeil(min: int.parse(min)),
|
|
||||||
[final String min, final String max] => RangeWithOptionalCeil(
|
|
||||||
min: int.parse(min),
|
|
||||||
max: int.parse(max),
|
|
||||||
),
|
|
||||||
_ => throw Exception("Invalid range: $input"),
|
|
||||||
};
|
|
||||||
|
|
||||||
static RangeWithOptionalCeil? tryParse(
|
|
||||||
String input, {
|
|
||||||
bool allowEmpty = false,
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
return RangeWithOptionalCeil._fromString(input);
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RangeWithOptionalCeilJsonConverter
|
|
||||||
implements JsonConverter<RangeWithOptionalCeil, String> {
|
|
||||||
const RangeWithOptionalCeilJsonConverter();
|
|
||||||
|
|
||||||
@override
|
|
||||||
RangeWithOptionalCeil fromJson(String json) =>
|
|
||||||
RangeWithOptionalCeil._fromString(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toJson(RangeWithOptionalCeil object) => object.format();
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
|
|
||||||
class IntervalInSecondsConverter implements JsonConverter<Duration, int> {
|
class IntervalInSecondsMapper extends SimpleMapper<Duration> {
|
||||||
const IntervalInSecondsConverter();
|
const IntervalInSecondsMapper();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Duration fromJson(int json) => Duration(seconds: json);
|
Duration decode(dynamic value) => Duration(seconds: value as int);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int toJson(Duration object) => object.inSeconds;
|
dynamic encode(Duration self) => self.inSeconds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import 'package:hiddify/core/model/region.dart';
|
|||||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_patch.dart';
|
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
|
||||||
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_rule.dart';
|
import 'package:hiddify/singbox/model/singbox_rule.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
@@ -36,7 +34,7 @@ class ConfigOptionRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Either<ConfigOptionFailure, ConfigOptionEntity> getConfigOption() {
|
Either<ConfigOptionFailure, ConfigOptionEntity> getConfigOption() {
|
||||||
try {
|
try {
|
||||||
final map = ConfigOptionEntity.initial.toJson();
|
final map = ConfigOptionEntity.initial().toMap();
|
||||||
for (final key in map.keys) {
|
for (final key in map.keys) {
|
||||||
final persisted = preferences.get(key);
|
final persisted = preferences.get(key);
|
||||||
if (persisted != null) {
|
if (persisted != null) {
|
||||||
@@ -51,7 +49,7 @@ class ConfigOptionRepositoryImpl
|
|||||||
map[key] = persisted;
|
map[key] = persisted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final options = ConfigOptionEntity.fromJson(map);
|
final options = ConfigOptionEntityMapper.fromMap(map);
|
||||||
return right(options);
|
return right(options);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
return left(ConfigOptionUnexpectedFailure(error, stackTrace));
|
return left(ConfigOptionUnexpectedFailure(error, stackTrace));
|
||||||
@@ -64,7 +62,7 @@ class ConfigOptionRepositoryImpl
|
|||||||
) {
|
) {
|
||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
() async {
|
() async {
|
||||||
final map = patch.toJson();
|
final map = patch.toMap();
|
||||||
await updateByJson(map);
|
await updateByJson(map);
|
||||||
return right(unit);
|
return right(unit);
|
||||||
},
|
},
|
||||||
@@ -76,7 +74,7 @@ class ConfigOptionRepositoryImpl
|
|||||||
TaskEither<ConfigOptionFailure, Unit> resetConfigOption() {
|
TaskEither<ConfigOptionFailure, Unit> resetConfigOption() {
|
||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
() async {
|
() async {
|
||||||
final map = ConfigOptionEntity.initial.toJson();
|
final map = ConfigOptionEntity.initial().toMap();
|
||||||
await updateByJson(map);
|
await updateByJson(map);
|
||||||
return right(unit);
|
return right(unit);
|
||||||
},
|
},
|
||||||
@@ -88,7 +86,7 @@ class ConfigOptionRepositoryImpl
|
|||||||
Future<void> updateByJson(
|
Future<void> updateByJson(
|
||||||
Map<String, dynamic> options,
|
Map<String, dynamic> options,
|
||||||
) async {
|
) async {
|
||||||
final map = ConfigOptionEntity.initial.toJson();
|
final map = ConfigOptionEntity.initial().toMap();
|
||||||
for (final key in map.keys) {
|
for (final key in map.keys) {
|
||||||
final value = options[key];
|
final value = options[key];
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -172,48 +170,7 @@ class SingBoxConfigOptionRepositoryImpl
|
|||||||
|
|
||||||
final persisted =
|
final persisted =
|
||||||
optionsRepository.getConfigOption().getOrElse((l) => throw l);
|
optionsRepository.getConfigOption().getOrElse((l) => throw l);
|
||||||
final singboxConfigOption = SingboxConfigOption(
|
final singboxConfigOption = persisted.toSingbox(
|
||||||
executeConfigAsIs: false,
|
|
||||||
logLevel: persisted.logLevel,
|
|
||||||
resolveDestination: persisted.resolveDestination,
|
|
||||||
ipv6Mode: persisted.ipv6Mode,
|
|
||||||
remoteDnsAddress: persisted.remoteDnsAddress,
|
|
||||||
remoteDnsDomainStrategy: persisted.remoteDnsDomainStrategy,
|
|
||||||
directDnsAddress: persisted.directDnsAddress,
|
|
||||||
directDnsDomainStrategy: persisted.directDnsDomainStrategy,
|
|
||||||
mixedPort: persisted.mixedPort,
|
|
||||||
localDnsPort: persisted.localDnsPort,
|
|
||||||
tunImplementation: persisted.tunImplementation,
|
|
||||||
mtu: persisted.mtu,
|
|
||||||
strictRoute: persisted.strictRoute,
|
|
||||||
connectionTestUrl: persisted.connectionTestUrl,
|
|
||||||
urlTestInterval: persisted.urlTestInterval,
|
|
||||||
enableClashApi: persisted.enableClashApi,
|
|
||||||
clashApiPort: persisted.clashApiPort,
|
|
||||||
enableTun: persisted.serviceMode == ServiceMode.tun,
|
|
||||||
enableTunService: persisted.serviceMode == ServiceMode.tunService,
|
|
||||||
setSystemProxy: persisted.serviceMode == ServiceMode.systemProxy,
|
|
||||||
bypassLan: persisted.bypassLan,
|
|
||||||
allowConnectionFromLan: persisted.allowConnectionFromLan,
|
|
||||||
enableFakeDns: persisted.enableFakeDns,
|
|
||||||
enableDnsRouting: persisted.enableDnsRouting,
|
|
||||||
independentDnsCache: persisted.independentDnsCache,
|
|
||||||
enableTlsFragment: persisted.enableTlsFragment,
|
|
||||||
tlsFragmentSize: persisted.tlsFragmentSize,
|
|
||||||
tlsFragmentSleep: persisted.tlsFragmentSleep,
|
|
||||||
enableTlsMixedSniCase: persisted.enableTlsMixedSniCase,
|
|
||||||
enableTlsPadding: persisted.enableTlsPadding,
|
|
||||||
tlsPaddingSize: persisted.tlsPaddingSize,
|
|
||||||
enableMux: persisted.enableMux,
|
|
||||||
muxPadding: persisted.muxPadding,
|
|
||||||
muxMaxStreams: persisted.muxMaxStreams,
|
|
||||||
muxProtocol: persisted.muxProtocol,
|
|
||||||
enableWarp: persisted.enableWarp,
|
|
||||||
warpDetourMode: persisted.warpDetourMode,
|
|
||||||
warpLicenseKey: persisted.warpLicenseKey,
|
|
||||||
warpCleanIp: persisted.warpCleanIp,
|
|
||||||
warpPort: persisted.warpPort,
|
|
||||||
warpNoise: persisted.warpNoise,
|
|
||||||
geoipPath: geoAssetPathResolver.relativePath(
|
geoipPath: geoAssetPathResolver.relativePath(
|
||||||
geoAssets.geoip.providerName,
|
geoAssets.geoip.providerName,
|
||||||
geoAssets.geoip.fileName,
|
geoAssets.geoip.fileName,
|
||||||
|
|||||||
@@ -1,75 +1,107 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
import 'package:hiddify/core/model/range.dart';
|
import 'package:hiddify/core/model/optional_range.dart';
|
||||||
import 'package:hiddify/core/utils/json_converters.dart';
|
import 'package:hiddify/core/utils/json_converters.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_patch.dart';
|
|
||||||
import 'package:hiddify/features/log/model/log_level.dart';
|
import 'package:hiddify/features/log/model/log_level.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||||
|
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
||||||
|
import 'package:hiddify/singbox/model/singbox_rule.dart';
|
||||||
import 'package:hiddify/utils/platform_utils.dart';
|
import 'package:hiddify/utils/platform_utils.dart';
|
||||||
|
|
||||||
part 'config_option_entity.freezed.dart';
|
part 'config_option_entity.mapper.dart';
|
||||||
part 'config_option_entity.g.dart';
|
|
||||||
|
|
||||||
@freezed
|
@MappableClass(
|
||||||
class ConfigOptionEntity with _$ConfigOptionEntity {
|
caseStyle: CaseStyle.paramCase,
|
||||||
const ConfigOptionEntity._();
|
includeCustomMappers: [
|
||||||
|
OptionalRangeJsonMapper(),
|
||||||
|
IntervalInSecondsMapper(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class ConfigOptionEntity with ConfigOptionEntityMappable {
|
||||||
|
const ConfigOptionEntity({
|
||||||
|
required this.serviceMode,
|
||||||
|
this.logLevel = LogLevel.warn,
|
||||||
|
this.resolveDestination = false,
|
||||||
|
this.ipv6Mode = IPv6Mode.disable,
|
||||||
|
this.remoteDnsAddress = "http://1.1.1.1",
|
||||||
|
this.remoteDnsDomainStrategy = DomainStrategy.auto,
|
||||||
|
this.directDnsAddress = "1.1.1.1",
|
||||||
|
this.directDnsDomainStrategy = DomainStrategy.auto,
|
||||||
|
this.mixedPort = 2334,
|
||||||
|
this.localDnsPort = 6450,
|
||||||
|
this.tunImplementation = TunImplementation.mixed,
|
||||||
|
this.mtu = 9000,
|
||||||
|
this.strictRoute = true,
|
||||||
|
this.connectionTestUrl = "http://cp.cloudflare.com/",
|
||||||
|
this.urlTestInterval = const Duration(minutes: 10),
|
||||||
|
this.enableClashApi = true,
|
||||||
|
this.clashApiPort = 6756,
|
||||||
|
this.bypassLan = false,
|
||||||
|
this.allowConnectionFromLan = false,
|
||||||
|
this.enableFakeDns = false,
|
||||||
|
this.enableDnsRouting = true,
|
||||||
|
this.independentDnsCache = true,
|
||||||
|
this.enableTlsFragment = false,
|
||||||
|
this.tlsFragmentSize = const OptionalRange(min: 10, max: 100),
|
||||||
|
this.tlsFragmentSleep = const OptionalRange(min: 50, max: 200),
|
||||||
|
this.enableTlsMixedSniCase = false,
|
||||||
|
this.enableTlsPadding = false,
|
||||||
|
this.tlsPaddingSize = const OptionalRange(min: 100, max: 200),
|
||||||
|
this.enableMux = false,
|
||||||
|
this.muxPadding = false,
|
||||||
|
this.muxMaxStreams = 8,
|
||||||
|
this.muxProtocol = MuxProtocol.h2mux,
|
||||||
|
this.enableWarp = false,
|
||||||
|
this.warpDetourMode = WarpDetourMode.outbound,
|
||||||
|
this.warpLicenseKey = "",
|
||||||
|
this.warpCleanIp = "auto",
|
||||||
|
this.warpPort = 0,
|
||||||
|
this.warpNoise = const OptionalRange(),
|
||||||
|
});
|
||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
final ServiceMode serviceMode;
|
||||||
const factory ConfigOptionEntity({
|
final LogLevel logLevel;
|
||||||
required ServiceMode serviceMode,
|
final bool resolveDestination;
|
||||||
@Default(LogLevel.warn) LogLevel logLevel,
|
@MappableField(key: "ipv6-mode")
|
||||||
@Default(false) bool resolveDestination,
|
final IPv6Mode ipv6Mode;
|
||||||
@Default(IPv6Mode.disable) IPv6Mode ipv6Mode,
|
final String remoteDnsAddress;
|
||||||
@Default("udp://1.1.1.1") String remoteDnsAddress,
|
final DomainStrategy remoteDnsDomainStrategy;
|
||||||
@Default(DomainStrategy.auto) DomainStrategy remoteDnsDomainStrategy,
|
final String directDnsAddress;
|
||||||
@Default("1.1.1.1") String directDnsAddress,
|
final DomainStrategy directDnsDomainStrategy;
|
||||||
@Default(DomainStrategy.auto) DomainStrategy directDnsDomainStrategy,
|
final int mixedPort;
|
||||||
@Default(2334) int mixedPort,
|
final int localDnsPort;
|
||||||
@Default(6450) int localDnsPort,
|
final TunImplementation tunImplementation;
|
||||||
@Default(TunImplementation.mixed) TunImplementation tunImplementation,
|
final int mtu;
|
||||||
@Default(9000) int mtu,
|
final bool strictRoute;
|
||||||
@Default(true) bool strictRoute,
|
final String connectionTestUrl;
|
||||||
@Default("http://cp.cloudflare.com/") String connectionTestUrl,
|
final Duration urlTestInterval;
|
||||||
@IntervalInSecondsConverter()
|
final bool enableClashApi;
|
||||||
@Default(Duration(minutes: 10))
|
final int clashApiPort;
|
||||||
Duration urlTestInterval,
|
final bool bypassLan;
|
||||||
@Default(true) bool enableClashApi,
|
final bool allowConnectionFromLan;
|
||||||
@Default(6756) int clashApiPort,
|
final bool enableFakeDns;
|
||||||
@Default(false) bool bypassLan,
|
final bool enableDnsRouting;
|
||||||
@Default(false) bool allowConnectionFromLan,
|
final bool independentDnsCache;
|
||||||
@Default(false) bool enableFakeDns,
|
final bool enableTlsFragment;
|
||||||
@Default(true) bool enableDnsRouting,
|
final OptionalRange tlsFragmentSize;
|
||||||
@Default(true) bool independentDnsCache,
|
final OptionalRange tlsFragmentSleep;
|
||||||
@Default(false) bool enableTlsFragment,
|
final bool enableTlsMixedSniCase;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
final bool enableTlsPadding;
|
||||||
@Default(RangeWithOptionalCeil(min: 10, max: 100))
|
final OptionalRange tlsPaddingSize;
|
||||||
RangeWithOptionalCeil tlsFragmentSize,
|
final bool enableMux;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
final bool muxPadding;
|
||||||
@Default(RangeWithOptionalCeil(min: 50, max: 200))
|
final int muxMaxStreams;
|
||||||
RangeWithOptionalCeil tlsFragmentSleep,
|
final MuxProtocol muxProtocol;
|
||||||
@Default(false) bool enableTlsMixedSniCase,
|
final bool enableWarp;
|
||||||
@Default(false) bool enableTlsPadding,
|
final WarpDetourMode warpDetourMode;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
final String warpLicenseKey;
|
||||||
@Default(RangeWithOptionalCeil(min: 100, max: 200))
|
final String warpCleanIp;
|
||||||
RangeWithOptionalCeil tlsPaddingSize,
|
final int warpPort;
|
||||||
@Default(false) bool enableMux,
|
final OptionalRange warpNoise;
|
||||||
@Default(false) bool muxPadding,
|
|
||||||
@Default(8) int muxMaxStreams,
|
|
||||||
@Default(MuxProtocol.h2mux) MuxProtocol muxProtocol,
|
|
||||||
@Default(false) bool enableWarp,
|
|
||||||
@Default(WarpDetourMode.outbound) WarpDetourMode warpDetourMode,
|
|
||||||
@Default("") String warpLicenseKey,
|
|
||||||
@Default("auto") String warpCleanIp,
|
|
||||||
@Default(0) int warpPort,
|
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
|
||||||
@Default(RangeWithOptionalCeil())
|
|
||||||
RangeWithOptionalCeil warpNoise,
|
|
||||||
}) = _ConfigOptionEntity;
|
|
||||||
|
|
||||||
static ConfigOptionEntity initial = ConfigOptionEntity(
|
factory ConfigOptionEntity.initial() =>
|
||||||
serviceMode: ServiceMode.defaultMode,
|
ConfigOptionEntity(serviceMode: ServiceMode.defaultMode);
|
||||||
);
|
|
||||||
|
|
||||||
bool hasExperimentalOptions() {
|
bool hasExperimentalOptions() {
|
||||||
if (PlatformUtils.isDesktop && serviceMode == ServiceMode.tun) {
|
if (PlatformUtils.isDesktop && serviceMode == ServiceMode.tun) {
|
||||||
@@ -88,56 +120,157 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
|||||||
|
|
||||||
String format() {
|
String format() {
|
||||||
const encoder = JsonEncoder.withIndent(' ');
|
const encoder = JsonEncoder.withIndent(' ');
|
||||||
return encoder.convert(toJson());
|
return encoder.convert(toMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigOptionEntity patch(ConfigOptionPatch patch) {
|
ConfigOptionEntity patch(ConfigOptionPatch patch) {
|
||||||
return copyWith(
|
return copyWith.$delta(patch.delta());
|
||||||
serviceMode: patch.serviceMode ?? serviceMode,
|
|
||||||
logLevel: patch.logLevel ?? logLevel,
|
|
||||||
resolveDestination: patch.resolveDestination ?? resolveDestination,
|
|
||||||
ipv6Mode: patch.ipv6Mode ?? ipv6Mode,
|
|
||||||
remoteDnsAddress: patch.remoteDnsAddress ?? remoteDnsAddress,
|
|
||||||
remoteDnsDomainStrategy:
|
|
||||||
patch.remoteDnsDomainStrategy ?? remoteDnsDomainStrategy,
|
|
||||||
directDnsAddress: patch.directDnsAddress ?? directDnsAddress,
|
|
||||||
directDnsDomainStrategy:
|
|
||||||
patch.directDnsDomainStrategy ?? directDnsDomainStrategy,
|
|
||||||
mixedPort: patch.mixedPort ?? mixedPort,
|
|
||||||
localDnsPort: patch.localDnsPort ?? localDnsPort,
|
|
||||||
tunImplementation: patch.tunImplementation ?? tunImplementation,
|
|
||||||
mtu: patch.mtu ?? mtu,
|
|
||||||
strictRoute: patch.strictRoute ?? strictRoute,
|
|
||||||
connectionTestUrl: patch.connectionTestUrl ?? connectionTestUrl,
|
|
||||||
urlTestInterval: patch.urlTestInterval ?? urlTestInterval,
|
|
||||||
enableClashApi: patch.enableClashApi ?? enableClashApi,
|
|
||||||
clashApiPort: patch.clashApiPort ?? clashApiPort,
|
|
||||||
bypassLan: patch.bypassLan ?? bypassLan,
|
|
||||||
allowConnectionFromLan:
|
|
||||||
patch.allowConnectionFromLan ?? allowConnectionFromLan,
|
|
||||||
enableFakeDns: patch.enableFakeDns ?? enableFakeDns,
|
|
||||||
enableDnsRouting: patch.enableDnsRouting ?? enableDnsRouting,
|
|
||||||
independentDnsCache: patch.independentDnsCache ?? independentDnsCache,
|
|
||||||
enableTlsFragment: patch.enableTlsFragment ?? enableTlsFragment,
|
|
||||||
tlsFragmentSize: patch.tlsFragmentSize ?? tlsFragmentSize,
|
|
||||||
tlsFragmentSleep: patch.tlsFragmentSleep ?? tlsFragmentSleep,
|
|
||||||
enableTlsMixedSniCase:
|
|
||||||
patch.enableTlsMixedSniCase ?? enableTlsMixedSniCase,
|
|
||||||
enableTlsPadding: patch.enableTlsPadding ?? enableTlsPadding,
|
|
||||||
tlsPaddingSize: patch.tlsPaddingSize ?? tlsPaddingSize,
|
|
||||||
enableMux: patch.enableMux ?? enableMux,
|
|
||||||
muxPadding: patch.muxPadding ?? muxPadding,
|
|
||||||
muxMaxStreams: patch.muxMaxStreams ?? muxMaxStreams,
|
|
||||||
muxProtocol: patch.muxProtocol ?? muxProtocol,
|
|
||||||
enableWarp: patch.enableWarp ?? enableWarp,
|
|
||||||
warpDetourMode: patch.warpDetourMode ?? warpDetourMode,
|
|
||||||
warpLicenseKey: patch.warpLicenseKey ?? warpLicenseKey,
|
|
||||||
warpCleanIp: patch.warpCleanIp ?? warpCleanIp,
|
|
||||||
warpPort: patch.warpPort ?? warpPort,
|
|
||||||
warpNoise: patch.warpNoise ?? warpNoise,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ConfigOptionEntity.fromJson(Map<String, dynamic> json) =>
|
SingboxConfigOption toSingbox({
|
||||||
_$ConfigOptionEntityFromJson(json);
|
required String geoipPath,
|
||||||
|
required String geositePath,
|
||||||
|
required List<SingboxRule> rules,
|
||||||
|
}) {
|
||||||
|
return SingboxConfigOption(
|
||||||
|
executeConfigAsIs: false,
|
||||||
|
logLevel: logLevel,
|
||||||
|
resolveDestination: resolveDestination,
|
||||||
|
ipv6Mode: ipv6Mode,
|
||||||
|
remoteDnsAddress: remoteDnsAddress,
|
||||||
|
remoteDnsDomainStrategy: remoteDnsDomainStrategy,
|
||||||
|
directDnsAddress: directDnsAddress,
|
||||||
|
directDnsDomainStrategy: directDnsDomainStrategy,
|
||||||
|
mixedPort: mixedPort,
|
||||||
|
localDnsPort: localDnsPort,
|
||||||
|
tunImplementation: tunImplementation,
|
||||||
|
mtu: mtu,
|
||||||
|
strictRoute: strictRoute,
|
||||||
|
connectionTestUrl: connectionTestUrl,
|
||||||
|
urlTestInterval: urlTestInterval,
|
||||||
|
enableClashApi: enableClashApi,
|
||||||
|
clashApiPort: clashApiPort,
|
||||||
|
enableTun: serviceMode == ServiceMode.tun,
|
||||||
|
enableTunService: serviceMode == ServiceMode.tunService,
|
||||||
|
setSystemProxy: serviceMode == ServiceMode.systemProxy,
|
||||||
|
bypassLan: bypassLan,
|
||||||
|
allowConnectionFromLan: allowConnectionFromLan,
|
||||||
|
enableFakeDns: enableFakeDns,
|
||||||
|
enableDnsRouting: enableDnsRouting,
|
||||||
|
independentDnsCache: independentDnsCache,
|
||||||
|
enableTlsFragment: enableTlsFragment,
|
||||||
|
tlsFragmentSize: tlsFragmentSize,
|
||||||
|
tlsFragmentSleep: tlsFragmentSleep,
|
||||||
|
enableTlsMixedSniCase: enableTlsMixedSniCase,
|
||||||
|
enableTlsPadding: enableTlsPadding,
|
||||||
|
tlsPaddingSize: tlsPaddingSize,
|
||||||
|
enableMux: enableMux,
|
||||||
|
muxPadding: muxPadding,
|
||||||
|
muxMaxStreams: muxMaxStreams,
|
||||||
|
muxProtocol: muxProtocol,
|
||||||
|
enableWarp: enableWarp,
|
||||||
|
warpDetourMode: warpDetourMode,
|
||||||
|
warpLicenseKey: warpLicenseKey,
|
||||||
|
warpCleanIp: warpCleanIp,
|
||||||
|
warpPort: warpPort,
|
||||||
|
warpNoise: warpNoise,
|
||||||
|
geoipPath: geoipPath,
|
||||||
|
geositePath: geositePath,
|
||||||
|
rules: rules,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MappableClass(
|
||||||
|
caseStyle: CaseStyle.paramCase,
|
||||||
|
ignoreNull: true,
|
||||||
|
includeCustomMappers: [
|
||||||
|
OptionalRangeJsonMapper(),
|
||||||
|
IntervalInSecondsMapper(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class ConfigOptionPatch with ConfigOptionPatchMappable {
|
||||||
|
const ConfigOptionPatch({
|
||||||
|
this.serviceMode,
|
||||||
|
this.logLevel,
|
||||||
|
this.resolveDestination,
|
||||||
|
this.ipv6Mode,
|
||||||
|
this.remoteDnsAddress,
|
||||||
|
this.remoteDnsDomainStrategy,
|
||||||
|
this.directDnsAddress,
|
||||||
|
this.directDnsDomainStrategy,
|
||||||
|
this.mixedPort,
|
||||||
|
this.localDnsPort,
|
||||||
|
this.tunImplementation,
|
||||||
|
this.mtu,
|
||||||
|
this.strictRoute,
|
||||||
|
this.connectionTestUrl,
|
||||||
|
this.urlTestInterval,
|
||||||
|
this.enableClashApi,
|
||||||
|
this.clashApiPort,
|
||||||
|
this.bypassLan,
|
||||||
|
this.allowConnectionFromLan,
|
||||||
|
this.enableFakeDns,
|
||||||
|
this.enableDnsRouting,
|
||||||
|
this.independentDnsCache,
|
||||||
|
this.enableTlsFragment,
|
||||||
|
this.tlsFragmentSize,
|
||||||
|
this.tlsFragmentSleep,
|
||||||
|
this.enableTlsMixedSniCase,
|
||||||
|
this.enableTlsPadding,
|
||||||
|
this.tlsPaddingSize,
|
||||||
|
this.enableMux,
|
||||||
|
this.muxPadding,
|
||||||
|
this.muxMaxStreams,
|
||||||
|
this.muxProtocol,
|
||||||
|
this.enableWarp,
|
||||||
|
this.warpDetourMode,
|
||||||
|
this.warpLicenseKey,
|
||||||
|
this.warpCleanIp,
|
||||||
|
this.warpPort,
|
||||||
|
this.warpNoise,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ServiceMode? serviceMode;
|
||||||
|
final LogLevel? logLevel;
|
||||||
|
final bool? resolveDestination;
|
||||||
|
@MappableField(key: "ipv6-mode")
|
||||||
|
final IPv6Mode? ipv6Mode;
|
||||||
|
final String? remoteDnsAddress;
|
||||||
|
final DomainStrategy? remoteDnsDomainStrategy;
|
||||||
|
final String? directDnsAddress;
|
||||||
|
final DomainStrategy? directDnsDomainStrategy;
|
||||||
|
final int? mixedPort;
|
||||||
|
final int? localDnsPort;
|
||||||
|
final TunImplementation? tunImplementation;
|
||||||
|
final int? mtu;
|
||||||
|
final bool? strictRoute;
|
||||||
|
final String? connectionTestUrl;
|
||||||
|
final Duration? urlTestInterval;
|
||||||
|
final bool? enableClashApi;
|
||||||
|
final int? clashApiPort;
|
||||||
|
final bool? bypassLan;
|
||||||
|
final bool? allowConnectionFromLan;
|
||||||
|
final bool? enableFakeDns;
|
||||||
|
final bool? enableDnsRouting;
|
||||||
|
final bool? independentDnsCache;
|
||||||
|
final bool? enableTlsFragment;
|
||||||
|
final OptionalRange? tlsFragmentSize;
|
||||||
|
final OptionalRange? tlsFragmentSleep;
|
||||||
|
final bool? enableTlsMixedSniCase;
|
||||||
|
final bool? enableTlsPadding;
|
||||||
|
final OptionalRange? tlsPaddingSize;
|
||||||
|
final bool? enableMux;
|
||||||
|
final bool? muxPadding;
|
||||||
|
final int? muxMaxStreams;
|
||||||
|
final MuxProtocol? muxProtocol;
|
||||||
|
final bool? enableWarp;
|
||||||
|
final WarpDetourMode? warpDetourMode;
|
||||||
|
final String? warpLicenseKey;
|
||||||
|
final String? warpCleanIp;
|
||||||
|
final int? warpPort;
|
||||||
|
final OptionalRange? warpNoise;
|
||||||
|
|
||||||
|
Map<String, dynamic> delta() =>
|
||||||
|
toMap()..removeWhere((key, value) => value == null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:hiddify/core/model/range.dart';
|
|
||||||
import 'package:hiddify/core/utils/json_converters.dart';
|
|
||||||
import 'package:hiddify/features/log/model/log_level.dart';
|
|
||||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
|
||||||
|
|
||||||
part 'config_option_patch.freezed.dart';
|
|
||||||
part 'config_option_patch.g.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ConfigOptionPatch with _$ConfigOptionPatch {
|
|
||||||
const ConfigOptionPatch._();
|
|
||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
|
||||||
const factory ConfigOptionPatch({
|
|
||||||
ServiceMode? serviceMode,
|
|
||||||
LogLevel? logLevel,
|
|
||||||
bool? resolveDestination,
|
|
||||||
IPv6Mode? ipv6Mode,
|
|
||||||
String? remoteDnsAddress,
|
|
||||||
DomainStrategy? remoteDnsDomainStrategy,
|
|
||||||
String? directDnsAddress,
|
|
||||||
DomainStrategy? directDnsDomainStrategy,
|
|
||||||
int? mixedPort,
|
|
||||||
int? localDnsPort,
|
|
||||||
TunImplementation? tunImplementation,
|
|
||||||
int? mtu,
|
|
||||||
bool? strictRoute,
|
|
||||||
String? connectionTestUrl,
|
|
||||||
@IntervalInSecondsConverter() Duration? urlTestInterval,
|
|
||||||
bool? enableClashApi,
|
|
||||||
int? clashApiPort,
|
|
||||||
bool? bypassLan,
|
|
||||||
bool? allowConnectionFromLan,
|
|
||||||
bool? enableFakeDns,
|
|
||||||
bool? enableDnsRouting,
|
|
||||||
bool? independentDnsCache,
|
|
||||||
bool? enableTlsFragment,
|
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
|
||||||
RangeWithOptionalCeil? tlsFragmentSize,
|
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
|
||||||
RangeWithOptionalCeil? tlsFragmentSleep,
|
|
||||||
bool? enableTlsMixedSniCase,
|
|
||||||
bool? enableTlsPadding,
|
|
||||||
@RangeWithOptionalCeilJsonConverter() RangeWithOptionalCeil? tlsPaddingSize,
|
|
||||||
bool? enableMux,
|
|
||||||
bool? muxPadding,
|
|
||||||
int? muxMaxStreams,
|
|
||||||
MuxProtocol? muxProtocol,
|
|
||||||
bool? enableWarp,
|
|
||||||
WarpDetourMode? warpDetourMode,
|
|
||||||
String? warpLicenseKey,
|
|
||||||
String? warpCleanIp,
|
|
||||||
int? warpPort,
|
|
||||||
@RangeWithOptionalCeilJsonConverter() RangeWithOptionalCeil? warpNoise,
|
|
||||||
}) = _ConfigOptionPatch;
|
|
||||||
|
|
||||||
factory ConfigOptionPatch.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$ConfigOptionPatchFromJson(json);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:hiddify/features/config_option/data/config_option_data_providers.dart';
|
import 'package:hiddify/features/config_option/data/config_option_data_providers.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_patch.dart';
|
|
||||||
import 'package:hiddify/utils/custom_loggers.dart';
|
import 'package:hiddify/utils/custom_loggers.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/model/failures.dart';
|
import 'package:hiddify/core/model/failures.dart';
|
||||||
import 'package:hiddify/core/model/range.dart';
|
import 'package:hiddify/core/model/optional_range.dart';
|
||||||
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
||||||
import 'package:hiddify/core/widget/tip_card.dart';
|
import 'package:hiddify/core/widget/tip_card.dart';
|
||||||
import 'package:hiddify/features/common/nested_app_bar.dart';
|
import 'package:hiddify/features/common/nested_app_bar.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_patch.dart';
|
|
||||||
import 'package:hiddify/features/config_option/notifier/config_option_notifier.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/overview/warp_options_widgets.dart';
|
||||||
import 'package:hiddify/features/log/model/log_level.dart';
|
import 'package:hiddify/features/log/model/log_level.dart';
|
||||||
@@ -28,7 +27,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final t = ref.watch(translationsProvider);
|
final t = ref.watch(translationsProvider);
|
||||||
|
|
||||||
final defaultOptions = ConfigOptionEntity.initial;
|
final defaultOptions = ConfigOptionEntity.initial();
|
||||||
final asyncOptions = ref.watch(configOptionNotifierProvider);
|
final asyncOptions = ref.watch(configOptionNotifierProvider);
|
||||||
|
|
||||||
Future<void> changeOption(ConfigOptionPatch patch) async {
|
Future<void> changeOption(ConfigOptionPatch patch) async {
|
||||||
@@ -349,8 +348,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
if (range == null) return;
|
if (range == null) return;
|
||||||
await changeOption(
|
await changeOption(
|
||||||
ConfigOptionPatch(
|
ConfigOptionPatch(
|
||||||
tlsFragmentSize:
|
tlsFragmentSize: OptionalRange.tryParse(range),
|
||||||
RangeWithOptionalCeil.tryParse(range),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -367,8 +365,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
if (range == null) return;
|
if (range == null) return;
|
||||||
await changeOption(
|
await changeOption(
|
||||||
ConfigOptionPatch(
|
ConfigOptionPatch(
|
||||||
tlsFragmentSleep:
|
tlsFragmentSleep: OptionalRange.tryParse(range),
|
||||||
RangeWithOptionalCeil.tryParse(range),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -402,7 +399,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
if (range == null) return;
|
if (range == null) return;
|
||||||
await changeOption(
|
await changeOption(
|
||||||
ConfigOptionPatch(
|
ConfigOptionPatch(
|
||||||
tlsPaddingSize: RangeWithOptionalCeil.tryParse(range),
|
tlsPaddingSize: OptionalRange.tryParse(range),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import 'package:flutter/gestures.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/model/constants.dart';
|
import 'package:hiddify/core/model/constants.dart';
|
||||||
import 'package:hiddify/core/model/range.dart';
|
import 'package:hiddify/core/model/optional_range.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_patch.dart';
|
|
||||||
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
|
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
|
||||||
import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart';
|
import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||||
@@ -134,10 +133,7 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
|||||||
if (warpNoise == null) return;
|
if (warpNoise == null) return;
|
||||||
await onChange(
|
await onChange(
|
||||||
ConfigOptionPatch(
|
ConfigOptionPatch(
|
||||||
warpNoise: RangeWithOptionalCeil.tryParse(
|
warpNoise: OptionalRange.tryParse(warpNoise, allowEmpty: true),
|
||||||
warpNoise,
|
|
||||||
allowEmpty: true,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:hiddify/core/utils/ffi_utils.dart';
|
import 'package:hiddify/core/utils/ffi_utils.dart';
|
||||||
import 'package:hiddify/utils/custom_loggers.dart';
|
import 'package:hiddify/utils/custom_loggers.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:posix/posix.dart';
|
import 'package:posix/posix.dart';
|
||||||
import 'package:win32/win32.dart';
|
import 'package:win32/win32.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
import 'package:dartx/dartx.dart';
|
import 'package:dartx/dartx.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
part 'log_level.mapper.dart';
|
||||||
|
|
||||||
|
@MappableEnum()
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
trace,
|
trace,
|
||||||
debug,
|
debug,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:dartx/dartx.dart';
|
import 'package:dartx/dartx.dart';
|
||||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/semantics.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/widget/animated_visibility.dart';
|
import 'package:hiddify/core/widget/animated_visibility.dart';
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
|||||||
import 'package:hiddify/features/proxy/data/proxy_data_providers.dart';
|
import 'package:hiddify/features/proxy/data/proxy_data_providers.dart';
|
||||||
import 'package:hiddify/features/proxy/model/proxy_entity.dart';
|
import 'package:hiddify/features/proxy/model/proxy_entity.dart';
|
||||||
import 'package:hiddify/features/proxy/model/proxy_failure.dart';
|
import 'package:hiddify/features/proxy/model/proxy_failure.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_proxy_type.dart';
|
|
||||||
import 'package:hiddify/utils/pref_notifier.dart';
|
import 'package:hiddify/utils/pref_notifier.dart';
|
||||||
import 'package:hiddify/utils/riverpod_utils.dart';
|
import 'package:hiddify/utils/riverpod_utils.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
@@ -94,13 +93,13 @@ class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger {
|
|||||||
for (final group in proxies) {
|
for (final group in proxies) {
|
||||||
final sortedItems = switch (sortBy) {
|
final sortedItems = switch (sortBy) {
|
||||||
ProxiesSort.name => group.items.sortedWith((a, b) {
|
ProxiesSort.name => group.items.sortedWith((a, b) {
|
||||||
if(a.type.isGroup && !b.type.isGroup) return -1;
|
if (a.type.isGroup && !b.type.isGroup) return -1;
|
||||||
if(!a.type.isGroup && b.type.isGroup) return 1;
|
if (!a.type.isGroup && b.type.isGroup) return 1;
|
||||||
return a.tag.compareTo(b.tag);
|
return a.tag.compareTo(b.tag);
|
||||||
}),
|
}),
|
||||||
ProxiesSort.delay => group.items.sortedWith((a, b) {
|
ProxiesSort.delay => group.items.sortedWith((a, b) {
|
||||||
if(a.type.isGroup && !b.type.isGroup) return -1;
|
if (a.type.isGroup && !b.type.isGroup) return -1;
|
||||||
if(!a.type.isGroup && b.type.isGroup) return 1;
|
if (!a.type.isGroup && b.type.isGroup) return 1;
|
||||||
|
|
||||||
final ai = a.urlTestDelay;
|
final ai = a.urlTestDelay;
|
||||||
final bi = b.urlTestDelay;
|
final bi = b.urlTestDelay;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/model/constants.dart';
|
import 'package:hiddify/core/model/constants.dart';
|
||||||
import 'package:hiddify/core/router/router.dart';
|
import 'package:hiddify/core/router/router.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_patch.dart';
|
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
||||||
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
||||||
import 'package:hiddify/features/connection/model/connection_status.dart';
|
import 'package:hiddify/features/connection/model/connection_status.dart';
|
||||||
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/utils/platform_utils.dart';
|
import 'package:hiddify/utils/platform_utils.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
@JsonEnum(valueField: 'key')
|
part 'singbox_config_enum.mapper.dart';
|
||||||
|
|
||||||
|
@MappableEnum()
|
||||||
enum ServiceMode {
|
enum ServiceMode {
|
||||||
proxy("proxy"),
|
@MappableValue("proxy")
|
||||||
systemProxy("system-proxy"),
|
proxy,
|
||||||
tun("vpn"),
|
|
||||||
tunService("vpn-service");
|
|
||||||
|
|
||||||
const ServiceMode(this.key);
|
@MappableValue("system-proxy")
|
||||||
|
systemProxy,
|
||||||
|
|
||||||
final String key;
|
@MappableValue("vpn")
|
||||||
|
tun,
|
||||||
|
|
||||||
|
@MappableValue("vpn-service")
|
||||||
|
tunService;
|
||||||
|
|
||||||
static ServiceMode get defaultMode =>
|
static ServiceMode get defaultMode =>
|
||||||
PlatformUtils.isDesktop ? systemProxy : tun;
|
PlatformUtils.isDesktop ? systemProxy : tun;
|
||||||
@@ -39,16 +44,19 @@ enum ServiceMode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonEnum(valueField: 'key')
|
@MappableEnum()
|
||||||
enum IPv6Mode {
|
enum IPv6Mode {
|
||||||
disable("ipv4_only"),
|
@MappableValue("ipv4_only")
|
||||||
enable("prefer_ipv4"),
|
disable,
|
||||||
prefer("prefer_ipv6"),
|
|
||||||
only("ipv6_only");
|
|
||||||
|
|
||||||
const IPv6Mode(this.key);
|
@MappableValue("prefer_ipv4")
|
||||||
|
enable,
|
||||||
|
|
||||||
final String key;
|
@MappableValue("prefer_ipv6")
|
||||||
|
prefer,
|
||||||
|
|
||||||
|
@MappableValue("ipv6_only")
|
||||||
|
only;
|
||||||
|
|
||||||
String present(TranslationsEn t) => switch (this) {
|
String present(TranslationsEn t) => switch (this) {
|
||||||
disable => t.settings.config.ipv6Modes.disable,
|
disable => t.settings.config.ipv6Modes.disable,
|
||||||
@@ -58,12 +66,21 @@ enum IPv6Mode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonEnum(valueField: 'key')
|
@MappableEnum()
|
||||||
enum DomainStrategy {
|
enum DomainStrategy {
|
||||||
|
@MappableValue("")
|
||||||
auto(""),
|
auto(""),
|
||||||
|
|
||||||
|
@MappableValue("prefer_ipv6")
|
||||||
preferIpv6("prefer_ipv6"),
|
preferIpv6("prefer_ipv6"),
|
||||||
|
|
||||||
|
@MappableValue("prefer_ipv4")
|
||||||
preferIpv4("prefer_ipv4"),
|
preferIpv4("prefer_ipv4"),
|
||||||
|
|
||||||
|
@MappableValue("ipv4_only")
|
||||||
ipv4Only("ipv4_only"),
|
ipv4Only("ipv4_only"),
|
||||||
|
|
||||||
|
@MappableValue("ipv6_only")
|
||||||
ipv6Only("ipv6_only");
|
ipv6Only("ipv6_only");
|
||||||
|
|
||||||
const DomainStrategy(this.key);
|
const DomainStrategy(this.key);
|
||||||
@@ -76,18 +93,21 @@ enum DomainStrategy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MappableEnum()
|
||||||
enum TunImplementation {
|
enum TunImplementation {
|
||||||
mixed,
|
mixed,
|
||||||
system,
|
system,
|
||||||
gVisor;
|
gVisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MappableEnum()
|
||||||
enum MuxProtocol {
|
enum MuxProtocol {
|
||||||
h2mux,
|
h2mux,
|
||||||
smux,
|
smux,
|
||||||
yamux;
|
yamux;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MappableEnum()
|
||||||
enum WarpDetourMode {
|
enum WarpDetourMode {
|
||||||
outbound,
|
outbound,
|
||||||
inbound;
|
inbound;
|
||||||
|
|||||||
@@ -1,86 +1,127 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
import 'package:hiddify/core/model/range.dart';
|
import 'package:hiddify/core/model/optional_range.dart';
|
||||||
import 'package:hiddify/features/log/model/log_level.dart';
|
import 'package:hiddify/features/log/model/log_level.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_rule.dart';
|
import 'package:hiddify/singbox/model/singbox_rule.dart';
|
||||||
|
|
||||||
part 'singbox_config_option.freezed.dart';
|
part 'singbox_config_option.mapper.dart';
|
||||||
part 'singbox_config_option.g.dart';
|
|
||||||
|
|
||||||
@freezed
|
@MappableClass(
|
||||||
class SingboxConfigOption with _$SingboxConfigOption {
|
caseStyle: CaseStyle.paramCase,
|
||||||
const SingboxConfigOption._();
|
includeCustomMappers: [
|
||||||
|
OptionalRangeJsonMapper(),
|
||||||
|
IntervalMapper(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class SingboxConfigOption with SingboxConfigOptionMappable {
|
||||||
|
const SingboxConfigOption({
|
||||||
|
required this.executeConfigAsIs,
|
||||||
|
required this.logLevel,
|
||||||
|
required this.resolveDestination,
|
||||||
|
required this.ipv6Mode,
|
||||||
|
required this.remoteDnsAddress,
|
||||||
|
required this.remoteDnsDomainStrategy,
|
||||||
|
required this.directDnsAddress,
|
||||||
|
required this.directDnsDomainStrategy,
|
||||||
|
required this.mixedPort,
|
||||||
|
required this.localDnsPort,
|
||||||
|
required this.tunImplementation,
|
||||||
|
required this.mtu,
|
||||||
|
required this.strictRoute,
|
||||||
|
required this.connectionTestUrl,
|
||||||
|
required this.urlTestInterval,
|
||||||
|
required this.enableClashApi,
|
||||||
|
required this.clashApiPort,
|
||||||
|
required this.enableTun,
|
||||||
|
required this.enableTunService,
|
||||||
|
required this.setSystemProxy,
|
||||||
|
required this.bypassLan,
|
||||||
|
required this.allowConnectionFromLan,
|
||||||
|
required this.enableFakeDns,
|
||||||
|
required this.enableDnsRouting,
|
||||||
|
required this.independentDnsCache,
|
||||||
|
required this.enableTlsFragment,
|
||||||
|
required this.tlsFragmentSize,
|
||||||
|
required this.tlsFragmentSleep,
|
||||||
|
required this.enableTlsMixedSniCase,
|
||||||
|
required this.enableTlsPadding,
|
||||||
|
required this.tlsPaddingSize,
|
||||||
|
required this.enableMux,
|
||||||
|
required this.muxPadding,
|
||||||
|
required this.muxMaxStreams,
|
||||||
|
required this.muxProtocol,
|
||||||
|
required this.enableWarp,
|
||||||
|
required this.warpDetourMode,
|
||||||
|
required this.warpLicenseKey,
|
||||||
|
required this.warpCleanIp,
|
||||||
|
required this.warpPort,
|
||||||
|
required this.warpNoise,
|
||||||
|
required this.geoipPath,
|
||||||
|
required this.geositePath,
|
||||||
|
required this.rules,
|
||||||
|
});
|
||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
final bool executeConfigAsIs;
|
||||||
const factory SingboxConfigOption({
|
final LogLevel logLevel;
|
||||||
required bool executeConfigAsIs,
|
final bool resolveDestination;
|
||||||
required LogLevel logLevel,
|
@MappableField(key: "ipv6-mode")
|
||||||
required bool resolveDestination,
|
final IPv6Mode ipv6Mode;
|
||||||
required IPv6Mode ipv6Mode,
|
final String remoteDnsAddress;
|
||||||
required String remoteDnsAddress,
|
final DomainStrategy remoteDnsDomainStrategy;
|
||||||
required DomainStrategy remoteDnsDomainStrategy,
|
final String directDnsAddress;
|
||||||
required String directDnsAddress,
|
final DomainStrategy directDnsDomainStrategy;
|
||||||
required DomainStrategy directDnsDomainStrategy,
|
final int mixedPort;
|
||||||
required int mixedPort,
|
final int localDnsPort;
|
||||||
required int localDnsPort,
|
final TunImplementation tunImplementation;
|
||||||
required TunImplementation tunImplementation,
|
final int mtu;
|
||||||
required int mtu,
|
final bool strictRoute;
|
||||||
required bool strictRoute,
|
final String connectionTestUrl;
|
||||||
required String connectionTestUrl,
|
final Duration urlTestInterval;
|
||||||
@IntervalConverter() required Duration urlTestInterval,
|
final bool enableClashApi;
|
||||||
required bool enableClashApi,
|
final int clashApiPort;
|
||||||
required int clashApiPort,
|
final bool enableTun;
|
||||||
required bool enableTun,
|
final bool enableTunService;
|
||||||
required bool enableTunService,
|
final bool setSystemProxy;
|
||||||
required bool setSystemProxy,
|
final bool bypassLan;
|
||||||
required bool bypassLan,
|
final bool allowConnectionFromLan;
|
||||||
required bool allowConnectionFromLan,
|
final bool enableFakeDns;
|
||||||
required bool enableFakeDns,
|
final bool enableDnsRouting;
|
||||||
required bool enableDnsRouting,
|
final bool independentDnsCache;
|
||||||
required bool independentDnsCache,
|
final bool enableTlsFragment;
|
||||||
required bool enableTlsFragment,
|
final OptionalRange tlsFragmentSize;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
final OptionalRange tlsFragmentSleep;
|
||||||
required RangeWithOptionalCeil tlsFragmentSize,
|
final bool enableTlsMixedSniCase;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
final bool enableTlsPadding;
|
||||||
required RangeWithOptionalCeil tlsFragmentSleep,
|
final OptionalRange tlsPaddingSize;
|
||||||
required bool enableTlsMixedSniCase,
|
final bool enableMux;
|
||||||
required bool enableTlsPadding,
|
final bool muxPadding;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
final int muxMaxStreams;
|
||||||
required RangeWithOptionalCeil tlsPaddingSize,
|
final MuxProtocol muxProtocol;
|
||||||
required bool enableMux,
|
final bool enableWarp;
|
||||||
required bool muxPadding,
|
final WarpDetourMode warpDetourMode;
|
||||||
required int muxMaxStreams,
|
final String warpLicenseKey;
|
||||||
required MuxProtocol muxProtocol,
|
final String warpCleanIp;
|
||||||
required bool enableWarp,
|
final int warpPort;
|
||||||
required WarpDetourMode warpDetourMode,
|
final OptionalRange warpNoise;
|
||||||
required String warpLicenseKey,
|
final String geoipPath;
|
||||||
required String warpCleanIp,
|
final String geositePath;
|
||||||
required int warpPort,
|
final List<SingboxRule> rules;
|
||||||
@RangeWithOptionalCeilJsonConverter()
|
|
||||||
required RangeWithOptionalCeil warpNoise,
|
|
||||||
required String geoipPath,
|
|
||||||
required String geositePath,
|
|
||||||
required List<SingboxRule> rules,
|
|
||||||
}) = _SingboxConfigOption;
|
|
||||||
|
|
||||||
String format() {
|
String format() {
|
||||||
const encoder = JsonEncoder.withIndent(' ');
|
const encoder = JsonEncoder.withIndent(' ');
|
||||||
return encoder.convert(toJson());
|
return encoder.convert(toMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SingboxConfigOptionFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class IntervalConverter implements JsonConverter<Duration, String> {
|
class IntervalMapper extends SimpleMapper<Duration> {
|
||||||
const IntervalConverter();
|
const IntervalMapper();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Duration fromJson(String json) =>
|
Duration decode(dynamic value) =>
|
||||||
Duration(minutes: int.parse(json.replaceAll("m", "")));
|
Duration(minutes: int.parse((value as String).replaceAll("m", "")));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toJson(Duration object) => "${object.inMinutes}m";
|
String encode(Duration self) => "${self.inMinutes}m";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:dart_mappable/dart_mappable.dart';
|
||||||
|
|
||||||
part 'singbox_rule.freezed.dart';
|
part 'singbox_rule.mapper.dart';
|
||||||
part 'singbox_rule.g.dart';
|
|
||||||
|
|
||||||
@freezed
|
@MappableClass()
|
||||||
class SingboxRule with _$SingboxRule {
|
class SingboxRule with SingboxRuleMappable {
|
||||||
const SingboxRule._();
|
const SingboxRule({
|
||||||
|
this.domains,
|
||||||
|
this.ip,
|
||||||
|
this.port,
|
||||||
|
this.protocol,
|
||||||
|
this.network = RuleNetwork.tcpAndUdp,
|
||||||
|
this.outbound = RuleOutbound.proxy,
|
||||||
|
});
|
||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
final String? domains;
|
||||||
const factory SingboxRule({
|
final String? ip;
|
||||||
String? domains,
|
final String? port;
|
||||||
String? ip,
|
final String? protocol;
|
||||||
String? port,
|
final RuleNetwork network;
|
||||||
String? protocol,
|
final RuleOutbound outbound;
|
||||||
@Default(RuleNetwork.tcpAndUdp) RuleNetwork network,
|
|
||||||
@Default(RuleOutbound.proxy) RuleOutbound outbound,
|
|
||||||
}) = _SingboxRule;
|
|
||||||
|
|
||||||
factory SingboxRule.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SingboxRuleFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MappableEnum()
|
||||||
enum RuleOutbound { proxy, bypass, block }
|
enum RuleOutbound { proxy, bypass, block }
|
||||||
|
|
||||||
@JsonEnum(valueField: 'key')
|
@MappableEnum()
|
||||||
enum RuleNetwork {
|
enum RuleNetwork {
|
||||||
tcpAndUdp(""),
|
@MappableValue("")
|
||||||
tcp("tcp"),
|
tcpAndUdp,
|
||||||
udp("udp");
|
|
||||||
|
|
||||||
const RuleNetwork(this.key);
|
@MappableValue("tcp")
|
||||||
|
tcp,
|
||||||
|
|
||||||
final String? key;
|
@MappableValue("udp")
|
||||||
|
udp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => CombineWorker().execute(
|
||||||
() {
|
() {
|
||||||
final json = jsonEncode(options.toJson());
|
final json = options.toJson();
|
||||||
final err = _box
|
final err = _box
|
||||||
.changeConfigOptions(json.toNativeUtf8().cast())
|
.changeConfigOptions(json.toNativeUtf8().cast())
|
||||||
.cast<Utf8>()
|
.cast<Utf8>()
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class PlatformSingboxService with InfraLogger implements SingboxService {
|
|||||||
loggy.debug("changing options");
|
loggy.debug("changing options");
|
||||||
await methodChannel.invokeMethod(
|
await methodChannel.invokeMethod(
|
||||||
"change_config_options",
|
"change_config_options",
|
||||||
jsonEncode(options.toJson()),
|
options.toJson(),
|
||||||
);
|
);
|
||||||
return right(unit);
|
return right(unit);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dartx/dartx.dart';
|
import 'package:dartx/dartx.dart';
|
||||||
import 'package:fpdart/fpdart.dart';
|
|
||||||
import 'package:hiddify/features/profile/data/profile_parser.dart';
|
import 'package:hiddify/features/profile/data/profile_parser.dart';
|
||||||
import 'package:hiddify/features/profile/data/profile_repository.dart';
|
import 'package:hiddify/features/profile/data/profile_repository.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_proxy_type.dart';
|
import 'package:hiddify/singbox/model/singbox_proxy_type.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user