diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index d430c9e3..ace136d8 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -411,6 +411,8 @@ "warpCleanIp": "Clean IP", "warpPort": "Port", "warpNoise": "Noise Count", + "warpNoiseSize": "Noise Size", + "warpNoiseMode": "Noise Mode", "warpNoiseDelay": "Noise Delay" } } diff --git a/dependencies.properties b/dependencies.properties index 50374082..45e729d5 100644 --- a/dependencies.properties +++ b/dependencies.properties @@ -1 +1 @@ -core.version=1.6.3 +core.version=1.7.0 diff --git a/lib/features/config_option/data/config_option_repository.dart b/lib/features/config_option/data/config_option_repository.dart index 06303e4c..cccebbbd 100644 --- a/lib/features/config_option/data/config_option_repository.dart +++ b/lib/features/config_option/data/config_option_repository.dart @@ -271,6 +271,10 @@ abstract class ConfigOptions { mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true), mapTo: const OptionalRangeJsonConverter().toJson, ); + static final warpNoiseMode = PreferencesNotifier.create( + "warp-noise-mode", + "m6", + ); static final warpNoiseDelay = PreferencesNotifier.create( "warp-noise-delay", @@ -278,6 +282,12 @@ abstract class ConfigOptions { mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true), mapTo: const OptionalRangeJsonConverter().toJson, ); + static final warpNoiseSize = PreferencesNotifier.create( + "warp-noise-size", + const OptionalRange(min: 10, max: 30), + mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true), + mapTo: const OptionalRangeJsonConverter().toJson, + ); static final warpWireguardConfig = PreferencesNotifier.create( "warp-wireguard-config", @@ -361,6 +371,8 @@ abstract class ConfigOptions { "warp.clean-ip": warpCleanIp, "warp.clean-port": warpPort, "warp.noise": warpNoise, + "warp.noise-size": warpNoiseSize, + "warp.noise-mode": warpNoiseMode, "warp.noise-delay": warpNoiseDelay, "warp.wireguard-config": warpWireguardConfig, "warp2.license-key": warp2LicenseKey, @@ -461,6 +473,8 @@ abstract class ConfigOptions { cleanIp: ref.watch(warpCleanIp), cleanPort: ref.watch(warpPort), noise: ref.watch(warpNoise), + noiseMode: ref.watch(warpNoiseMode), + noiseSize: ref.watch(warpNoiseSize), noiseDelay: ref.watch(warpNoiseDelay), ), warp2: SingboxWarpOption( @@ -473,6 +487,8 @@ abstract class ConfigOptions { cleanIp: ref.watch(warpCleanIp), cleanPort: ref.watch(warpPort), noise: ref.watch(warpNoise), + noiseMode: ref.watch(warpNoiseMode), + noiseSize: ref.watch(warpNoiseSize), noiseDelay: ref.watch(warpNoiseDelay), ), // geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath( diff --git a/lib/features/config_option/overview/warp_options_widgets.dart b/lib/features/config_option/overview/warp_options_widgets.dart index ba02bf00..15ae1d19 100644 --- a/lib/features/config_option/overview/warp_options_widgets.dart +++ b/lib/features/config_option/overview/warp_options_widgets.dart @@ -113,6 +113,21 @@ class WarpOptionsTiles extends HookConsumerWidget { presentValue: (value) => value.present(t), formatInputValue: (value) => value.format(), ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.warpNoiseMode), + preferences: ref.watch(ConfigOptions.warpNoiseMode.notifier), + enabled: canChangeOptions, + title: t.config.warpNoiseMode, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.warpNoiseSize), + preferences: ref.watch(ConfigOptions.warpNoiseSize.notifier), + enabled: canChangeOptions, + title: t.config.warpNoiseSize, + inputToValue: (input) => OptionalRange.tryParse(input, allowEmpty: true), + presentValue: (value) => value.present(t), + formatInputValue: (value) => value.format(), + ), ValuePreferenceWidget( value: ref.watch(ConfigOptions.warpNoiseDelay), preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier), diff --git a/lib/features/profile/data/profile_repository.dart b/lib/features/profile/data/profile_repository.dart index 9343a651..a50518ab 100644 --- a/lib/features/profile/data/profile_repository.dart +++ b/lib/features/profile/data/profile_repository.dart @@ -62,9 +62,7 @@ abstract interface class ProfileRepository { TaskEither deleteById(String id); } -class ProfileRepositoryImpl - with ExceptionHandler, InfraLogger - implements ProfileRepository { +class ProfileRepositoryImpl with ExceptionHandler, InfraLogger implements ProfileRepository { ProfileRepositoryImpl({ required this.profileDataSource, required this.profilePathResolver, @@ -105,10 +103,7 @@ class ProfileRepositoryImpl @override Stream> watchActiveProfile() { - return profileDataSource - .watchActiveProfile() - .map((event) => event?.toEntity()) - .handleExceptions( + return profileDataSource.watchActiveProfile().map((event) => event?.toEntity()).handleExceptions( (error, stackTrace) { loggy.error("error watching active profile", error, stackTrace); return ProfileUnexpectedFailure(error, stackTrace); @@ -118,10 +113,7 @@ class ProfileRepositoryImpl @override Stream> watchHasAnyProfile() { - return profileDataSource - .watchProfilesCount() - .map((event) => event != 0) - .handleExceptions(ProfileUnexpectedFailure.new); + return profileDataSource.watchProfilesCount().map((event) => event != 0).handleExceptions(ProfileUnexpectedFailure.new); } @override @@ -129,10 +121,7 @@ class ProfileRepositoryImpl ProfilesSort sort = ProfilesSort.lastUpdate, SortMode sortMode = SortMode.ascending, }) { - return profileDataSource - .watchAll(sort: sort, sortMode: sortMode) - .map((event) => event.map((e) => e.toEntity()).toList()) - .handleExceptions(ProfileUnexpectedFailure.new); + return profileDataSource.watchAll(sort: sort, sortMode: sortMode).map((event) => event.map((e) => e.toEntity()).toList()).handleExceptions(ProfileUnexpectedFailure.new); } @override @@ -143,14 +132,10 @@ class ProfileRepositoryImpl }) { return exceptionHandler( () async { - final existingProfile = await profileDataSource - .getByUrl(url) - .then((value) => value?.toEntity()); + final existingProfile = await profileDataSource.getByUrl(url).then((value) => value?.toEntity()); if (existingProfile case RemoteProfileEntity()) { loggy.info("profile with same url already exists, updating"); - final baseProfile = markAsActive - ? existingProfile.copyWith(active: true) - : existingProfile; + final baseProfile = markAsActive ? existingProfile.copyWith(active: true) : existingProfile; return updateSubscription( baseProfile, cancelToken: cancelToken, @@ -163,9 +148,7 @@ class ProfileRepositoryImpl (profile) => TaskEither( () async { await profileDataSource.insert( - profile - .copyWith(id: profileId, active: markAsActive) - .toEntry(), + profile.copyWith(id: profileId, active: markAsActive).toEntry(), ); return right(unit); }, @@ -188,10 +171,7 @@ class ProfileRepositoryImpl ) { return exceptionHandler( () { - return singbox - .validateConfigByPath(path, tempPath, debug) - .mapLeft(ProfileFailure.invalidConfig) - .run(); + return singbox.validateConfigByPath(path, tempPath, debug).mapLeft(ProfileFailure.invalidConfig).run(); }, ProfileUnexpectedFailure.new, ); @@ -273,9 +253,7 @@ class ProfileRepositoryImpl final configFile = profilePathResolver.file(id); // TODO pass options return await $( - singbox - .generateFullConfigByPath(configFile.path) - .mapLeft(ProfileFailure.unexpected), + singbox.generateFullConfigByPath(configFile.path).mapLeft(ProfileFailure.unexpected), ); }, ).handleExceptions(ProfileFailure.unexpected); @@ -296,9 +274,7 @@ class ProfileRepositoryImpl .flatMap( (remoteProfile) => TaskEither( () async { - final profilePatch = remoteProfile - .subInfoPatch() - .copyWith(lastUpdate: Value(DateTime.now())); + final profilePatch = remoteProfile.subInfoPatch().copyWith(lastUpdate: Value(DateTime.now())); await profileDataSource.edit( baseProfile.id, @@ -306,8 +282,7 @@ class ProfileRepositoryImpl ? profilePatch.copyWith( name: Value(baseProfile.name), url: Value(baseProfile.url), - updateInterval: - Value(baseProfile.options?.updateInterval), + updateInterval: Value(baseProfile.options?.updateInterval), ) : profilePatch, ); @@ -393,8 +368,7 @@ class ProfileRepositoryImpl tempFile.path, cancelToken: cancelToken, ); - final headers = - await _populateHeaders(response.headers.map, tempFile.path); + final headers = await _populateHeaders(response.headers.map, tempFile.path); return await validateConfig(file.path, tempFile.path, false) .andThen( () => TaskEither(() async { @@ -444,15 +418,9 @@ class ProfileRepositoryImpl if (line.startsWith("#") || line.startsWith("//")) { final index = line.indexOf(':'); if (index == -1) continue; - final key = line - .substring(0, index) - .replaceFirst(RegExp("^#|//"), "") - .trim() - .toLowerCase(); + final key = line.substring(0, index).replaceFirst(RegExp("^#|//"), "").trim().toLowerCase(); final value = line.substring(index + 1).trim(); - if (!headers.keys.contains(key) && - _subInfoHeaders.contains(key) && - value.isNotEmpty) { + if (!headers.keys.contains(key) && _subInfoHeaders.contains(key) && value.isNotEmpty) { headers[key] = [value]; } } diff --git a/lib/features/profile/notifier/profile_notifier.dart b/lib/features/profile/notifier/profile_notifier.dart index d351d999..f0d6d886 100644 --- a/lib/features/profile/notifier/profile_notifier.dart +++ b/lib/features/profile/notifier/profile_notifier.dart @@ -1,10 +1,17 @@ +import 'dart:async'; + import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/core/haptic/haptic_service.dart'; import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/model/failures.dart'; import 'package:hiddify/core/notification/in_app_notification_controller.dart'; import 'package:hiddify/core/preferences/general_preferences.dart'; +import 'package:hiddify/core/preferences/preferences_provider.dart'; +import 'package:hiddify/features/common/adaptive_root_scaffold.dart'; +import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart'; +import 'package:hiddify/features/config_option/overview/warp_options_widgets.dart'; import 'package:hiddify/features/connection/notifier/connection_notifier.dart'; import 'package:hiddify/features/profile/data/profile_data_providers.dart'; import 'package:hiddify/features/profile/data/profile_repository.dart'; @@ -53,6 +60,7 @@ class AddProfile extends _$AddProfile with AppLogger { Future add(String rawInput) async { if (state.isLoading) return; state = const AsyncLoading(); + // await check4Warp(rawInput); state = await AsyncValue.guard( () async { final activeProfile = await ref.read(activeProfileProvider.future); @@ -100,6 +108,48 @@ class AddProfile extends _$AddProfile with AppLogger { }, ); } + + Future check4Warp(String rawInput) async { + for (final line in rawInput.split("\n")) { + if (line.toLowerCase().startsWith("warp://")) { + final _prefs = ref.read(sharedPreferencesProvider).requireValue; + final _warp = ref.read(warpOptionNotifierProvider.notifier); + + final consent = false && (_prefs.getBool(WarpOptionNotifier.warpConsentGiven) ?? false); + + final t = ref.read(translationsProvider); + final notification = ref.read(inAppNotificationControllerProvider); + + if (!consent) { + final agreed = await showDialog( + context: RootScaffold.stateKey.currentContext!, + builder: (context) => const WarpLicenseAgreementModal(), + ); + + if (agreed ?? false) { + await _prefs.setBool(WarpOptionNotifier.warpConsentGiven, true); + final toast = notification.showInfoToast(t.profile.add.addingWarpMsg, duration: const Duration(milliseconds: 100)); + toast?.pause(); + await _warp.generateWarpConfig(); + toast?.start(); + } else { + return; + } + } + + final accountId = _prefs.getString("warp2-account-id"); + final accessToken = _prefs.getString("warp2-access-token"); + final hasWarp2Config = accountId != null && accessToken != null; + + if (!hasWarp2Config || true) { + final toast = notification.showInfoToast(t.profile.add.addingWarpMsg, duration: const Duration(milliseconds: 100)); + toast?.pause(); + await _warp.generateWarp2Config(); + toast?.start(); + } + } + } + } } @riverpod diff --git a/lib/singbox/model/singbox_config_option.dart b/lib/singbox/model/singbox_config_option.dart index 06735e79..aefe9a70 100644 --- a/lib/singbox/model/singbox_config_option.dart +++ b/lib/singbox/model/singbox_config_option.dart @@ -74,7 +74,9 @@ class SingboxWarpOption with _$SingboxWarpOption { required String cleanIp, required int cleanPort, @OptionalRangeJsonConverter() required OptionalRange noise, + @OptionalRangeJsonConverter() required OptionalRange noiseSize, @OptionalRangeJsonConverter() required OptionalRange noiseDelay, + @OptionalRangeJsonConverter() required String noiseMode, }) = _SingboxWarpOption; factory SingboxWarpOption.fromJson(Map json) => _$SingboxWarpOptionFromJson(json); diff --git a/libcore b/libcore index 96fafad0..e378a83c 160000 --- a/libcore +++ b/libcore @@ -1 +1 @@ -Subproject commit 96fafad01d07421ef6cf72834e77f6a7f8663d0c +Subproject commit e378a83c26a98922feaec2cb44c60813a46b231c