From e284e95c23deb51b75e6e775e0c45886222a1b1a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 19 Oct 2022 14:12:53 +0200 Subject: [PATCH] Added dns settings screen --- lib/l10n/app_en.arb | 15 +- lib/l10n/app_es.arb | 15 +- lib/screens/settings/dns/dns.dart | 149 +++++++++++++++++++ lib/screens/settings/dns/dns_mode_modal.dart | 110 ++++++++++++++ lib/screens/settings/settings.dart | 13 ++ lib/widgets/custom_radio.dart | 22 +-- lib/widgets/custom_radio_list_tile.dart | 74 +++++++++ 7 files changed, 386 insertions(+), 12 deletions(-) create mode 100644 lib/screens/settings/dns/dns.dart create mode 100644 lib/screens/settings/dns/dns_mode_modal.dart create mode 100644 lib/widgets/custom_radio_list_tile.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4c2e19a..f92ce52 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -411,5 +411,18 @@ "logsNotCleared": "Logs could not be cleared", "runningHomeAssistant": "Running on Home Assistant", "serverError": "Server error", - "noItems": "No items to show here" + "noItems": "No items to show here", + "dnsSettings": "DNS settings", + "dnsSettingsDescription": "Configure connection with DNS servers", + "upstreamDns": "Upstream DNS servers", + "bootstrapDns": "Bootstrap DNS servers", + "noUpstreamDns": "No upstream DNS servers added.", + "dnsMode": "DNS mode", + "noDnsMode": "No DNS mode selected", + "loadBalancing": "Load balancing", + "parallelRequests": "Parallel requests", + "fastestIpAddress": "Fastest IP address", + "loadBalancingDescription": "Query one upstream server at a time. AdGuard Home uses its weighted random algorithm to pick the server so that the fastest server is used more often.", + "parallelRequestsDescription": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.", + "fastestIpAddressDescription": "Query all DNS servers and return the fastest IP address among all responses. This slows down DNS queries as AdGuard Home has to wait for responses from all DNS servers, but improves the overall connectivity." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index bb6a886..c3097d2 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -411,5 +411,18 @@ "logsNotCleared": "No se han podido borrar los registros", "runningHomeAssistant": "Ejecutando en Home Assistant", "serverError": "Error del servidor", - "noItems": "No hay elementos para mostrar aquí" + "noItems": "No hay elementos para mostrar aquí", + "dnsSettings": "Configuración de DNS", + "dnsSettingsDescription": "Configurar conexión con servidores DNS", + "upstreamDns": "Servidores DNS de subida", + "bootstrapDns": "Servidores DNS de arranque", + "noUpstreamDns": "No hay servidores DNS de subida añadidos.", + "dnsMode": "Modo DNS", + "noDnsMode": "No se ha seleccionado modo DNS", + "loadBalancing": "Balanceo de carga", + "parallelRequests": "Peticiones paralelas", + "fastestIpAddress": "Dirección IP más rápida", + "loadBalancingDescription": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.", + "parallelRequestsDescription": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.", + "fastestIpAddressDescription": "Consulta todos los servidores DNS y devuelve la dirección IP más rápida de todas las respuestas. Esto ralentiza las consultas DNS ya que AdGuard Home tiene que esperar las respuestas de todos los servidores DNS, pero mejora la conectividad general." } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart new file mode 100644 index 0000000..897aa94 --- /dev/null +++ b/lib/screens/settings/dns/dns.dart @@ -0,0 +1,149 @@ +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/screens/settings/dns/dns_mode_modal.dart'; + +class DnsSettings extends StatefulWidget { + const DnsSettings({Key? key}) : super(key: key); + + @override + State createState() => _DnsSettingsState(); +} + +class _DnsSettingsState extends State { + List upstreamControllers = [ + TextEditingController() + ]; + + String upstreamMode = "load_balancing"; + + @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), + ), + 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 + ), + ), + ], + ), + ) + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/dns_mode_modal.dart b/lib/screens/settings/dns/dns_mode_modal.dart new file mode 100644 index 0000000..f42a74d --- /dev/null +++ b/lib/screens/settings/dns/dns_mode_modal.dart @@ -0,0 +1,110 @@ +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/settings.dart b/lib/screens/settings/settings.dart index e1ce544..7f9126a 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -10,6 +10,7 @@ import 'package:adguard_home_manager/screens/settings/server_info/server_info.da import 'package:adguard_home_manager/screens/settings/access_settings/access_settings.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart'; import 'package:adguard_home_manager/screens/settings/section_label.dart'; +import 'package:adguard_home_manager/screens/settings/dns/dns.dart'; import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrites.dart'; import 'package:adguard_home_manager/screens/settings/appbar.dart'; import 'package:adguard_home_manager/screens/servers/servers.dart'; @@ -113,6 +114,18 @@ class Settings extends StatelessWidget { ) }, ), + CustomListTile( + leadingIcon: Icons.dns_rounded, + label: AppLocalizations.of(context)!.dnsSettings, + description: AppLocalizations.of(context)!.dnsSettingsDescription, + onTap: () => { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const DnsSettings() + ) + ) + }, + ), CustomListTile( leadingIcon: Icons.route_rounded, label: AppLocalizations.of(context)!.dnsRewrites, diff --git a/lib/widgets/custom_radio.dart b/lib/widgets/custom_radio.dart index 868f407..879f476 100644 --- a/lib/widgets/custom_radio.dart +++ b/lib/widgets/custom_radio.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; class CustomRadio extends StatelessWidget { - final int value; - final int groupValue; - final Function(int)? onChange; + final dynamic value; + final dynamic groupValue; + final Function(dynamic)? onChange; final Color backgroundColor; const CustomRadio({ @@ -20,8 +20,8 @@ class CustomRadio extends StatelessWidget { alignment: Alignment.center, children: [ Container( - width: 20, - height: 20, + width: 18, + height: 18, decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), color: value == groupValue @@ -32,16 +32,18 @@ class CustomRadio extends StatelessWidget { ), ), Container( - width: 16, - height: 16, + width: 14, + height: 14, decoration: BoxDecoration( borderRadius: BorderRadius.circular(35), color: backgroundColor ), ), - Container( - width: 12, - height: 12, + AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + width: 9.5, + height: 9.5, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: value == groupValue diff --git a/lib/widgets/custom_radio_list_tile.dart b/lib/widgets/custom_radio_list_tile.dart new file mode 100644 index 0000000..bccd20a --- /dev/null +++ b/lib/widgets/custom_radio_list_tile.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/widgets/custom_radio.dart'; + +class CustomRadioListTile extends StatelessWidget { + final String groupValue; + final String value; + final Color radioBackgroundColor; + final String title; + final String? subtitle; + final void Function(String) onChanged; + + const CustomRadioListTile({ + Key? key, + required this.groupValue, + required this.value, + required this.radioBackgroundColor, + required this.title, + this.subtitle, + required this.onChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onChanged(value), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + const SizedBox(width: 10), + CustomRadio( + value: value, + groupValue: groupValue, + backgroundColor: radioBackgroundColor, + ), + const SizedBox(width: 30), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width-110, + child: Text( + title, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16 + ), + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width-110, + child: Text( + subtitle!, + style: const TextStyle( + color: Colors.grey, + fontSize: 14 + ), + ), + ), + ] + ], + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file