Add warp config generator
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -11,6 +12,7 @@ ConfigOptionRepository configOptionRepository(
|
||||
) {
|
||||
return ConfigOptionRepositoryImpl(
|
||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||
singbox: ref.watch(singboxServiceProvider),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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/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:shared_preferences/shared_preferences.dart';
|
||||
@@ -17,6 +18,7 @@ abstract interface class ConfigOptionRepository {
|
||||
ConfigOptionPatch patch,
|
||||
);
|
||||
TaskEither<ConfigOptionFailure, Unit> resetConfigOption();
|
||||
TaskEither<ConfigOptionFailure, Unit> generateWarpConfig();
|
||||
}
|
||||
|
||||
abstract interface class SingBoxConfigOptionRepository {
|
||||
@@ -27,9 +29,13 @@ abstract interface class SingBoxConfigOptionRepository {
|
||||
class ConfigOptionRepositoryImpl
|
||||
with ExceptionHandler, InfraLogger
|
||||
implements ConfigOptionRepository {
|
||||
ConfigOptionRepositoryImpl({required this.preferences});
|
||||
ConfigOptionRepositoryImpl({
|
||||
required this.preferences,
|
||||
required this.singbox,
|
||||
});
|
||||
|
||||
final SharedPreferences preferences;
|
||||
final SingboxService singbox;
|
||||
|
||||
@override
|
||||
Either<ConfigOptionFailure, ConfigOptionEntity> getConfigOption() {
|
||||
@@ -107,6 +113,35 @@ class ConfigOptionRepositoryImpl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<ConfigOptionFailure, Unit> 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,
|
||||
),
|
||||
),
|
||||
)
|
||||
.run();
|
||||
},
|
||||
(error, stackTrace) {
|
||||
loggy.error(error);
|
||||
return ConfigOptionUnexpectedFailure(error, stackTrace);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SingBoxConfigOptionRepositoryImpl
|
||||
|
||||
@@ -61,6 +61,8 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
||||
@Default(false) bool enableWarp,
|
||||
@Default(WarpDetourMode.outbound) WarpDetourMode warpDetourMode,
|
||||
@Default("") String warpLicenseKey,
|
||||
@Default("") String warpAccountId,
|
||||
@Default("") String warpAccessToken,
|
||||
@Default("auto") String warpCleanIp,
|
||||
@Default(0) int warpPort,
|
||||
@OptionalRangeJsonConverter()
|
||||
@@ -133,6 +135,8 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
||||
enableWarp: patch.enableWarp ?? enableWarp,
|
||||
warpDetourMode: patch.warpDetourMode ?? warpDetourMode,
|
||||
warpLicenseKey: patch.warpLicenseKey ?? warpLicenseKey,
|
||||
warpAccountId: patch.warpAccountId ?? warpAccountId,
|
||||
warpAccessToken: patch.warpAccessToken ?? warpAccessToken,
|
||||
warpCleanIp: patch.warpCleanIp ?? warpCleanIp,
|
||||
warpPort: patch.warpPort ?? warpPort,
|
||||
warpNoise: patch.warpNoise ?? warpNoise,
|
||||
@@ -183,6 +187,8 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
||||
enableWarp: enableWarp,
|
||||
warpDetourMode: warpDetourMode,
|
||||
warpLicenseKey: warpLicenseKey,
|
||||
warpAccountId: warpAccountId,
|
||||
warpAccessToken: warpAccessToken,
|
||||
warpCleanIp: warpCleanIp,
|
||||
warpPort: warpPort,
|
||||
warpNoise: warpNoise,
|
||||
@@ -237,6 +243,8 @@ class ConfigOptionPatch with _$ConfigOptionPatch {
|
||||
bool? enableWarp,
|
||||
WarpDetourMode? warpDetourMode,
|
||||
String? warpLicenseKey,
|
||||
String? warpAccountId,
|
||||
String? warpAccessToken,
|
||||
String? warpCleanIp,
|
||||
int? warpPort,
|
||||
@OptionalRangeJsonConverter() OptionalRange? warpNoise,
|
||||
|
||||
@@ -14,6 +14,9 @@ sealed class ConfigOptionFailure with _$ConfigOptionFailure, Failure {
|
||||
StackTrace? stackTrace,
|
||||
]) = ConfigOptionUnexpectedFailure;
|
||||
|
||||
@With<ExpectedFailure>()
|
||||
const factory ConfigOptionFailure.missingWarp() = MissingWarpConfigFailure;
|
||||
|
||||
@override
|
||||
({String type, String? message}) present(TranslationsEn t) {
|
||||
return switch (this) {
|
||||
@@ -21,6 +24,10 @@ sealed class ConfigOptionFailure with _$ConfigOptionFailure, Failure {
|
||||
type: t.failure.unexpected,
|
||||
message: null,
|
||||
),
|
||||
MissingWarpConfigFailure() => (
|
||||
type: t.settings.config.missingWarpConfig,
|
||||
message: null,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,71 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_data_providers.dart';
|
||||
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'warp_option_notifier.freezed.dart';
|
||||
part 'warp_option_notifier.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class WarpOptionNotifier extends _$WarpOptionNotifier {
|
||||
class WarpOptionNotifier extends _$WarpOptionNotifier with AppLogger {
|
||||
@override
|
||||
bool build() {
|
||||
return ref
|
||||
.read(sharedPreferencesProvider)
|
||||
.requireValue
|
||||
.getBool(warpConsentGiven) ??
|
||||
false;
|
||||
WarpOptions build() {
|
||||
final consent = _prefs.getBool(warpConsentGiven) ?? false;
|
||||
bool hasWarpConfig = false;
|
||||
try {
|
||||
final accountId = _prefs.getString("warp-account-id");
|
||||
final accessToken = _prefs.getString("warp-access-token");
|
||||
hasWarpConfig = accountId != null && accessToken != null;
|
||||
} catch (e) {
|
||||
loggy.warning(e);
|
||||
}
|
||||
|
||||
return WarpOptions(
|
||||
consentGiven: consent,
|
||||
configGeneration: hasWarpConfig
|
||||
? const AsyncValue.data(unit)
|
||||
: AsyncError(const MissingWarpConfigFailure(), StackTrace.current),
|
||||
);
|
||||
}
|
||||
|
||||
SharedPreferences get _prefs =>
|
||||
ref.read(sharedPreferencesProvider).requireValue;
|
||||
|
||||
Future<void> agree() async {
|
||||
await ref
|
||||
.read(sharedPreferencesProvider)
|
||||
.requireValue
|
||||
.setBool(warpConsentGiven, true);
|
||||
state = true;
|
||||
state = state.copyWith(consentGiven: true);
|
||||
await generateWarpConfig();
|
||||
}
|
||||
|
||||
Future<void> generateWarpConfig() async {
|
||||
if (state.configGeneration.isLoading) return;
|
||||
state = state.copyWith(configGeneration: const AsyncLoading());
|
||||
final result = await AsyncValue.guard(
|
||||
() async => await ref
|
||||
.read(configOptionRepositoryProvider)
|
||||
.generateWarpConfig()
|
||||
.getOrElse((l) {
|
||||
loggy.warning("error generating warp config: $l", l);
|
||||
throw l;
|
||||
}).run(),
|
||||
);
|
||||
state = state.copyWith(configGeneration: result);
|
||||
}
|
||||
|
||||
static const warpConsentGiven = "warp_consent_given";
|
||||
}
|
||||
|
||||
@freezed
|
||||
class WarpOptions with _$WarpOptions {
|
||||
const factory WarpOptions({
|
||||
required bool consentGiven,
|
||||
required AsyncValue<Unit> configGeneration,
|
||||
}) = _WarpOptions;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
|
||||
final warpPrefaceCompleted = ref.watch(warpOptionNotifierProvider);
|
||||
final warpOptions = ref.watch(warpOptionNotifierProvider);
|
||||
final warpPrefaceCompleted = warpOptions.consentGiven;
|
||||
final canChangeOptions = warpPrefaceCompleted && options.enableWarp;
|
||||
|
||||
return Column(
|
||||
@@ -51,6 +52,26 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(t.settings.config.generateWarpConfig),
|
||||
subtitle: canChangeOptions
|
||||
? switch (warpOptions.configGeneration) {
|
||||
AsyncLoading() => const LinearProgressIndicator(),
|
||||
AsyncError() => Text(
|
||||
t.settings.config.missingWarpConfig,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
_ => null,
|
||||
}
|
||||
: null,
|
||||
enabled: canChangeOptions,
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(warpOptionNotifierProvider.notifier)
|
||||
.generateWarpConfig();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(t.settings.config.warpDetourMode),
|
||||
subtitle: Text(options.warpDetourMode.present(t)),
|
||||
|
||||
Reference in New Issue
Block a user