Change mapping and bug fixes

This commit is contained in:
problematicconsumer
2024-02-15 19:39:35 +03:30
parent bd4c5eed7e
commit 702c59c3bc
21 changed files with 501 additions and 422 deletions

View File

@@ -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,
); );

View 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();
}

View File

@@ -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();
}

View File

@@ -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;
} }

View File

@@ -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,

View File

@@ -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);
} }

View File

@@ -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);
}

View File

@@ -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';

View File

@@ -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),
), ),
); );
}, },

View File

@@ -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,
),
), ),
); );
}, },

View File

@@ -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';

View File

@@ -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,

View File

@@ -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';

View File

@@ -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;

View File

@@ -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';

View File

@@ -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;

View File

@@ -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";
} }

View File

@@ -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;
} }

View File

@@ -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>()

View File

@@ -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);
}, },

View File

@@ -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';