new: add more warp modes, handle both ipv4 and ipv6 in wireguard, add customizable size and more
This commit is contained in:
@@ -411,6 +411,8 @@
|
|||||||
"warpCleanIp": "Clean IP",
|
"warpCleanIp": "Clean IP",
|
||||||
"warpPort": "Port",
|
"warpPort": "Port",
|
||||||
"warpNoise": "Noise Count",
|
"warpNoise": "Noise Count",
|
||||||
|
"warpNoiseSize": "Noise Size",
|
||||||
|
"warpNoiseMode": "Noise Mode",
|
||||||
"warpNoiseDelay": "Noise Delay"
|
"warpNoiseDelay": "Noise Delay"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
core.version=1.6.3
|
core.version=1.7.0
|
||||||
|
|||||||
@@ -271,6 +271,10 @@ abstract class ConfigOptions {
|
|||||||
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
||||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||||
);
|
);
|
||||||
|
static final warpNoiseMode = PreferencesNotifier.create<String, String>(
|
||||||
|
"warp-noise-mode",
|
||||||
|
"m6",
|
||||||
|
);
|
||||||
|
|
||||||
static final warpNoiseDelay = PreferencesNotifier.create<OptionalRange, String>(
|
static final warpNoiseDelay = PreferencesNotifier.create<OptionalRange, String>(
|
||||||
"warp-noise-delay",
|
"warp-noise-delay",
|
||||||
@@ -278,6 +282,12 @@ abstract class ConfigOptions {
|
|||||||
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
||||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||||
);
|
);
|
||||||
|
static final warpNoiseSize = PreferencesNotifier.create<OptionalRange, String>(
|
||||||
|
"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<String, String>(
|
static final warpWireguardConfig = PreferencesNotifier.create<String, String>(
|
||||||
"warp-wireguard-config",
|
"warp-wireguard-config",
|
||||||
@@ -361,6 +371,8 @@ abstract class ConfigOptions {
|
|||||||
"warp.clean-ip": warpCleanIp,
|
"warp.clean-ip": warpCleanIp,
|
||||||
"warp.clean-port": warpPort,
|
"warp.clean-port": warpPort,
|
||||||
"warp.noise": warpNoise,
|
"warp.noise": warpNoise,
|
||||||
|
"warp.noise-size": warpNoiseSize,
|
||||||
|
"warp.noise-mode": warpNoiseMode,
|
||||||
"warp.noise-delay": warpNoiseDelay,
|
"warp.noise-delay": warpNoiseDelay,
|
||||||
"warp.wireguard-config": warpWireguardConfig,
|
"warp.wireguard-config": warpWireguardConfig,
|
||||||
"warp2.license-key": warp2LicenseKey,
|
"warp2.license-key": warp2LicenseKey,
|
||||||
@@ -461,6 +473,8 @@ abstract class ConfigOptions {
|
|||||||
cleanIp: ref.watch(warpCleanIp),
|
cleanIp: ref.watch(warpCleanIp),
|
||||||
cleanPort: ref.watch(warpPort),
|
cleanPort: ref.watch(warpPort),
|
||||||
noise: ref.watch(warpNoise),
|
noise: ref.watch(warpNoise),
|
||||||
|
noiseMode: ref.watch(warpNoiseMode),
|
||||||
|
noiseSize: ref.watch(warpNoiseSize),
|
||||||
noiseDelay: ref.watch(warpNoiseDelay),
|
noiseDelay: ref.watch(warpNoiseDelay),
|
||||||
),
|
),
|
||||||
warp2: SingboxWarpOption(
|
warp2: SingboxWarpOption(
|
||||||
@@ -473,6 +487,8 @@ abstract class ConfigOptions {
|
|||||||
cleanIp: ref.watch(warpCleanIp),
|
cleanIp: ref.watch(warpCleanIp),
|
||||||
cleanPort: ref.watch(warpPort),
|
cleanPort: ref.watch(warpPort),
|
||||||
noise: ref.watch(warpNoise),
|
noise: ref.watch(warpNoise),
|
||||||
|
noiseMode: ref.watch(warpNoiseMode),
|
||||||
|
noiseSize: ref.watch(warpNoiseSize),
|
||||||
noiseDelay: ref.watch(warpNoiseDelay),
|
noiseDelay: ref.watch(warpNoiseDelay),
|
||||||
),
|
),
|
||||||
// geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
// geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
||||||
|
|||||||
@@ -113,6 +113,21 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
|||||||
presentValue: (value) => value.present(t),
|
presentValue: (value) => value.present(t),
|
||||||
formatInputValue: (value) => value.format(),
|
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(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.warpNoiseDelay),
|
value: ref.watch(ConfigOptions.warpNoiseDelay),
|
||||||
preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier),
|
preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier),
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ abstract interface class ProfileRepository {
|
|||||||
TaskEither<ProfileFailure, Unit> deleteById(String id);
|
TaskEither<ProfileFailure, Unit> deleteById(String id);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileRepositoryImpl
|
class ProfileRepositoryImpl with ExceptionHandler, InfraLogger implements ProfileRepository {
|
||||||
with ExceptionHandler, InfraLogger
|
|
||||||
implements ProfileRepository {
|
|
||||||
ProfileRepositoryImpl({
|
ProfileRepositoryImpl({
|
||||||
required this.profileDataSource,
|
required this.profileDataSource,
|
||||||
required this.profilePathResolver,
|
required this.profilePathResolver,
|
||||||
@@ -105,10 +103,7 @@ class ProfileRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Either<ProfileFailure, ProfileEntity?>> watchActiveProfile() {
|
Stream<Either<ProfileFailure, ProfileEntity?>> watchActiveProfile() {
|
||||||
return profileDataSource
|
return profileDataSource.watchActiveProfile().map((event) => event?.toEntity()).handleExceptions(
|
||||||
.watchActiveProfile()
|
|
||||||
.map((event) => event?.toEntity())
|
|
||||||
.handleExceptions(
|
|
||||||
(error, stackTrace) {
|
(error, stackTrace) {
|
||||||
loggy.error("error watching active profile", error, stackTrace);
|
loggy.error("error watching active profile", error, stackTrace);
|
||||||
return ProfileUnexpectedFailure(error, stackTrace);
|
return ProfileUnexpectedFailure(error, stackTrace);
|
||||||
@@ -118,10 +113,7 @@ class ProfileRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Either<ProfileFailure, bool>> watchHasAnyProfile() {
|
Stream<Either<ProfileFailure, bool>> watchHasAnyProfile() {
|
||||||
return profileDataSource
|
return profileDataSource.watchProfilesCount().map((event) => event != 0).handleExceptions(ProfileUnexpectedFailure.new);
|
||||||
.watchProfilesCount()
|
|
||||||
.map((event) => event != 0)
|
|
||||||
.handleExceptions(ProfileUnexpectedFailure.new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -129,10 +121,7 @@ class ProfileRepositoryImpl
|
|||||||
ProfilesSort sort = ProfilesSort.lastUpdate,
|
ProfilesSort sort = ProfilesSort.lastUpdate,
|
||||||
SortMode sortMode = SortMode.ascending,
|
SortMode sortMode = SortMode.ascending,
|
||||||
}) {
|
}) {
|
||||||
return profileDataSource
|
return profileDataSource.watchAll(sort: sort, sortMode: sortMode).map((event) => event.map((e) => e.toEntity()).toList()).handleExceptions(ProfileUnexpectedFailure.new);
|
||||||
.watchAll(sort: sort, sortMode: sortMode)
|
|
||||||
.map((event) => event.map((e) => e.toEntity()).toList())
|
|
||||||
.handleExceptions(ProfileUnexpectedFailure.new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -143,14 +132,10 @@ class ProfileRepositoryImpl
|
|||||||
}) {
|
}) {
|
||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
() async {
|
() async {
|
||||||
final existingProfile = await profileDataSource
|
final existingProfile = await profileDataSource.getByUrl(url).then((value) => value?.toEntity());
|
||||||
.getByUrl(url)
|
|
||||||
.then((value) => value?.toEntity());
|
|
||||||
if (existingProfile case RemoteProfileEntity()) {
|
if (existingProfile case RemoteProfileEntity()) {
|
||||||
loggy.info("profile with same url already exists, updating");
|
loggy.info("profile with same url already exists, updating");
|
||||||
final baseProfile = markAsActive
|
final baseProfile = markAsActive ? existingProfile.copyWith(active: true) : existingProfile;
|
||||||
? existingProfile.copyWith(active: true)
|
|
||||||
: existingProfile;
|
|
||||||
return updateSubscription(
|
return updateSubscription(
|
||||||
baseProfile,
|
baseProfile,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
@@ -163,9 +148,7 @@ class ProfileRepositoryImpl
|
|||||||
(profile) => TaskEither(
|
(profile) => TaskEither(
|
||||||
() async {
|
() async {
|
||||||
await profileDataSource.insert(
|
await profileDataSource.insert(
|
||||||
profile
|
profile.copyWith(id: profileId, active: markAsActive).toEntry(),
|
||||||
.copyWith(id: profileId, active: markAsActive)
|
|
||||||
.toEntry(),
|
|
||||||
);
|
);
|
||||||
return right(unit);
|
return right(unit);
|
||||||
},
|
},
|
||||||
@@ -188,10 +171,7 @@ class ProfileRepositoryImpl
|
|||||||
) {
|
) {
|
||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
() {
|
() {
|
||||||
return singbox
|
return singbox.validateConfigByPath(path, tempPath, debug).mapLeft(ProfileFailure.invalidConfig).run();
|
||||||
.validateConfigByPath(path, tempPath, debug)
|
|
||||||
.mapLeft(ProfileFailure.invalidConfig)
|
|
||||||
.run();
|
|
||||||
},
|
},
|
||||||
ProfileUnexpectedFailure.new,
|
ProfileUnexpectedFailure.new,
|
||||||
);
|
);
|
||||||
@@ -273,9 +253,7 @@ class ProfileRepositoryImpl
|
|||||||
final configFile = profilePathResolver.file(id);
|
final configFile = profilePathResolver.file(id);
|
||||||
// TODO pass options
|
// TODO pass options
|
||||||
return await $(
|
return await $(
|
||||||
singbox
|
singbox.generateFullConfigByPath(configFile.path).mapLeft(ProfileFailure.unexpected),
|
||||||
.generateFullConfigByPath(configFile.path)
|
|
||||||
.mapLeft(ProfileFailure.unexpected),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).handleExceptions(ProfileFailure.unexpected);
|
).handleExceptions(ProfileFailure.unexpected);
|
||||||
@@ -296,9 +274,7 @@ class ProfileRepositoryImpl
|
|||||||
.flatMap(
|
.flatMap(
|
||||||
(remoteProfile) => TaskEither(
|
(remoteProfile) => TaskEither(
|
||||||
() async {
|
() async {
|
||||||
final profilePatch = remoteProfile
|
final profilePatch = remoteProfile.subInfoPatch().copyWith(lastUpdate: Value(DateTime.now()));
|
||||||
.subInfoPatch()
|
|
||||||
.copyWith(lastUpdate: Value(DateTime.now()));
|
|
||||||
|
|
||||||
await profileDataSource.edit(
|
await profileDataSource.edit(
|
||||||
baseProfile.id,
|
baseProfile.id,
|
||||||
@@ -306,8 +282,7 @@ class ProfileRepositoryImpl
|
|||||||
? profilePatch.copyWith(
|
? profilePatch.copyWith(
|
||||||
name: Value(baseProfile.name),
|
name: Value(baseProfile.name),
|
||||||
url: Value(baseProfile.url),
|
url: Value(baseProfile.url),
|
||||||
updateInterval:
|
updateInterval: Value(baseProfile.options?.updateInterval),
|
||||||
Value(baseProfile.options?.updateInterval),
|
|
||||||
)
|
)
|
||||||
: profilePatch,
|
: profilePatch,
|
||||||
);
|
);
|
||||||
@@ -393,8 +368,7 @@ class ProfileRepositoryImpl
|
|||||||
tempFile.path,
|
tempFile.path,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
final headers =
|
final headers = await _populateHeaders(response.headers.map, tempFile.path);
|
||||||
await _populateHeaders(response.headers.map, tempFile.path);
|
|
||||||
return await validateConfig(file.path, tempFile.path, false)
|
return await validateConfig(file.path, tempFile.path, false)
|
||||||
.andThen(
|
.andThen(
|
||||||
() => TaskEither(() async {
|
() => TaskEither(() async {
|
||||||
@@ -444,15 +418,9 @@ class ProfileRepositoryImpl
|
|||||||
if (line.startsWith("#") || line.startsWith("//")) {
|
if (line.startsWith("#") || line.startsWith("//")) {
|
||||||
final index = line.indexOf(':');
|
final index = line.indexOf(':');
|
||||||
if (index == -1) continue;
|
if (index == -1) continue;
|
||||||
final key = line
|
final key = line.substring(0, index).replaceFirst(RegExp("^#|//"), "").trim().toLowerCase();
|
||||||
.substring(0, index)
|
|
||||||
.replaceFirst(RegExp("^#|//"), "")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase();
|
|
||||||
final value = line.substring(index + 1).trim();
|
final value = line.substring(index + 1).trim();
|
||||||
if (!headers.keys.contains(key) &&
|
if (!headers.keys.contains(key) && _subInfoHeaders.contains(key) && value.isNotEmpty) {
|
||||||
_subInfoHeaders.contains(key) &&
|
|
||||||
value.isNotEmpty) {
|
|
||||||
headers[key] = [value];
|
headers[key] = [value];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fpdart/fpdart.dart';
|
import 'package:fpdart/fpdart.dart';
|
||||||
import 'package:hiddify/core/haptic/haptic_service.dart';
|
import 'package:hiddify/core/haptic/haptic_service.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/notification/in_app_notification_controller.dart';
|
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
|
||||||
import 'package:hiddify/core/preferences/general_preferences.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/connection/notifier/connection_notifier.dart';
|
||||||
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
||||||
import 'package:hiddify/features/profile/data/profile_repository.dart';
|
import 'package:hiddify/features/profile/data/profile_repository.dart';
|
||||||
@@ -53,6 +60,7 @@ class AddProfile extends _$AddProfile with AppLogger {
|
|||||||
Future<void> add(String rawInput) async {
|
Future<void> add(String rawInput) async {
|
||||||
if (state.isLoading) return;
|
if (state.isLoading) return;
|
||||||
state = const AsyncLoading();
|
state = const AsyncLoading();
|
||||||
|
// await check4Warp(rawInput);
|
||||||
state = await AsyncValue.guard(
|
state = await AsyncValue.guard(
|
||||||
() async {
|
() async {
|
||||||
final activeProfile = await ref.read(activeProfileProvider.future);
|
final activeProfile = await ref.read(activeProfileProvider.future);
|
||||||
@@ -100,6 +108,48 @@ class AddProfile extends _$AddProfile with AppLogger {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> 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<bool>(
|
||||||
|
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
|
@riverpod
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ class SingboxWarpOption with _$SingboxWarpOption {
|
|||||||
required String cleanIp,
|
required String cleanIp,
|
||||||
required int cleanPort,
|
required int cleanPort,
|
||||||
@OptionalRangeJsonConverter() required OptionalRange noise,
|
@OptionalRangeJsonConverter() required OptionalRange noise,
|
||||||
|
@OptionalRangeJsonConverter() required OptionalRange noiseSize,
|
||||||
@OptionalRangeJsonConverter() required OptionalRange noiseDelay,
|
@OptionalRangeJsonConverter() required OptionalRange noiseDelay,
|
||||||
|
@OptionalRangeJsonConverter() required String noiseMode,
|
||||||
}) = _SingboxWarpOption;
|
}) = _SingboxWarpOption;
|
||||||
|
|
||||||
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) => _$SingboxWarpOptionFromJson(json);
|
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) => _$SingboxWarpOptionFromJson(json);
|
||||||
|
|||||||
2
libcore
2
libcore
Submodule libcore updated: 96fafad01d...e378a83c26
Reference in New Issue
Block a user