diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fa952aa..f923e8b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -803,5 +803,8 @@ "myOtherApps": "My other apps", "myOtherAppsDescription": "Check my other apps, make a donation, contact support, and more", "topToBottom": "From top to bottom", - "bottomToTop": "From bottom to top" + "bottomToTop": "From bottom to top", + "upstreamTimeout": "Upstream timeout", + "upstreamTimeoutHelper": "Specifies the number of seconds to wait for a response from the upstream server", + "fieldCannotBeEmpty": "This field cannot be empty" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 19a36cf..c93c501 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -803,5 +803,8 @@ "myOtherApps": "Mis otras apps", "myOtherAppsDescription": "Comprueba mis otras apps, hacer una donación, contactar al soporte, y más", "topToBottom": "Desde arriba hacia abajo", - "bottomToTop": "Desde abajo hacia arriba" + "bottomToTop": "Desde abajo hacia arriba", + "upstreamTimeout": "Tiempo de espera del upstream", + "upstreamTimeoutHelper": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream", + "fieldCannotBeEmpty": "El campo no puede estar vacío" } \ No newline at end of file diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 5a53023..7007644 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -26,6 +26,7 @@ class DnsInfo { int? ratelimitSubnetLenIpv4; int? ratelimitSubnetLenIpv6; List? ratelimitWhitelist; + int? upstreamTimeout; DnsInfo({ required this.upstreamDns, @@ -55,6 +56,7 @@ class DnsInfo { required this.ratelimitSubnetLenIpv4, required this.ratelimitSubnetLenIpv6, required this.ratelimitWhitelist, + required this.upstreamTimeout, }); factory DnsInfo.fromJson(Map json) => DnsInfo( @@ -85,6 +87,7 @@ class DnsInfo { ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"], ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"], ratelimitWhitelist: json["ratelimit_whitelist"] != null ? List.from(json["ratelimit_whitelist"].map((x) => x)) : [], + upstreamTimeout: json["upstream_timeout"], ); Map toJson() => { @@ -115,5 +118,6 @@ class DnsInfo { "ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4, "ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6, "ratelimit_whitelist": ratelimitWhitelist != null ? List.from(ratelimitWhitelist!.map((x) => x)) : null, + "upstream_timeout": upstreamTimeout, }; } diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart index 33818a1..1ae8c5e 100644 --- a/lib/screens/settings/dns/upstream_dns.dart +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -1,5 +1,3 @@ -// ignore_for_file: use_build_context_synchronously - import 'dart:io'; import 'package:flutter/material.dart'; @@ -30,6 +28,9 @@ class _UpstreamDnsScreenState extends State { bool validValues = false; + final upstreamTimeoutController = TextEditingController(); + String? upstreamTimeoutError = null; + checkValidValues() { if ( dnsServers.isNotEmpty && @@ -61,6 +62,7 @@ class _UpstreamDnsScreenState extends State { } } upstreamMode = dnsProvider.dnsInfo!.upstreamMode ?? ""; + upstreamTimeoutController.text = dnsProvider.dnsInfo!.upstreamTimeout != null ? dnsProvider.dnsInfo!.upstreamTimeout.toString() : ""; validValues = true; super.initState(); } @@ -72,6 +74,23 @@ class _UpstreamDnsScreenState extends State { final width = MediaQuery.of(context).size.width; + void validateTimeout(String value) { + if (value != '' && int.tryParse(value) != null && int.parse(value) > 0) { + setState(() { + upstreamTimeoutError = null; + validValues = true; + }); + } + else { + setState(() { + upstreamTimeoutError = value == '' + ? AppLocalizations.of(context)!.fieldCannotBeEmpty + : AppLocalizations.of(context)!.invalidValue; + validValues = false; + }); + } + } + void openAddCommentModal() { if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { showDialog( @@ -146,11 +165,13 @@ class _UpstreamDnsScreenState extends State { final result = await dnsProvider.saveUpstreamDnsConfig({ "upstream_dns": dnsServers.map((e) => e['controller'] != null ? e['controller'].text : e['comment']).toList(), - "upstream_mode": upstreamMode + "upstream_mode": upstreamMode, + "upstream_timeout": int.tryParse(upstreamTimeoutController.text) }); processModal.close(); + if (!context.mounted) return; if (result.successful == true) { showSnackbar( appConfigProvider: appConfigProvider, @@ -312,6 +333,27 @@ class _UpstreamDnsScreenState extends State { subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, onChanged: (value) => setState(() => upstreamMode = value), ), + const SizedBox(height: 16), + SectionLabel(label: AppLocalizations.of(context)!.others), + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + controller: upstreamTimeoutController, + onChanged: validateTimeout, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.upstreamTimeout, + helperText: AppLocalizations.of(context)!.upstreamTimeoutHelper, + helperMaxLines: 2, + errorText: upstreamTimeoutError + ) + ), + ), ], ), ),