Add cloudflare warp options
This commit is contained in:
@@ -11,7 +11,10 @@
|
|||||||
},
|
},
|
||||||
"sort": "Sort",
|
"sort": "Sort",
|
||||||
"sortBy": "Sort by",
|
"sortBy": "Sort by",
|
||||||
"addToClipboard": "Add to clipboard"
|
"addToClipboard": "Add to clipboard",
|
||||||
|
"notSet": "Not Set",
|
||||||
|
"agree": "Agree",
|
||||||
|
"decline": "Decline"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "by continuing you agree with ${tap(@:about.termsAndConditions)}",
|
"termsAndPolicyCaution(rich)": "by continuing you agree with ${tap(@:about.termsAndConditions)}",
|
||||||
@@ -204,8 +207,13 @@
|
|||||||
"mux": "Multiplexer",
|
"mux": "Multiplexer",
|
||||||
"outbound": "Outbound Options",
|
"outbound": "Outbound Options",
|
||||||
"tlsTricks": "TLS Tricks",
|
"tlsTricks": "TLS Tricks",
|
||||||
|
"warp": "WARP Options",
|
||||||
"misc": "Misc Options"
|
"misc": "Misc Options"
|
||||||
},
|
},
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "Cloudflare WARP Consent",
|
||||||
|
"description(rich)": "Cloudflare WARP is a free WireGuard VPN provider. By enabling this option you are agreeing to the Cloudflare WARP's ${tos(Terms of Service)} and ${privacy(Privacy Policy)}."
|
||||||
|
},
|
||||||
"pageTitle": "Config Options",
|
"pageTitle": "Config Options",
|
||||||
"logLevel": "Log Level",
|
"logLevel": "Log Level",
|
||||||
"resolveDestination": "Resolve Destination",
|
"resolveDestination": "Resolve Destination",
|
||||||
@@ -243,7 +251,17 @@
|
|||||||
"tlsPaddingSize": "TLS Padding",
|
"tlsPaddingSize": "TLS Padding",
|
||||||
"enableMux": "Enable Mux",
|
"enableMux": "Enable Mux",
|
||||||
"muxProtocol": "Mux Protocol",
|
"muxProtocol": "Mux Protocol",
|
||||||
"muxMaxStreams": "Max Concurrent Streams"
|
"muxMaxStreams": "Max Concurrent Streams",
|
||||||
|
"enableWarp": "Enable WARP",
|
||||||
|
"warpDetourMode": "Detour Mode",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"inbound": "Detour WARP through proxies",
|
||||||
|
"outbound": "Detour proxies through WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "License Key",
|
||||||
|
"warpCleanIp": "Clean IP",
|
||||||
|
"warpPort": "Port",
|
||||||
|
"warpNoise": "Noise"
|
||||||
},
|
},
|
||||||
"geoAssets": {
|
"geoAssets": {
|
||||||
"pageTitle": "Routing Assets",
|
"pageTitle": "Routing Assets",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
},
|
},
|
||||||
"sort": "Clasificar",
|
"sort": "Clasificar",
|
||||||
"sortBy": "Ordenar por",
|
"sortBy": "Ordenar por",
|
||||||
"addToClipboard": "Añadir al portapapeles"
|
"addToClipboard": "Añadir al portapapeles",
|
||||||
|
"notSet": "No establecido",
|
||||||
|
"agree": "Aceptar",
|
||||||
|
"decline": "Rechazar"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"emptyProfilesMsg": "Comience agregando un perfil de suscripción",
|
"emptyProfilesMsg": "Comience agregando un perfil de suscripción",
|
||||||
@@ -211,6 +214,7 @@
|
|||||||
"mux": "Multiplexer",
|
"mux": "Multiplexer",
|
||||||
"outbound": "Opciones de salida",
|
"outbound": "Opciones de salida",
|
||||||
"tlsTricks": "Trucos TLS",
|
"tlsTricks": "Trucos TLS",
|
||||||
|
"warp": "WARP Options",
|
||||||
"misc": "Opciones varias"
|
"misc": "Opciones varias"
|
||||||
},
|
},
|
||||||
"pageTitle": "Opciones de configuración",
|
"pageTitle": "Opciones de configuración",
|
||||||
@@ -237,7 +241,21 @@
|
|||||||
"tlsPaddingSize": "Relleno TLS",
|
"tlsPaddingSize": "Relleno TLS",
|
||||||
"enableMux": "Enable Mux",
|
"enableMux": "Enable Mux",
|
||||||
"muxProtocol": "Mux Protocol",
|
"muxProtocol": "Mux Protocol",
|
||||||
"muxMaxStreams": "Max Concurrent Streams"
|
"muxMaxStreams": "Max Concurrent Streams",
|
||||||
|
"enableWarp": "Enable WARP",
|
||||||
|
"warpDetourMode": "Detour Mode",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"inbound": "Detour WARP through proxies",
|
||||||
|
"outbound": "Detour proxies through WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "License Key",
|
||||||
|
"warpCleanIp": "Clean IP",
|
||||||
|
"warpPort": "Port",
|
||||||
|
"warpNoise": "Noise",
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "Consentimiento WARP de Cloudflare",
|
||||||
|
"description(rich)": "Cloudflare WARP es un proveedor de VPN WireGuard gratuito. Al habilitar esta opción, acepta los ${tos(Términos de servicio)} y ${privacy(Política de privacidad)} de Cloudflare WARP."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"geoAssets": {
|
"geoAssets": {
|
||||||
"successMsg": "Activo actualizado correctamente",
|
"successMsg": "Activo actualizado correctamente",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
},
|
},
|
||||||
"sort": "مرتبسازی",
|
"sort": "مرتبسازی",
|
||||||
"sortBy": "مرتبسازی براساس",
|
"sortBy": "مرتبسازی براساس",
|
||||||
"addToClipboard": "به کلیپ بورد اضافه کنید"
|
"addToClipboard": "به کلیپ بورد اضافه کنید",
|
||||||
|
"notSet": "تنظیم نشده",
|
||||||
|
"agree": "موافق",
|
||||||
|
"decline": "کاهش می یابد"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "در صورت ادامه با ${tap(@:about.termsAndConditions)} موافقت میکنید",
|
"termsAndPolicyCaution(rich)": "در صورت ادامه با ${tap(@:about.termsAndConditions)} موافقت میکنید",
|
||||||
@@ -204,6 +207,7 @@
|
|||||||
"mux": "Multiplexer",
|
"mux": "Multiplexer",
|
||||||
"outbound": "Outbound Options",
|
"outbound": "Outbound Options",
|
||||||
"tlsTricks": "TLS Tricks",
|
"tlsTricks": "TLS Tricks",
|
||||||
|
"warp": "WARP Options",
|
||||||
"misc": "تنظیمات متفرقه"
|
"misc": "تنظیمات متفرقه"
|
||||||
},
|
},
|
||||||
"pageTitle": "تنظیمات کانفیگ",
|
"pageTitle": "تنظیمات کانفیگ",
|
||||||
@@ -243,7 +247,21 @@
|
|||||||
"tlsPaddingSize": "TLS Padding",
|
"tlsPaddingSize": "TLS Padding",
|
||||||
"enableMux": "Enable Mux",
|
"enableMux": "Enable Mux",
|
||||||
"muxProtocol": "Mux Protocol",
|
"muxProtocol": "Mux Protocol",
|
||||||
"muxMaxStreams": "Max Concurrent Streams"
|
"muxMaxStreams": "Max Concurrent Streams",
|
||||||
|
"enableWarp": "Enable WARP",
|
||||||
|
"warpDetourMode": "Detour Mode",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"inbound": "Detour WARP through proxies",
|
||||||
|
"outbound": "Detour proxies through WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "License Key",
|
||||||
|
"warpCleanIp": "Clean IP",
|
||||||
|
"warpPort": "Port",
|
||||||
|
"warpNoise": "Noise",
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "رضایت Cloudflare WARP",
|
||||||
|
"description(rich)": "Cloudflare WARP یک ارائه دهنده رایگان WireGuard VPN است. با فعال کردن این گزینه، با ${tos(شرایط خدمات)} و ${privacy(خطمشی رازداری)} Cloudflare WARP موافقت میکنید."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"geoAssets": {
|
"geoAssets": {
|
||||||
"pageTitle": "فایلهای مسیریابی",
|
"pageTitle": "فایلهای مسیریابی",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
},
|
},
|
||||||
"sort": "Сортировка",
|
"sort": "Сортировка",
|
||||||
"sortBy": "Сортировка",
|
"sortBy": "Сортировка",
|
||||||
"addToClipboard": "Копировать в буфер обмена"
|
"addToClipboard": "Копировать в буфер обмена",
|
||||||
|
"notSet": "Не задано",
|
||||||
|
"agree": "Соглашаться",
|
||||||
|
"decline": "Отклонить"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "Продолжая, Вы соглашаетесь с ${tap(@:about.termsAndConditions)}",
|
"termsAndPolicyCaution(rich)": "Продолжая, Вы соглашаетесь с ${tap(@:about.termsAndConditions)}",
|
||||||
@@ -204,6 +207,7 @@
|
|||||||
"mux": "Multiplexer",
|
"mux": "Multiplexer",
|
||||||
"outbound": "Outbound Options",
|
"outbound": "Outbound Options",
|
||||||
"tlsTricks": "TLS Tricks",
|
"tlsTricks": "TLS Tricks",
|
||||||
|
"warp": "WARP Options",
|
||||||
"misc": "Разные параметры"
|
"misc": "Разные параметры"
|
||||||
},
|
},
|
||||||
"pageTitle": "Параметры конфигурации",
|
"pageTitle": "Параметры конфигурации",
|
||||||
@@ -243,7 +247,21 @@
|
|||||||
"tlsPaddingSize": "TLS Padding",
|
"tlsPaddingSize": "TLS Padding",
|
||||||
"enableMux": "Enable Mux",
|
"enableMux": "Enable Mux",
|
||||||
"muxProtocol": "Mux Protocol",
|
"muxProtocol": "Mux Protocol",
|
||||||
"muxMaxStreams": "Max Concurrent Streams"
|
"muxMaxStreams": "Max Concurrent Streams",
|
||||||
|
"enableWarp": "Enable WARP",
|
||||||
|
"warpDetourMode": "Detour Mode",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"inbound": "Detour WARP through proxies",
|
||||||
|
"outbound": "Detour proxies through WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "License Key",
|
||||||
|
"warpCleanIp": "Clean IP",
|
||||||
|
"warpPort": "Port",
|
||||||
|
"warpNoise": "Noise",
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "Согласие Cloudflare WARP",
|
||||||
|
"description(rich)": "Cloudflare WARP — бесплатный провайдер WireGuard VPN. Включая эту опцию, вы соглашаетесь с ${tos(Условиями обслуживания)} и ${privacy(Политикой конфиденциальности)} Cloudflare WARP."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"geoAssets": {
|
"geoAssets": {
|
||||||
"pageTitle": "Активы маршрутизации",
|
"pageTitle": "Активы маршрутизации",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
},
|
},
|
||||||
"sort": "Sırala",
|
"sort": "Sırala",
|
||||||
"sortBy": "Sırala",
|
"sortBy": "Sırala",
|
||||||
"addToClipboard": "Panoya ekle"
|
"addToClipboard": "Panoya ekle",
|
||||||
|
"notSet": "Ayarlanmadı",
|
||||||
|
"agree": "Kabul etmek",
|
||||||
|
"decline": "Reddetmek"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "devam ederek ${tap(@:about.termsAndConditions)} kabul etmiş olursunuz",
|
"termsAndPolicyCaution(rich)": "devam ederek ${tap(@:about.termsAndConditions)} kabul etmiş olursunuz",
|
||||||
@@ -204,6 +207,7 @@
|
|||||||
"mux": "Multiplexer",
|
"mux": "Multiplexer",
|
||||||
"outbound": "Outbound Options",
|
"outbound": "Outbound Options",
|
||||||
"tlsTricks": "TLS Tricks",
|
"tlsTricks": "TLS Tricks",
|
||||||
|
"warp": "WARP Options",
|
||||||
"misc": "Çeşitli Seçenekler"
|
"misc": "Çeşitli Seçenekler"
|
||||||
},
|
},
|
||||||
"pageTitle": "Yapılandırma Seçenekleri",
|
"pageTitle": "Yapılandırma Seçenekleri",
|
||||||
@@ -243,7 +247,21 @@
|
|||||||
"tlsPaddingSize": "TLS Padding",
|
"tlsPaddingSize": "TLS Padding",
|
||||||
"enableMux": "Enable Mux",
|
"enableMux": "Enable Mux",
|
||||||
"muxProtocol": "Mux Protocol",
|
"muxProtocol": "Mux Protocol",
|
||||||
"muxMaxStreams": "Max Concurrent Streams"
|
"muxMaxStreams": "Max Concurrent Streams",
|
||||||
|
"enableWarp": "Enable WARP",
|
||||||
|
"warpDetourMode": "Detour Mode",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"inbound": "Detour WARP through proxies",
|
||||||
|
"outbound": "Detour proxies through WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "License Key",
|
||||||
|
"warpCleanIp": "Clean IP",
|
||||||
|
"warpPort": "Port",
|
||||||
|
"warpNoise": "Noise",
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "Cloudflare WARP Onayı",
|
||||||
|
"description(rich)": "Cloudflare WARP ücretsiz bir WireGuard VPN sağlayıcısıdır. Bu seçeneği etkinleştirerek Cloudflare WARP'ın ${tos(Hizmet Şartları)} ve ${privacy(Gizlilik Politikası)}'nı kabul etmiş olursunuz."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"geoAssets": {
|
"geoAssets": {
|
||||||
"pageTitle": "Varlıkları Yönlendirme",
|
"pageTitle": "Varlıkları Yönlendirme",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
},
|
},
|
||||||
"sort": "排序",
|
"sort": "排序",
|
||||||
"sortBy": "排序方式",
|
"sortBy": "排序方式",
|
||||||
"addToClipboard": "添加到剪贴板"
|
"addToClipboard": "添加到剪贴板",
|
||||||
|
"notSet": "没有设置",
|
||||||
|
"agree": "同意",
|
||||||
|
"decline": "衰退"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "继续即表示您同意 ${tap(@:about.termsAndConditions)}",
|
"termsAndPolicyCaution(rich)": "继续即表示您同意 ${tap(@:about.termsAndConditions)}",
|
||||||
@@ -204,6 +207,7 @@
|
|||||||
"mux": "Multiplexer",
|
"mux": "Multiplexer",
|
||||||
"outbound": "出站选项",
|
"outbound": "出站选项",
|
||||||
"tlsTricks": "TLS Tricks",
|
"tlsTricks": "TLS Tricks",
|
||||||
|
"warp": "WARP Options",
|
||||||
"misc": "其它选项"
|
"misc": "其它选项"
|
||||||
},
|
},
|
||||||
"pageTitle": "配置选项",
|
"pageTitle": "配置选项",
|
||||||
@@ -243,7 +247,21 @@
|
|||||||
"tlsPaddingSize": "TLS 填充",
|
"tlsPaddingSize": "TLS 填充",
|
||||||
"enableMux": "Enable Mux",
|
"enableMux": "Enable Mux",
|
||||||
"muxProtocol": "Mux Protocol",
|
"muxProtocol": "Mux Protocol",
|
||||||
"muxMaxStreams": "Max Concurrent Streams"
|
"muxMaxStreams": "Max Concurrent Streams",
|
||||||
|
"enableWarp": "Enable WARP",
|
||||||
|
"warpDetourMode": "Detour Mode",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"inbound": "Detour WARP through proxies",
|
||||||
|
"outbound": "Detour proxies through WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "License Key",
|
||||||
|
"warpCleanIp": "Clean IP",
|
||||||
|
"warpPort": "Port",
|
||||||
|
"warpNoise": "Noise",
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "Cloudflare WARP 同意",
|
||||||
|
"description(rich)": "Cloudflare WARP 是免费的 WireGuard VPN 提供商。启用此选项即表示您同意 Cloudflare WARP 的 ${tos(服务条款)} 和 ${privacy(隐私政策)}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"geoAssets": {
|
"geoAssets": {
|
||||||
"pageTitle": "路由资源文件",
|
"pageTitle": "路由资源文件",
|
||||||
|
|||||||
@@ -10,4 +10,8 @@ abstract class Constants {
|
|||||||
static const telegramChannelUrl = "https://t.me/hiddify";
|
static const telegramChannelUrl = "https://t.me/hiddify";
|
||||||
static const privacyPolicyUrl = "https://hiddify.com/privacy-policy/";
|
static const privacyPolicyUrl = "https://hiddify.com/privacy-policy/";
|
||||||
static const termsAndConditionsUrl = "https://hiddify.com/terms/";
|
static const termsAndConditionsUrl = "https://hiddify.com/terms/";
|
||||||
|
static const cfWarpPrivacyPolicy =
|
||||||
|
"https://www.cloudflare.com/application/privacypolicy/";
|
||||||
|
static const cfWarpTermsOfService =
|
||||||
|
"https://www.cloudflare.com/application/terms/";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import 'package:dartx/dartx.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
|
|
||||||
part 'range.freezed.dart';
|
part 'range.freezed.dart';
|
||||||
|
|
||||||
@@ -7,14 +9,21 @@ class RangeWithOptionalCeil with _$RangeWithOptionalCeil {
|
|||||||
const RangeWithOptionalCeil._();
|
const RangeWithOptionalCeil._();
|
||||||
|
|
||||||
const factory RangeWithOptionalCeil({
|
const factory RangeWithOptionalCeil({
|
||||||
required int min,
|
int? min,
|
||||||
int? max,
|
int? max,
|
||||||
}) = _RangeWithOptionalCeil;
|
}) = _RangeWithOptionalCeil;
|
||||||
|
|
||||||
String format() => "$min${max != null ? "-$max" : ""}";
|
String format() => [min, max].whereNotNull().join("-");
|
||||||
|
String present(TranslationsEn t) =>
|
||||||
|
format().isEmpty ? t.general.notSet : format();
|
||||||
|
|
||||||
factory RangeWithOptionalCeil.fromString(String input) =>
|
factory RangeWithOptionalCeil._fromString(
|
||||||
|
String input, {
|
||||||
|
bool allowEmpty = true,
|
||||||
|
}) =>
|
||||||
switch (input.split("-")) {
|
switch (input.split("-")) {
|
||||||
|
[final String val] when val.isEmpty && allowEmpty =>
|
||||||
|
const RangeWithOptionalCeil(),
|
||||||
[final String min] => RangeWithOptionalCeil(min: int.parse(min)),
|
[final String min] => RangeWithOptionalCeil(min: int.parse(min)),
|
||||||
[final String min, final String max] => RangeWithOptionalCeil(
|
[final String min, final String max] => RangeWithOptionalCeil(
|
||||||
min: int.parse(min),
|
min: int.parse(min),
|
||||||
@@ -23,9 +32,12 @@ class RangeWithOptionalCeil with _$RangeWithOptionalCeil {
|
|||||||
_ => throw Exception("Invalid range: $input"),
|
_ => throw Exception("Invalid range: $input"),
|
||||||
};
|
};
|
||||||
|
|
||||||
static RangeWithOptionalCeil? tryParse(String input) {
|
static RangeWithOptionalCeil? tryParse(
|
||||||
|
String input, {
|
||||||
|
bool allowEmpty = false,
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
return RangeWithOptionalCeil.fromString(input);
|
return RangeWithOptionalCeil._fromString(input);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -38,7 +50,7 @@ class RangeWithOptionalCeilJsonConverter
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
RangeWithOptionalCeil fromJson(String json) =>
|
RangeWithOptionalCeil fromJson(String json) =>
|
||||||
RangeWithOptionalCeil.fromString(json);
|
RangeWithOptionalCeil._fromString(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toJson(RangeWithOptionalCeil object) => object.format();
|
String toJson(RangeWithOptionalCeil object) => object.format();
|
||||||
|
|||||||
@@ -117,6 +117,12 @@ class ConfigOptionRepositoryImpl
|
|||||||
muxPadding: persisted.muxPadding,
|
muxPadding: persisted.muxPadding,
|
||||||
muxMaxStreams: persisted.muxMaxStreams,
|
muxMaxStreams: persisted.muxMaxStreams,
|
||||||
muxProtocol: persisted.muxProtocol,
|
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,
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
|||||||
@Default(false) bool muxPadding,
|
@Default(false) bool muxPadding,
|
||||||
@Default(8) int muxMaxStreams,
|
@Default(8) int muxMaxStreams,
|
||||||
@Default(MuxProtocol.h2mux) MuxProtocol muxProtocol,
|
@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;
|
}) = _ConfigOptionEntity;
|
||||||
|
|
||||||
static ConfigOptionEntity initial = ConfigOptionEntity(
|
static ConfigOptionEntity initial = ConfigOptionEntity(
|
||||||
@@ -67,7 +75,11 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
|||||||
if (PlatformUtils.isDesktop && serviceMode == ServiceMode.tun) {
|
if (PlatformUtils.isDesktop && serviceMode == ServiceMode.tun) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (enableTlsFragment || enableTlsMixedSniCase || enableTlsPadding||enableMux) {
|
if (enableTlsFragment ||
|
||||||
|
enableTlsMixedSniCase ||
|
||||||
|
enableTlsPadding ||
|
||||||
|
enableMux ||
|
||||||
|
enableWarp) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +129,12 @@ class ConfigOptionEntity with _$ConfigOptionEntity {
|
|||||||
muxPadding: patch.muxPadding ?? muxPadding,
|
muxPadding: patch.muxPadding ?? muxPadding,
|
||||||
muxMaxStreams: patch.muxMaxStreams ?? muxMaxStreams,
|
muxMaxStreams: patch.muxMaxStreams ?? muxMaxStreams,
|
||||||
muxProtocol: patch.muxProtocol ?? muxProtocol,
|
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ class ConfigOptionPatch with _$ConfigOptionPatch {
|
|||||||
bool? muxPadding,
|
bool? muxPadding,
|
||||||
int? muxMaxStreams,
|
int? muxMaxStreams,
|
||||||
MuxProtocol? muxProtocol,
|
MuxProtocol? muxProtocol,
|
||||||
|
bool? enableWarp,
|
||||||
|
WarpDetourMode? warpDetourMode,
|
||||||
|
String? warpLicenseKey,
|
||||||
|
String? warpCleanIp,
|
||||||
|
int? warpPort,
|
||||||
|
@RangeWithOptionalCeilJsonConverter() RangeWithOptionalCeil? warpNoise,
|
||||||
}) = _ConfigOptionPatch;
|
}) = _ConfigOptionPatch;
|
||||||
|
|
||||||
factory ConfigOptionPatch.fromJson(Map<String, dynamic> json) =>
|
factory ConfigOptionPatch.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'warp_option_notifier.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class WarpOptionNotifier extends _$WarpOptionNotifier {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return ref
|
||||||
|
.read(sharedPreferencesProvider)
|
||||||
|
.requireValue
|
||||||
|
.getBool(warpConsentGiven) ??
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> agree() async {
|
||||||
|
await ref
|
||||||
|
.read(sharedPreferencesProvider)
|
||||||
|
.requireValue
|
||||||
|
.setBool(warpConsentGiven, true);
|
||||||
|
state = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const warpConsentGiven = "warp_consent_given";
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import 'package:hiddify/core/widget/tip_card.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/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/log/model/log_level.dart';
|
import 'package:hiddify/features/log/model/log_level.dart';
|
||||||
import 'package:hiddify/features/settings/widgets/sections_widgets.dart';
|
import 'package:hiddify/features/settings/widgets/sections_widgets.dart';
|
||||||
import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart';
|
import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart';
|
||||||
@@ -319,7 +320,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.settings.config.tlsFragmentSize),
|
title: Text(t.settings.config.tlsFragmentSize),
|
||||||
subtitle: Text(options.tlsFragmentSize.format()),
|
subtitle: Text(options.tlsFragmentSize.present(t)),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final range = await SettingsInputDialog(
|
final range = await SettingsInputDialog(
|
||||||
title: t.settings.config.tlsFragmentSize,
|
title: t.settings.config.tlsFragmentSize,
|
||||||
@@ -336,7 +337,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.settings.config.tlsFragmentSleep),
|
title: Text(t.settings.config.tlsFragmentSleep),
|
||||||
subtitle: Text(options.tlsFragmentSleep.format()),
|
subtitle: Text(options.tlsFragmentSleep.present(t)),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final range = await SettingsInputDialog(
|
final range = await SettingsInputDialog(
|
||||||
title: t.settings.config.tlsFragmentSleep,
|
title: t.settings.config.tlsFragmentSleep,
|
||||||
@@ -368,7 +369,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.settings.config.tlsPaddingSize),
|
title: Text(t.settings.config.tlsPaddingSize),
|
||||||
subtitle: Text(options.tlsPaddingSize.format()),
|
subtitle: Text(options.tlsPaddingSize.present(t)),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final range = await SettingsInputDialog(
|
final range = await SettingsInputDialog(
|
||||||
title: t.settings.config.tlsPaddingSize,
|
title: t.settings.config.tlsPaddingSize,
|
||||||
@@ -384,6 +385,13 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SettingsDivider(),
|
const SettingsDivider(),
|
||||||
|
SettingsSection(experimental(t.settings.config.section.warp)),
|
||||||
|
WarpOptionsTiles(
|
||||||
|
options: options,
|
||||||
|
defaultOptions: defaultOptions,
|
||||||
|
onChange: changeOption,
|
||||||
|
),
|
||||||
|
const SettingsDivider(),
|
||||||
SettingsSection(t.settings.config.section.misc),
|
SettingsSection(t.settings.config.section.misc),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.settings.config.connectionTestUrl),
|
title: Text(t.settings.config.connectionTestUrl),
|
||||||
|
|||||||
199
lib/features/config_option/overview/warp_options_widgets.dart
Normal file
199
lib/features/config_option/overview/warp_options_widgets.dart
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import 'package:dartx/dartx.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
|
import 'package:hiddify/core/model/constants.dart';
|
||||||
|
import 'package:hiddify/core/model/range.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/settings/widgets/settings_input_dialog.dart';
|
||||||
|
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||||
|
import 'package:hiddify/utils/uri_utils.dart';
|
||||||
|
import 'package:hiddify/utils/validators.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
class WarpOptionsTiles extends HookConsumerWidget {
|
||||||
|
const WarpOptionsTiles({
|
||||||
|
required this.options,
|
||||||
|
required this.defaultOptions,
|
||||||
|
required this.onChange,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ConfigOptionEntity options;
|
||||||
|
final ConfigOptionEntity defaultOptions;
|
||||||
|
final Future<void> Function(ConfigOptionPatch patch) onChange;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final t = ref.watch(translationsProvider);
|
||||||
|
|
||||||
|
final warpPrefaceCompleted = ref.watch(warpOptionNotifierProvider);
|
||||||
|
final canChangeOptions = warpPrefaceCompleted && options.enableWarp;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
title: Text(t.settings.config.enableWarp),
|
||||||
|
value: options.enableWarp,
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (!warpPrefaceCompleted) {
|
||||||
|
final agreed = await showAdaptiveDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const WarpLicenseAgreementModal(),
|
||||||
|
);
|
||||||
|
if (agreed ?? false) {
|
||||||
|
await ref.read(warpOptionNotifierProvider.notifier).agree();
|
||||||
|
await onChange(ConfigOptionPatch(enableWarp: value));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await onChange(ConfigOptionPatch(enableWarp: value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(t.settings.config.warpDetourMode),
|
||||||
|
subtitle: Text(options.warpDetourMode.present(t)),
|
||||||
|
enabled: canChangeOptions,
|
||||||
|
onTap: () async {
|
||||||
|
final warpDetourMode = await SettingsPickerDialog(
|
||||||
|
title: t.settings.config.warpDetourMode,
|
||||||
|
selected: options.warpDetourMode,
|
||||||
|
options: WarpDetourMode.values,
|
||||||
|
getTitle: (e) => e.present(t),
|
||||||
|
resetValue: defaultOptions.warpDetourMode,
|
||||||
|
).show(context);
|
||||||
|
if (warpDetourMode == null) return;
|
||||||
|
await onChange(
|
||||||
|
ConfigOptionPatch(warpDetourMode: warpDetourMode),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(t.settings.config.warpLicenseKey),
|
||||||
|
subtitle: Text(
|
||||||
|
options.warpLicenseKey.isEmpty
|
||||||
|
? t.general.notSet
|
||||||
|
: options.warpLicenseKey,
|
||||||
|
),
|
||||||
|
enabled: canChangeOptions,
|
||||||
|
onTap: () async {
|
||||||
|
final licenseKey = await SettingsInputDialog(
|
||||||
|
title: t.settings.config.warpLicenseKey,
|
||||||
|
initialValue: options.warpLicenseKey,
|
||||||
|
resetValue: defaultOptions.warpLicenseKey,
|
||||||
|
).show(context);
|
||||||
|
if (licenseKey == null) return;
|
||||||
|
await onChange(ConfigOptionPatch(warpLicenseKey: licenseKey));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(t.settings.config.warpCleanIp),
|
||||||
|
subtitle: Text(options.warpCleanIp),
|
||||||
|
enabled: canChangeOptions,
|
||||||
|
onTap: () async {
|
||||||
|
final warpCleanIp = await SettingsInputDialog(
|
||||||
|
title: t.settings.config.warpCleanIp,
|
||||||
|
initialValue: options.warpCleanIp,
|
||||||
|
resetValue: defaultOptions.warpCleanIp,
|
||||||
|
).show(context);
|
||||||
|
if (warpCleanIp == null || warpCleanIp.isBlank) return;
|
||||||
|
await onChange(ConfigOptionPatch(warpCleanIp: warpCleanIp));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(t.settings.config.warpPort),
|
||||||
|
subtitle: Text(options.warpPort.toString()),
|
||||||
|
enabled: canChangeOptions,
|
||||||
|
onTap: () async {
|
||||||
|
final warpPort = await SettingsInputDialog(
|
||||||
|
title: t.settings.config.warpPort,
|
||||||
|
initialValue: options.warpPort,
|
||||||
|
resetValue: defaultOptions.warpPort,
|
||||||
|
validator: isPort,
|
||||||
|
mapTo: int.tryParse,
|
||||||
|
digitsOnly: true,
|
||||||
|
).show(context);
|
||||||
|
if (warpPort == null) return;
|
||||||
|
await onChange(
|
||||||
|
ConfigOptionPatch(warpPort: warpPort),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(t.settings.config.warpNoise),
|
||||||
|
subtitle: Text(options.warpNoise.present(t)),
|
||||||
|
enabled: canChangeOptions,
|
||||||
|
onTap: () async {
|
||||||
|
final warpNoise = await SettingsInputDialog(
|
||||||
|
title: t.settings.config.warpNoise,
|
||||||
|
initialValue: options.warpNoise.format(),
|
||||||
|
resetValue: defaultOptions.warpNoise.format(),
|
||||||
|
).show(context);
|
||||||
|
if (warpNoise == null) return;
|
||||||
|
await onChange(
|
||||||
|
ConfigOptionPatch(
|
||||||
|
warpNoise: RangeWithOptionalCeil.tryParse(
|
||||||
|
warpNoise,
|
||||||
|
allowEmpty: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WarpLicenseAgreementModal extends HookConsumerWidget {
|
||||||
|
const WarpLicenseAgreementModal({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final t = ref.watch(translationsProvider);
|
||||||
|
|
||||||
|
return AlertDialog.adaptive(
|
||||||
|
title: Text(t.settings.config.warpConsent.title),
|
||||||
|
content: Text.rich(
|
||||||
|
t.settings.config.warpConsent.description(
|
||||||
|
tos: (text) => TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: const TextStyle(color: Colors.blue),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () async {
|
||||||
|
await UriUtils.tryLaunch(
|
||||||
|
Uri.parse(Constants.cfWarpTermsOfService),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
privacy: (text) => TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: const TextStyle(color: Colors.blue),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () async {
|
||||||
|
await UriUtils.tryLaunch(
|
||||||
|
Uri.parse(Constants.cfWarpPrivacyPolicy),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(false);
|
||||||
|
},
|
||||||
|
child: Text(t.general.decline),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: Text(t.general.agree),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,3 +78,13 @@ enum MuxProtocol {
|
|||||||
smux,
|
smux,
|
||||||
yamux;
|
yamux;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum WarpDetourMode {
|
||||||
|
outbound,
|
||||||
|
inbound;
|
||||||
|
|
||||||
|
String present(TranslationsEn t) => switch (this) {
|
||||||
|
outbound => t.settings.config.warpDetourModes.outbound,
|
||||||
|
inbound => t.settings.config.warpDetourModes.inbound,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ class SingboxConfigOption with _$SingboxConfigOption {
|
|||||||
required bool muxPadding,
|
required bool muxPadding,
|
||||||
required int muxMaxStreams,
|
required int muxMaxStreams,
|
||||||
required MuxProtocol muxProtocol,
|
required MuxProtocol muxProtocol,
|
||||||
|
required bool enableWarp,
|
||||||
|
required WarpDetourMode warpDetourMode,
|
||||||
|
required String warpLicenseKey,
|
||||||
|
required String warpCleanIp,
|
||||||
|
required int warpPort,
|
||||||
|
@RangeWithOptionalCeilJsonConverter()
|
||||||
|
required RangeWithOptionalCeil warpNoise,
|
||||||
required String geoipPath,
|
required String geoipPath,
|
||||||
required String geositePath,
|
required String geositePath,
|
||||||
required List<SingboxRule> rules,
|
required List<SingboxRule> rules,
|
||||||
|
|||||||
Reference in New Issue
Block a user