Refactor preferences
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
||||
import 'package:hiddify/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'config_option_data_providers.g.dart';
|
||||
@@ -10,19 +9,9 @@ part 'config_option_data_providers.g.dart';
|
||||
ConfigOptionRepository configOptionRepository(
|
||||
ConfigOptionRepositoryRef ref,
|
||||
) {
|
||||
return ConfigOptionRepositoryImpl(
|
||||
return ConfigOptionRepository(
|
||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||
singbox: ref.watch(singboxServiceProvider),
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
SingBoxConfigOptionRepository singBoxConfigOptionRepository(
|
||||
SingBoxConfigOptionRepositoryRef ref,
|
||||
) {
|
||||
return SingBoxConfigOptionRepositoryImpl(
|
||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||
optionsRepository: ref.watch(configOptionRepositoryProvider),
|
||||
getConfigOptions: () => ConfigOptions.singboxOptions(ref),
|
||||
geoAssetRepository: ref.watch(geoAssetRepositoryProvider).requireValue,
|
||||
geoAssetPathResolver: ref.watch(geoAssetPathResolverProvider),
|
||||
);
|
||||
|
||||
@@ -1,166 +1,402 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/core/model/optional_range.dart';
|
||||
import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||
import 'package:hiddify/features/config_option/model/config_option_entity.dart';
|
||||
import 'package:hiddify/core/utils/json_converters.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:hiddify/features/config_option/model/config_option_failure.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/log/model/log_level.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/singbox/service/singbox_service.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
abstract interface class ConfigOptionRepository {
|
||||
Either<ConfigOptionFailure, ConfigOptionEntity> getConfigOption();
|
||||
TaskEither<ConfigOptionFailure, Unit> updateConfigOption(
|
||||
ConfigOptionPatch patch,
|
||||
abstract class ConfigOptions {
|
||||
static final serviceMode = PreferencesNotifier.create<ServiceMode, String>(
|
||||
"service-mode",
|
||||
ServiceMode.defaultMode,
|
||||
mapFrom: (value) => ServiceMode.choices.firstWhere((e) => e.key == value),
|
||||
mapTo: (value) => value.key,
|
||||
);
|
||||
TaskEither<ConfigOptionFailure, Unit> resetConfigOption();
|
||||
TaskEither<ConfigOptionFailure, String> generateWarpConfig();
|
||||
}
|
||||
|
||||
abstract interface class SingBoxConfigOptionRepository {
|
||||
TaskEither<ConfigOptionFailure, SingboxConfigOption>
|
||||
getFullSingboxConfigOption();
|
||||
}
|
||||
static final logLevel = PreferencesNotifier.create<LogLevel, String>(
|
||||
"log-level",
|
||||
LogLevel.warn,
|
||||
mapFrom: LogLevel.values.byName,
|
||||
mapTo: (value) => value.name,
|
||||
);
|
||||
|
||||
class ConfigOptionRepositoryImpl
|
||||
with ExceptionHandler, InfraLogger
|
||||
implements ConfigOptionRepository {
|
||||
ConfigOptionRepositoryImpl({
|
||||
required this.preferences,
|
||||
required this.singbox,
|
||||
});
|
||||
static final resolveDestination = PreferencesNotifier.create<bool, bool>(
|
||||
"resolve-destination",
|
||||
false,
|
||||
);
|
||||
|
||||
final SharedPreferences preferences;
|
||||
final SingboxService singbox;
|
||||
static final ipv6Mode = PreferencesNotifier.create<IPv6Mode, String>(
|
||||
"ipv6-mode",
|
||||
IPv6Mode.disable,
|
||||
mapFrom: (value) => IPv6Mode.values.firstWhere((e) => e.key == value),
|
||||
mapTo: (value) => value.key,
|
||||
);
|
||||
|
||||
@override
|
||||
Either<ConfigOptionFailure, ConfigOptionEntity> getConfigOption() {
|
||||
try {
|
||||
final map = ConfigOptionEntity.initial().toJson();
|
||||
for (final key in map.keys) {
|
||||
final persisted = preferences.get(key);
|
||||
if (persisted != null) {
|
||||
final defaultValue = map[key];
|
||||
if (defaultValue != null &&
|
||||
persisted.runtimeType != defaultValue.runtimeType) {
|
||||
loggy.warning(
|
||||
"error getting preference[$key], expected type: [${defaultValue.runtimeType}] - received value: [$persisted](${persisted.runtimeType})",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
map[key] = persisted;
|
||||
}
|
||||
static final remoteDnsAddress = PreferencesNotifier.create<String, String>(
|
||||
"remote-dns-address",
|
||||
"udp://1.1.1.1",
|
||||
validator: (value) => value.isNotBlank,
|
||||
);
|
||||
|
||||
static final remoteDnsDomainStrategy =
|
||||
PreferencesNotifier.create<DomainStrategy, String>(
|
||||
"remote-dns-domain-strategy",
|
||||
DomainStrategy.auto,
|
||||
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
|
||||
mapTo: (value) => value.key,
|
||||
);
|
||||
|
||||
static final directDnsAddress = PreferencesNotifier.create<String, String>(
|
||||
"direct-dns-address",
|
||||
"1.1.1.1",
|
||||
validator: (value) => value.isNotBlank,
|
||||
);
|
||||
|
||||
static final directDnsDomainStrategy =
|
||||
PreferencesNotifier.create<DomainStrategy, String>(
|
||||
"direct-dns-domain-strategy",
|
||||
DomainStrategy.auto,
|
||||
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
|
||||
mapTo: (value) => value.key,
|
||||
);
|
||||
|
||||
static final mixedPort = PreferencesNotifier.create<int, int>(
|
||||
"mixed-port",
|
||||
2334,
|
||||
validator: (value) => isPort(value.toString()),
|
||||
);
|
||||
|
||||
static final localDnsPort = PreferencesNotifier.create<int, int>(
|
||||
"local-dns-port",
|
||||
6450,
|
||||
validator: (value) => isPort(value.toString()),
|
||||
);
|
||||
|
||||
static final tunImplementation =
|
||||
PreferencesNotifier.create<TunImplementation, String>(
|
||||
"tun-implementation",
|
||||
TunImplementation.mixed,
|
||||
mapFrom: TunImplementation.values.byName,
|
||||
mapTo: (value) => value.name,
|
||||
);
|
||||
|
||||
static final mtu = PreferencesNotifier.create<int, int>("mtu", 9000);
|
||||
|
||||
static final strictRoute =
|
||||
PreferencesNotifier.create<bool, bool>("strict-route", true);
|
||||
|
||||
static final connectionTestUrl = PreferencesNotifier.create<String, String>(
|
||||
"connection-test-url",
|
||||
"http://cp.cloudflare.com/",
|
||||
validator: (value) => value.isNotBlank && isUrl(value),
|
||||
);
|
||||
|
||||
static final urlTestInterval = PreferencesNotifier.create<Duration, int>(
|
||||
"url-test-interval",
|
||||
const Duration(minutes: 10),
|
||||
mapFrom: const IntervalInSecondsConverter().fromJson,
|
||||
mapTo: const IntervalInSecondsConverter().toJson,
|
||||
);
|
||||
|
||||
static final enableClashApi = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-clash-api",
|
||||
true,
|
||||
);
|
||||
|
||||
static final clashApiPort = PreferencesNotifier.create<int, int>(
|
||||
"clash-api-port",
|
||||
6756,
|
||||
validator: (value) => isPort(value.toString()),
|
||||
);
|
||||
|
||||
static final bypassLan =
|
||||
PreferencesNotifier.create<bool, bool>("bypass-lan", false);
|
||||
|
||||
static final allowConnectionFromLan = PreferencesNotifier.create<bool, bool>(
|
||||
"allow-connection-from-lan",
|
||||
false,
|
||||
);
|
||||
|
||||
static final enableFakeDns = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-fake-dns",
|
||||
false,
|
||||
);
|
||||
|
||||
static final enableDnsRouting = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-dns-routing",
|
||||
true,
|
||||
);
|
||||
|
||||
static final independentDnsCache = PreferencesNotifier.create<bool, bool>(
|
||||
"independent-dns-cache",
|
||||
true,
|
||||
);
|
||||
|
||||
static final enableTlsFragment = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-tls-fragment",
|
||||
false,
|
||||
);
|
||||
|
||||
static final tlsFragmentSize =
|
||||
PreferencesNotifier.create<OptionalRange, String>(
|
||||
"tls-fragment-size",
|
||||
const OptionalRange(min: 1, max: 500),
|
||||
mapFrom: OptionalRange.parse,
|
||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||
);
|
||||
|
||||
static final tlsFragmentSleep =
|
||||
PreferencesNotifier.create<OptionalRange, String>(
|
||||
"tls-fragment-sleep",
|
||||
const OptionalRange(min: 0, max: 500),
|
||||
mapFrom: OptionalRange.parse,
|
||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||
);
|
||||
|
||||
static final enableTlsMixedSniCase = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-tls-mixed-sni-case",
|
||||
false,
|
||||
);
|
||||
|
||||
static final enableTlsPadding = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-tls-padding",
|
||||
false,
|
||||
);
|
||||
|
||||
static final tlsPaddingSize =
|
||||
PreferencesNotifier.create<OptionalRange, String>(
|
||||
"tls-padding-size",
|
||||
const OptionalRange(min: 1, max: 1500),
|
||||
mapFrom: OptionalRange.parse,
|
||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||
);
|
||||
|
||||
static final enableMux = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-mux",
|
||||
false,
|
||||
);
|
||||
|
||||
static final muxPadding = PreferencesNotifier.create<bool, bool>(
|
||||
"mux-padding",
|
||||
false,
|
||||
);
|
||||
|
||||
static final muxMaxStreams = PreferencesNotifier.create<int, int>(
|
||||
"mux-max-streams",
|
||||
8,
|
||||
validator: (value) => value > 0,
|
||||
);
|
||||
|
||||
static final muxProtocol = PreferencesNotifier.create<MuxProtocol, String>(
|
||||
"mux-protocol",
|
||||
MuxProtocol.h2mux,
|
||||
mapFrom: MuxProtocol.values.byName,
|
||||
mapTo: (value) => value.name,
|
||||
);
|
||||
|
||||
static final enableWarp = PreferencesNotifier.create<bool, bool>(
|
||||
"enable-warp",
|
||||
false,
|
||||
);
|
||||
|
||||
static final warpDetourMode =
|
||||
PreferencesNotifier.create<WarpDetourMode, String>(
|
||||
"warp-detour-mode",
|
||||
WarpDetourMode.outbound,
|
||||
mapFrom: WarpDetourMode.values.byName,
|
||||
mapTo: (value) => value.name,
|
||||
);
|
||||
|
||||
static final warpLicenseKey = PreferencesNotifier.create<String, String>(
|
||||
"warp-license-key",
|
||||
"",
|
||||
);
|
||||
|
||||
static final warpAccountId = PreferencesNotifier.create<String, String>(
|
||||
"warp-account-id",
|
||||
"",
|
||||
);
|
||||
|
||||
static final warpAccessToken = PreferencesNotifier.create<String, String>(
|
||||
"warp-access-token",
|
||||
"",
|
||||
);
|
||||
|
||||
static final warpCleanIp = PreferencesNotifier.create<String, String>(
|
||||
"warp-clean-ip",
|
||||
"auto",
|
||||
);
|
||||
|
||||
static final warpPort = PreferencesNotifier.create<int, int>(
|
||||
"warp-port",
|
||||
0,
|
||||
validator: (value) => isPort(value.toString()),
|
||||
);
|
||||
|
||||
static final warpNoise = PreferencesNotifier.create<OptionalRange, String>(
|
||||
"warp-noise",
|
||||
const OptionalRange(min: 5, max: 10),
|
||||
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||
);
|
||||
|
||||
static final warpNoiseDelay =
|
||||
PreferencesNotifier.create<OptionalRange, String>(
|
||||
"warp-noise-delay",
|
||||
const OptionalRange(min: 20, max: 200),
|
||||
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||
);
|
||||
|
||||
static final warpWireguardConfig = PreferencesNotifier.create<String, String>(
|
||||
"warp-wireguard-config",
|
||||
"",
|
||||
);
|
||||
|
||||
static final hasExperimentalFeatures = Provider.autoDispose<bool>(
|
||||
(ref) {
|
||||
final mode = ref.watch(serviceMode);
|
||||
if (PlatformUtils.isDesktop && mode == ServiceMode.tun) {
|
||||
return true;
|
||||
}
|
||||
final options = ConfigOptionEntity.fromJson(map);
|
||||
return right(options);
|
||||
} catch (error, stackTrace) {
|
||||
return left(ConfigOptionUnexpectedFailure(error, stackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<ConfigOptionFailure, Unit> updateConfigOption(
|
||||
ConfigOptionPatch patch,
|
||||
) {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
final map = patch.toJson();
|
||||
await updateByJson(map);
|
||||
return right(unit);
|
||||
},
|
||||
ConfigOptionUnexpectedFailure.new,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<ConfigOptionFailure, Unit> resetConfigOption() {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
final map = ConfigOptionEntity.initial().toJson();
|
||||
await updateByJson(map);
|
||||
return right(unit);
|
||||
},
|
||||
ConfigOptionUnexpectedFailure.new,
|
||||
);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<void> updateByJson(
|
||||
Map<String, dynamic> options,
|
||||
) async {
|
||||
final map = ConfigOptionEntity.initial().toJson();
|
||||
for (final key in map.keys) {
|
||||
final value = options[key];
|
||||
if (value != null) {
|
||||
loggy.debug("updating [$key] to [$value]");
|
||||
|
||||
switch (value) {
|
||||
case bool _:
|
||||
await preferences.setBool(key, value);
|
||||
case String _:
|
||||
await preferences.setString(key, value);
|
||||
case int _:
|
||||
await preferences.setInt(key, value);
|
||||
case double _:
|
||||
await preferences.setDouble(key, value);
|
||||
default:
|
||||
loggy.warning("unexpected type");
|
||||
}
|
||||
if (ref.watch(enableTlsFragment) ||
|
||||
ref.watch(enableTlsMixedSniCase) ||
|
||||
ref.watch(enableTlsPadding) ||
|
||||
ref.watch(enableMux) ||
|
||||
ref.watch(enableWarp)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<ConfigOptionFailure, String> generateWarpConfig() {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
final options = getConfigOption().getOrElse((l) => throw l);
|
||||
return await singbox
|
||||
.generateWarpConfig(
|
||||
licenseKey: options.warpLicenseKey,
|
||||
previousAccountId: options.warpAccountId,
|
||||
previousAccessToken: options.warpAccessToken,
|
||||
)
|
||||
.mapLeft((l) => ConfigOptionFailure.unexpected(l))
|
||||
.flatMap(
|
||||
(warp) => updateConfigOption(
|
||||
ConfigOptionPatch(
|
||||
warpAccountId: warp.accountId,
|
||||
warpAccessToken: warp.accessToken,
|
||||
warpWireguardConfig: warp.wireguardConfig,
|
||||
),
|
||||
).map((_) => warp.log),
|
||||
)
|
||||
.run();
|
||||
},
|
||||
(error, stackTrace) {
|
||||
loggy.error(error);
|
||||
return ConfigOptionUnexpectedFailure(error, stackTrace);
|
||||
},
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
/// list of all config option preferences
|
||||
static final preferences = [
|
||||
serviceMode,
|
||||
logLevel,
|
||||
resolveDestination,
|
||||
ipv6Mode,
|
||||
remoteDnsAddress,
|
||||
remoteDnsDomainStrategy,
|
||||
directDnsAddress,
|
||||
directDnsDomainStrategy,
|
||||
mixedPort,
|
||||
localDnsPort,
|
||||
tunImplementation,
|
||||
mtu,
|
||||
strictRoute,
|
||||
connectionTestUrl,
|
||||
urlTestInterval,
|
||||
clashApiPort,
|
||||
bypassLan,
|
||||
allowConnectionFromLan,
|
||||
enableDnsRouting,
|
||||
enableTlsFragment,
|
||||
tlsFragmentSize,
|
||||
tlsFragmentSleep,
|
||||
enableTlsMixedSniCase,
|
||||
enableTlsPadding,
|
||||
tlsPaddingSize,
|
||||
enableMux,
|
||||
muxPadding,
|
||||
muxMaxStreams,
|
||||
muxProtocol,
|
||||
enableWarp,
|
||||
warpDetourMode,
|
||||
warpLicenseKey,
|
||||
warpAccountId,
|
||||
warpAccessToken,
|
||||
warpCleanIp,
|
||||
warpPort,
|
||||
warpNoise,
|
||||
warpWireguardConfig,
|
||||
];
|
||||
|
||||
/// singbox options
|
||||
///
|
||||
/// **this is partial, don't use it directly**
|
||||
static SingboxConfigOption singboxOptions(ProviderRef ref) {
|
||||
final mode = ref.read(serviceMode);
|
||||
return SingboxConfigOption(
|
||||
executeConfigAsIs: false,
|
||||
logLevel: ref.read(logLevel),
|
||||
resolveDestination: ref.read(resolveDestination),
|
||||
ipv6Mode: ref.read(ipv6Mode),
|
||||
remoteDnsAddress: ref.read(remoteDnsAddress),
|
||||
remoteDnsDomainStrategy: ref.read(remoteDnsDomainStrategy),
|
||||
directDnsAddress: ref.read(directDnsAddress),
|
||||
directDnsDomainStrategy: ref.read(directDnsDomainStrategy),
|
||||
mixedPort: ref.read(mixedPort),
|
||||
localDnsPort: ref.read(localDnsPort),
|
||||
tunImplementation: ref.read(tunImplementation),
|
||||
mtu: ref.read(mtu),
|
||||
strictRoute: ref.read(strictRoute),
|
||||
connectionTestUrl: ref.read(connectionTestUrl),
|
||||
urlTestInterval: ref.read(urlTestInterval),
|
||||
enableClashApi: ref.read(enableClashApi),
|
||||
clashApiPort: ref.read(clashApiPort),
|
||||
enableTun: mode == ServiceMode.tun,
|
||||
enableTunService: mode == ServiceMode.tunService,
|
||||
setSystemProxy: mode == ServiceMode.systemProxy,
|
||||
bypassLan: ref.read(bypassLan),
|
||||
allowConnectionFromLan: ref.read(allowConnectionFromLan),
|
||||
enableFakeDns: ref.read(enableFakeDns),
|
||||
enableDnsRouting: ref.read(enableDnsRouting),
|
||||
independentDnsCache: ref.read(independentDnsCache),
|
||||
enableTlsFragment: ref.read(enableTlsFragment),
|
||||
tlsFragmentSize: ref.read(tlsFragmentSize),
|
||||
tlsFragmentSleep: ref.read(tlsFragmentSleep),
|
||||
enableTlsMixedSniCase: ref.read(enableTlsMixedSniCase),
|
||||
enableTlsPadding: ref.read(enableTlsPadding),
|
||||
tlsPaddingSize: ref.read(tlsPaddingSize),
|
||||
enableMux: ref.read(enableMux),
|
||||
muxPadding: ref.read(muxPadding),
|
||||
muxMaxStreams: ref.read(muxMaxStreams),
|
||||
muxProtocol: ref.read(muxProtocol),
|
||||
warp: SingboxWarpOption(
|
||||
enable: ref.read(enableWarp),
|
||||
mode: ref.read(warpDetourMode),
|
||||
wireguardConfig: ref.read(warpWireguardConfig),
|
||||
licenseKey: ref.read(warpLicenseKey),
|
||||
accountId: ref.read(warpAccountId),
|
||||
accessToken: ref.read(warpAccessToken),
|
||||
cleanIp: ref.read(warpCleanIp),
|
||||
cleanPort: ref.read(warpPort),
|
||||
warpNoise: ref.read(warpNoise),
|
||||
warpNoiseDelay: ref.read(warpNoiseDelay),
|
||||
),
|
||||
geoipPath: "",
|
||||
geositePath: "",
|
||||
rules: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SingBoxConfigOptionRepositoryImpl
|
||||
with ExceptionHandler, InfraLogger
|
||||
implements SingBoxConfigOptionRepository {
|
||||
SingBoxConfigOptionRepositoryImpl({
|
||||
class ConfigOptionRepository with ExceptionHandler, InfraLogger {
|
||||
ConfigOptionRepository({
|
||||
required this.preferences,
|
||||
required this.optionsRepository,
|
||||
required this.getConfigOptions,
|
||||
required this.geoAssetRepository,
|
||||
required this.geoAssetPathResolver,
|
||||
});
|
||||
|
||||
final SharedPreferences preferences;
|
||||
final ConfigOptionRepository optionsRepository;
|
||||
final SingboxConfigOption Function() getConfigOptions;
|
||||
final GeoAssetRepository geoAssetRepository;
|
||||
final GeoAssetPathResolver geoAssetPathResolver;
|
||||
|
||||
@override
|
||||
TaskEither<ConfigOptionFailure, SingboxConfigOption>
|
||||
getFullSingboxConfigOption() {
|
||||
return exceptionHandler(
|
||||
@@ -204,9 +440,7 @@ class SingBoxConfigOptionRepositoryImpl
|
||||
.getOrElse((l) => throw l)
|
||||
.run();
|
||||
|
||||
final persisted =
|
||||
optionsRepository.getConfigOption().getOrElse((l) => throw l);
|
||||
final singboxConfigOption = persisted.toSingbox(
|
||||
final singboxConfigOption = getConfigOptions().copyWith(
|
||||
geoipPath: geoAssetPathResolver.relativePath(
|
||||
geoAssets.geoip.providerName,
|
||||
geoAssets.geoip.fileName,
|
||||
|
||||
Reference in New Issue
Block a user