diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3f12a95..4b13044 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -435,5 +435,23 @@ "usePrivateReverseDnsResolvers": "Use private reverse DNS resolvers", "usePrivateReverseDnsResolversDescription": "Perform reverse DNS lookups for locally served addresses using these upstream servers. If disabled, AdGuard Home responds with NXDOMAIN to all such PTR requests except for clients known from DHCP, /etc/hosts, and so on.", "enableReverseResolving": "Enable reverse resolving of clients' IP addresses", - "enableReverseResolvingDescription": "Reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream servers for clients with public IP addresses)." + "enableReverseResolvingDescription": "Reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream servers for clients with public IP addresses).", + "dnsServerSettings": "AdGuard Home DNS server settings", + "limitRequestsSecond": "Rate limit per second", + "valueNotNumber": "Value is not a number", + "enableEdns": "Enable EDNS client subnet", + "enableEdnsDescription": "Add the EDNS Client Subnet option (ECS) to upstream requests and log the values sent by the clients in the query log.", + "enableDnssec": "Enable DNSSEC", + "enableDnssecDescription": "Set DNSSEC flag in the outcoming DNS queries and check the result (DNSSEC-enabled resolver is required).", + "disableResolvingIpv6": "Disable resolving of IPv6 addresses", + "disableResolvingIpv6Description": "Drop all DNS queries for IPv6 addresses (type AAAA).", + "blockingMode": "Blocking mode", + "defaultMode": "Default", + "defaultDescription": "Respond with zero IP address (0.0.0.0 for A; :: for AAAA) when blocked by Adblock-style rule; respond with the IP address specified in the rule when blocked by /etc/hosts-style rule", + "refusedDescription": "Respond with REFUSED code", + "nxdomainDescription": "Respond with NXDOMAIN code", + "nullIp": "Null IP", + "nullIpDescription": "Respond with zero IP address (0.0.0.0 for A; :: for AAAA)", + "customIp": "Custom IP", + "customIpDescription": "Respond with a manually set IP address" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c25d10e..c5f138e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -435,5 +435,23 @@ "usePrivateReverseDnsResolvers": "Usar resolutores DNS inversos y privados", "usePrivateReverseDnsResolversDescription": "Realiza búsquedas DNS inversas para direcciones servidas localmente utilizando estos servidores DNS de subida. Si está deshabilitado, AdGuard Home responderá con NXDOMAIN a todas las peticiones PTR de este tipo, excepto para los clientes conocidos por DHCP, /etc/hosts, etc.", "enableReverseResolving": "Habilitar la resolución inversa de las direcciones IP de clientes", - "enableReverseResolvingDescription": "Resuelve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas)." + "enableReverseResolvingDescription": "Resuelve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas).", + "dnsServerSettings": "Configuración del servidor DNS de AdGuard Home", + "limitRequestsSecond": "Límite de peticiones por segundo", + "valueNotNumber": "El valor no es un número", + "enableEdns": "Habilitar subred de cliente EDNS", + "enableEdnsDescription": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del DNS de subida y registra los valores enviados por los clientes en el registro de consultas.", + "enableDnssec": "Habilitar DNSSEC", + "enableDnssecDescription": "Establece el indicador DNSSEC en las consultas DNS salientes y comprueba el resultado (se requiere un resolutor habilitado para DNSSEC).", + "disableResolvingIpv6": "Deshabilitar resolución de direcciones IPv6", + "disableResolvingIpv6Description": "Descarta todas las consultas DNS para direcciones IPv6 (tipo AAAA).", + "blockingMode": "Modo de bloqueo", + "defaultMode": "Por defecto", + "defaultDescription": "Responde con dirección IP cero (0.0.0.0 para A; :: para AAAA) cuando está bloqueado por la regla de estilo Adblock; responde con la dirección IP especificada en la regla cuando está bloqueado por una regla de estilo /etc/hosts", + "refusedDescription": "Responde con el código REFUSED", + "nxdomainDescription": "Responde con el código NXDOMAIN", + "nullIp": "IP nula", + "nullIpDescription": "Responde con dirección IP cero (0.0.0.0 para A; :: para AAAA)", + "customIp": "IP personalizada", + "customIpDescription": "Responde con una dirección IP establecida manualmente." } \ No newline at end of file diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart new file mode 100644 index 0000000..3ffba3f --- /dev/null +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class BootstrapDnsScreen extends StatefulWidget { + const BootstrapDnsScreen({Key? key}) : super(key: key); + + @override + State createState() => _BootstrapDnsScreenState(); +} + +class _BootstrapDnsScreenState extends State { + List bootstrapControllers = [ + TextEditingController() + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.bootstrapDns), + ), + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Card( + margin: const EdgeInsets.only( + left: 24, right: 24, bottom: 20 + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + const Icon(Icons.info_rounded), + const SizedBox(width: 20), + SizedBox( + width: MediaQuery.of(context).size.width-132, + child: Text(AppLocalizations.of(context)!.bootstrapDnsServersInfo) + ) + ], + ), + ), + ), + const SizedBox(height: 10), + if (bootstrapControllers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noBootstrapDns, + style: const TextStyle( + color: Colors.grey, + fontSize: 16 + ), + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ...bootstrapControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 24, right: 10, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width-90, + child: TextFormField( + controller: c, + // onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.dnsServer, + ) + ), + ), + IconButton( + onPressed: () => setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()), + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )).toList(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () => setState(() => bootstrapControllers.add(TextEditingController())), + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 273de30..f9ee4f5 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -1,361 +1,69 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; -import 'package:adguard_home_manager/screens/settings/section_label.dart'; -import 'package:adguard_home_manager/screens/settings/dns/dns_mode_modal.dart'; +import 'package:adguard_home_manager/screens/settings/dns/dns_server_settings.dart'; +import 'package:adguard_home_manager/screens/settings/dns/bootstrap_dns.dart'; +import 'package:adguard_home_manager/screens/settings/dns/private_reverse_servers.dart'; +import 'package:adguard_home_manager/screens/settings/dns/upstream_dns.dart'; -class DnsSettings extends StatefulWidget { +class DnsSettings extends StatelessWidget { const DnsSettings({Key? key}) : super(key: key); - @override - State createState() => _DnsSettingsState(); -} - -class _DnsSettingsState extends State { - List upstreamControllers = [ - TextEditingController() - ]; - - String upstreamMode = "load_balancing"; - - List bootstrapControllers = [ - TextEditingController() - ]; - - List privateControllers = []; - - List defaultReverseResolvers = ["80.58.61.250", "80.58.61.251"]; - bool editReverseResolvers = false; - List reverseResolversControllers = [ - TextEditingController() - ]; - bool usePrivateReverseDnsResolvers = false; - bool enableReverseResolve = false; - - @override Widget build(BuildContext context) { - void openDnsModalSheet() { - showModalBottomSheet( - context: context, - builder: (context) => DnsModeModal( - upstreamMode: upstreamMode, - onConfirm: (value) => setState(() => upstreamMode = value), - ), - isScrollControlled: true, - backgroundColor: Colors.transparent - ); - } - - String getStringUpstreamMode() { - switch (upstreamMode) { - case 'load_balancing': - return AppLocalizations.of(context)!.loadBalancing; - - case 'parallel_requests': - return AppLocalizations.of(context)!.parallelRequests; - - case 'fastest_ip_address': - return AppLocalizations.of(context)!.fastestIpAddress; - - default: - return AppLocalizations.of(context)!.noDnsMode; - } - } - return Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context)!.dnsSettings), + title: Text(AppLocalizations.of(context)!.dnsSettings), ), body: ListView( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SectionLabel(label: AppLocalizations.of(context)!.upstreamDns), - Padding( - padding: const EdgeInsets.only(right: 10), - child: IconButton( - onPressed: () => setState(() => upstreamControllers.add(TextEditingController())), - icon: const Icon(Icons.add) - ), - ) - ], - ), - if (upstreamControllers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noUpstreamDns, - style: const TextStyle( - color: Colors.grey, - fontSize: 16 - ), - ), - ), - ), - const SizedBox(height: 20), - ], - ), - ...upstreamControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 24, right: 10, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width-90, - child: TextFormField( - controller: c, - // onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.dnsServer, - ) - ), - ), - IconButton( - onPressed: () => setState(() => upstreamControllers = upstreamControllers.where((con) => con != c).toList()), - icon: const Icon(Icons.remove_circle_outline) - ) - ], - ), - )).toList(), - Material( - color: Colors.transparent, - child: InkWell( - onTap: openDnsModalSheet, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.dnsMode, - style: const TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 5), - Text( - getStringUpstreamMode(), - style: const TextStyle( - fontSize: 14, - color: Colors.grey - ), - ), - ], - ), - ) - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SectionLabel(label: AppLocalizations.of(context)!.bootstrapDns), - Padding( - padding: const EdgeInsets.only(right: 10), - child: IconButton( - onPressed: () => setState(() => bootstrapControllers.add(TextEditingController())), - icon: const Icon(Icons.add) - ), - ) - ], - ), - Card( - margin: const EdgeInsets.only( - left: 24, right: 24, bottom: 20 - ), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - const Icon(Icons.info_rounded), - const SizedBox(width: 20), - SizedBox( - width: MediaQuery.of(context).size.width-132, - child: Text(AppLocalizations.of(context)!.bootstrapDnsServersInfo) - ) - ], + ListTile( + title: Text( + AppLocalizations.of(context)!.upstreamDns, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal ), ), - ), - const SizedBox(height: 10), - if (bootstrapControllers.isEmpty) Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - AppLocalizations.of(context)!.noBootstrapDns, - style: const TextStyle( - color: Colors.grey, - fontSize: 16 - ), - ), - ), - ), - const SizedBox(height: 20), - ], - ), - ...bootstrapControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 24, right: 10, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width-90, - child: TextFormField( - controller: c, - // onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.dnsServer, - ) - ), - ), - IconButton( - onPressed: () => setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()), - icon: const Icon(Icons.remove_circle_outline) - ) - ], - ), - )).toList(), - SectionLabel(label: AppLocalizations.of(context)!.privateReverseDnsServers), - Card( - margin: const EdgeInsets.only( - left: 24, right: 24, bottom: 10 - ), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - const Icon(Icons.info_rounded), - const SizedBox(width: 20), - SizedBox( - width: MediaQuery.of(context).size.width-132, - child: Text(AppLocalizations.of(context)!.privateReverseDnsServersDescription) - ) - ], - ), - ), - ), - if (editReverseResolvers == false) ...[ - Padding( - padding: const EdgeInsets.all(20), - child: Text( - "${AppLocalizations.of(context)!.reverseDnsDefault}:\n\n${defaultReverseResolvers.map((item) => item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.grey, - fontSize: 16 - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 10, bottom: 20), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton.icon( - onPressed: () => setState(() => editReverseResolvers = true), - icon: const Icon(Icons.edit), - label: Text(AppLocalizations.of(context)!.edit) - ), - ], - ), - ) - ], - if (editReverseResolvers == true) ...[ - const SizedBox(height: 20), - ...reverseResolversControllers.map((c) => Padding( - padding: const EdgeInsets.only( - left: 24, right: 10, bottom: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width-90, - child: TextFormField( - controller: c, - // onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.serverAddress, - ) - ), - ), - IconButton( - onPressed: () => setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()), - icon: const Icon(Icons.remove_circle_outline) - ) - ], - ), + onTap: () => Navigator.push(context, MaterialPageRoute( + builder: (context) => const UpstreamDnsScreen() )), - if (reverseResolversControllers.isEmpty) Padding( - padding: const EdgeInsets.only( - left: 20, right: 20, bottom: 20 - ), - child: Center( - child: Text( - AppLocalizations.of(context)!.noServerAddressesAdded, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.grey, - fontSize: 16 - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: () => setState(() => reverseResolversControllers.add(TextEditingController())), - icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.addItem) - ), - ], - ), - ), - ], - CustomSwitchListTile( - value: usePrivateReverseDnsResolvers, - onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value), - title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers, - subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription ), - CustomSwitchListTile( - value: enableReverseResolve, - onChanged: (value) => setState(() => enableReverseResolve = value), - title: AppLocalizations.of(context)!.enableReverseResolving, - subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription + ListTile( + title: Text( + AppLocalizations.of(context)!.bootstrapDns, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal + ), + ), + onTap: () => Navigator.push(context, MaterialPageRoute( + builder: (context) => const BootstrapDnsScreen() + )), + ), + ListTile( + title: Text( + AppLocalizations.of(context)!.privateReverseDnsServers, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal + ), + ), + onTap: () => Navigator.push(context, MaterialPageRoute( + builder: (context) => const PrivateReverseDnsServersScreen() + )), + ), + ListTile( + title: Text( + AppLocalizations.of(context)!.dnsServerSettings, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal + ), + ), + onTap: () => Navigator.push(context, MaterialPageRoute( + builder: (context) => const DnsServerSettingsScreen() + )), ), ], ), diff --git a/lib/screens/settings/dns/dns_mode_modal.dart b/lib/screens/settings/dns/dns_mode_modal.dart deleted file mode 100644 index f42a74d..0000000 --- a/lib/screens/settings/dns/dns_mode_modal.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; - -class DnsModeModal extends StatefulWidget { - final String upstreamMode; - final void Function(String) onConfirm; - - const DnsModeModal({ - Key? key, - required this.upstreamMode, - required this.onConfirm, - }) : super(key: key); - - @override - State createState() => _DnsModeModalState(); -} - -class _DnsModeModalState extends State { - String upstreamMode = ""; - - @override - void initState() { - upstreamMode = widget.upstreamMode; - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Container( - height: 660, - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) - ), - child: ListView( - physics: const NeverScrollableScrollPhysics(), - children: [ - const Padding( - padding: EdgeInsets.only(top: 28, bottom: 20), - child: Icon( - Icons.dns_rounded, - size: 26, - ), - ), - Text( - AppLocalizations.of(context)!.dnsMode, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 24 - ), - ), - const SizedBox(height: 10), - CustomRadioListTile( - groupValue: upstreamMode, - value: "load_balancing", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.loadBalancing, - subtitle: AppLocalizations.of(context)!.loadBalancingDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - CustomRadioListTile( - groupValue: upstreamMode, - value: "parallel_requests", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.parallelRequests, - subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - CustomRadioListTile( - groupValue: upstreamMode, - value: "fastest_ip_address", - radioBackgroundColor: Theme.of(context).dialogBackgroundColor, - title: AppLocalizations.of(context)!.fastestIpAddress, - subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, - onChanged: (value) => setState(() => upstreamMode = value), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 20, - right: 20 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.close) - ), - const SizedBox(width: 20), - TextButton( - onPressed: () { - Navigator.pop(context); - widget.onConfirm(upstreamMode); - }, - child: Text(AppLocalizations.of(context)!.confirm) - ), - ], - ), - ), - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart new file mode 100644 index 0000000..5490948 --- /dev/null +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; +import 'package:adguard_home_manager/screens/settings/section_label.dart'; +import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; + +class DnsServerSettingsScreen extends StatefulWidget { + const DnsServerSettingsScreen({Key? key}) : super(key: key); + + @override + State createState() => _DnsServerSettingsScreenState(); +} + +class _DnsServerSettingsScreenState extends State { + final TextEditingController limitRequestsController = TextEditingController(); + String? limitRequestsError; + bool enableEdns = false; + bool enableDnssec = false; + bool disableIpv6Resolving = false; + + String blockingMode = "default"; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.dnsServerSettings), + ), + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: limitRequestsController, + onChanged: (value) { + if (int.tryParse(value) != null) { + setState(() => limitRequestsError = null); + } + else { + setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.looks_one_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.limitRequestsSecond, + errorText: limitRequestsError + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: enableEdns, + onChanged: (value) => setState(() => enableEdns = value), + title: AppLocalizations.of(context)!.enableEdns, + subtitle: AppLocalizations.of(context)!.enableEdnsDescription, + ), + CustomSwitchListTile( + value: enableDnssec, + onChanged: (value) => setState(() => enableDnssec = value), + title: AppLocalizations.of(context)!.enableDnssec, + subtitle: AppLocalizations.of(context)!.enableDnssecDescription, + ), + CustomSwitchListTile( + value: disableIpv6Resolving, + onChanged: (value) => setState(() => disableIpv6Resolving = value), + title: AppLocalizations.of(context)!.disableResolvingIpv6, + subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description, + ), + SectionLabel(label: AppLocalizations.of(context)!.blockingMode), + CustomRadioListTile( + groupValue: blockingMode, + value: "default", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.defaultMode, + subtitle: AppLocalizations.of(context)!.defaultDescription, + onChanged: (value) => setState(() => blockingMode = value), + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "refused", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: "REFUSED", + subtitle: AppLocalizations.of(context)!.refusedDescription, + onChanged: (value) => setState(() => blockingMode = value), + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "nxdomain", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: "NXDOMAIN", + subtitle: AppLocalizations.of(context)!.nxdomainDescription, + onChanged: (value) => setState(() => blockingMode = value), + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "null_ip", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.nullIp, + subtitle: AppLocalizations.of(context)!.nullIpDescription, + onChanged: (value) => setState(() => blockingMode = value), + ), + CustomRadioListTile( + groupValue: blockingMode, + value: "custom_ip", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.customIp, + subtitle: AppLocalizations.of(context)!.customIpDescription, + onChanged: (value) => setState(() => blockingMode = value), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart new file mode 100644 index 0000000..83e0488 --- /dev/null +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; + +class PrivateReverseDnsServersScreen extends StatefulWidget { + const PrivateReverseDnsServersScreen({Key? key}) : super(key: key); + + @override + State createState() => _PrivateReverseDnsServersScreenState(); +} + +class _PrivateReverseDnsServersScreenState extends State { + List privateControllers = []; + + List defaultReverseResolvers = ["80.58.61.250", "80.58.61.251"]; + bool editReverseResolvers = false; + List reverseResolversControllers = [ + TextEditingController() + ]; + bool usePrivateReverseDnsResolvers = false; + bool enableReverseResolve = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.privateReverseDnsServers), + ), + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + Card( + margin: const EdgeInsets.only( + left: 24, right: 24, bottom: 10 + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + const Icon(Icons.info_rounded), + const SizedBox(width: 20), + SizedBox( + width: MediaQuery.of(context).size.width-132, + child: Text(AppLocalizations.of(context)!.privateReverseDnsServersDescription) + ) + ], + ), + ), + ), + if (editReverseResolvers == false) ...[ + Padding( + padding: const EdgeInsets.all(20), + child: Text( + "${AppLocalizations.of(context)!.reverseDnsDefault}:\n\n${defaultReverseResolvers.map((item) => item).join(', ').toString().replaceAll(RegExp(r'\(|\)'), '')}", + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.grey, + fontSize: 16 + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 20), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: () => setState(() => editReverseResolvers = true), + icon: const Icon(Icons.edit), + label: Text(AppLocalizations.of(context)!.edit) + ), + ], + ), + ) + ], + if (editReverseResolvers == true) ...[ + const SizedBox(height: 20), + ...reverseResolversControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 24, right: 10, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width-90, + child: TextFormField( + controller: c, + // onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.serverAddress, + ) + ), + ), + IconButton( + onPressed: () => setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()), + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )), + if (reverseResolversControllers.isEmpty) Padding( + padding: const EdgeInsets.only( + left: 20, right: 20, bottom: 20 + ), + child: Center( + child: Text( + AppLocalizations.of(context)!.noServerAddressesAdded, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.grey, + fontSize: 16 + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () => setState(() => reverseResolversControllers.add(TextEditingController())), + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + ), + ], + CustomSwitchListTile( + value: usePrivateReverseDnsResolvers, + onChanged: (value) => setState(() => usePrivateReverseDnsResolvers = value), + title: AppLocalizations.of(context)!.usePrivateReverseDnsResolvers, + subtitle: AppLocalizations.of(context)!.usePrivateReverseDnsResolversDescription + ), + CustomSwitchListTile( + value: enableReverseResolve, + onChanged: (value) => setState(() => enableReverseResolve = value), + title: AppLocalizations.of(context)!.enableReverseResolving, + subtitle: AppLocalizations.of(context)!.enableReverseResolvingDescription + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart new file mode 100644 index 0000000..46d9cb8 --- /dev/null +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/settings/section_label.dart'; +import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; + +class UpstreamDnsScreen extends StatefulWidget { + const UpstreamDnsScreen({Key? key}) : super(key: key); + + @override + State createState() => _UpstreamDnsScreenState(); +} + +class _UpstreamDnsScreenState extends State { + List upstreamControllers = [ + TextEditingController() + ]; + + String upstreamMode = "load_balancing"; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.upstreamDns), + ), + body: ListView( + padding: const EdgeInsets.only(top: 10), + children: [ + if (upstreamControllers.isEmpty) Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Center( + child: Text( + AppLocalizations.of(context)!.noUpstreamDns, + style: const TextStyle( + color: Colors.grey, + fontSize: 16 + ), + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ...upstreamControllers.map((c) => Padding( + padding: const EdgeInsets.only( + left: 24, right: 10, bottom: 20 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width-90, + child: TextFormField( + controller: c, + // onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.dnsServer, + ) + ), + ), + IconButton( + onPressed: () => setState(() => upstreamControllers = upstreamControllers.where((con) => con != c).toList()), + icon: const Icon(Icons.remove_circle_outline) + ) + ], + ), + )).toList(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () => setState(() => upstreamControllers.add(TextEditingController())), + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.addItem) + ), + ], + ), + SectionLabel(label: AppLocalizations.of(context)!.dnsMode), + CustomRadioListTile( + groupValue: upstreamMode, + value: "load_balancing", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.loadBalancing, + subtitle: AppLocalizations.of(context)!.loadBalancingDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + CustomRadioListTile( + groupValue: upstreamMode, + value: "parallel_requests", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.parallelRequests, + subtitle: AppLocalizations.of(context)!.parallelRequestsDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + CustomRadioListTile( + groupValue: upstreamMode, + value: "fastest_ip_address", + radioBackgroundColor: Theme.of(context).dialogBackgroundColor, + title: AppLocalizations.of(context)!.fastestIpAddress, + subtitle: AppLocalizations.of(context)!.fastestIpAddressDescription, + onChanged: (value) => setState(() => upstreamMode = value), + ), + ], + ), + ); + } +} \ No newline at end of file