Added validation

This commit is contained in:
Juan Gilsanz Polo 2022-10-19 21:57:48 +02:00
parent ea1cb6165c
commit bfe2572e04
7 changed files with 257 additions and 22 deletions

View file

@ -468,5 +468,6 @@
"blockingIpv4": "Blocking IPv4",
"blockingIpv4Description": "IP address to be returned for a blocked A request",
"blockingIpv6": "Blocking IPv6",
"blockingIpv6Description": "IP address to be returned for a blocked AAAA request"
"blockingIpv6Description": "IP address to be returned for a blocked AAAA request",
"invalidIp": "Invalid IP address"
}

View file

@ -468,5 +468,6 @@
"blockingIpv4": "Bloqueo de IPv4",
"blockingIpv4Description": "Dirección IP devolverá una petición A bloqueada",
"blockingIpv6": "Bloqueo de IPv6",
"blockingIpv6Description": "Dirección IP devolverá una petición AAAA bloqueada"
"blockingIpv6Description": "Dirección IP devolverá una petición AAAA bloqueada",
"invalidIp": "Dirección IP no válida"
}

View file

@ -16,15 +16,45 @@ class BootstrapDnsScreen extends StatefulWidget {
}
class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
List<TextEditingController> bootstrapControllers = [];
List<Map<String, dynamic>> bootstrapControllers = [];
bool validValues = false;
void validateIp(Map<String, dynamic> field, String value) {
RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)');
if (ipAddress.hasMatch(value) == true) {
setState(() => field['error'] = null);
}
else {
setState(() => field['error'] = AppLocalizations.of(context)!.invalidIp);
}
checkValidValues();
}
void checkValidValues() {
if (
bootstrapControllers.isNotEmpty &&
bootstrapControllers.every((element) => element['controller'].text != '') &&
bootstrapControllers.every((element) => element['error'] == null)
) {
setState(() => validValues = true);
}
else {
setState(() => validValues = false);
}
}
@override
void initState() {
for (var item in widget.serversProvider.dnsInfo.data!.bootstrapDns) {
final controller = TextEditingController();
controller.text = item;
bootstrapControllers.add(controller);
bootstrapControllers.add({
'controller': controller,
'error': null
});
}
validValues = true;
super.initState();
}
@ -33,6 +63,16 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.bootstrapDns),
actions: [
IconButton(
onPressed: validValues == true
? () => {}
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: ListView(
padding: const EdgeInsets.only(top: 10),
@ -83,8 +123,8 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
SizedBox(
width: MediaQuery.of(context).size.width-90,
child: TextFormField(
controller: c,
// onChanged: (_) => checkValidValues(),
controller: c['controller'],
onChanged: (value) => validateIp(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
@ -92,12 +132,16 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
IconButton(
onPressed: () => setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList()),
onPressed: () {
setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
@ -108,7 +152,13 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () => setState(() => bootstrapControllers.add(TextEditingController())),
onPressed: () {
setState(() => bootstrapControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkValidValues();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),

View file

@ -29,12 +29,31 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
bool optimisticCache = false;
bool validData = false;
void checkValidData() {
if (
cacheSizeController.text != '' &&
cacheSizeError == null &&
overrideMinTtlController.text != '' &&
overrideMinTtlError == null &&
overrideMaxTtlController.text != '' &&
overrideMaxTtlError == null
) {
setState(() => validData = true);
}
else {
setState(() => validData = false);
}
}
@override
void initState() {
cacheSizeController.text = widget.serversProvider.dnsInfo.data!.cacheSize.toString();
overrideMinTtlController.text = widget.serversProvider.dnsInfo.data!.cacheTtlMin.toString();
overrideMaxTtlController.text = widget.serversProvider.dnsInfo.data!.cacheTtlMax.toString();
optimisticCache = widget.serversProvider.dnsInfo.data!.cacheOptimistic;
validData = true;
super.initState();
}
@ -73,6 +92,16 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.dnsCacheConfig),
actions: [
IconButton(
onPressed: validData == true
? () => {}
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: ListView(
padding: const EdgeInsets.only(top: 10),
@ -89,6 +118,7 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
else {
setState(() => cacheSizeError = AppLocalizations.of(context)!.valueNotNumber);
}
checkValidData();
}
),
const SizedBox(height: 30),
@ -104,6 +134,7 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
else {
setState(() => overrideMinTtlError = AppLocalizations.of(context)!.valueNotNumber);
}
checkValidData();
}
),
const SizedBox(height: 30),
@ -119,6 +150,7 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
else {
setState(() => overrideMaxTtlError = AppLocalizations.of(context)!.valueNotNumber);
}
checkValidData();
}
),
const SizedBox(height: 10),

View file

@ -33,6 +33,52 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
final TextEditingController ipv6controller = TextEditingController();
String? ipv6error;
bool isDataValid = false;
void validateIpv4(String value) {
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$');
if (ipAddress.hasMatch(value) == true) {
setState(() => ipv4error = null);
}
else {
setState(() => ipv4error = AppLocalizations.of(context)!.invalidIp);
}
validateData();
}
void validateIpv6(String value) {
RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)');
if (ipAddress.hasMatch(value) == true) {
setState(() => ipv6error = null);
}
else {
setState(() => ipv6error = AppLocalizations.of(context)!.invalidIp);
}
validateData();
}
void validateData() {
if (
limitRequestsController.text != '' &&
limitRequestsError == null &&
(
blockingMode != 'custom_ip' ||
(
blockingMode == 'custom_ip' &&
ipv4controller.text != '' &&
ipv4error == null &&
ipv6controller.text != '' &&
ipv6error == null
)
) == true
) {
setState(() => isDataValid = true);
}
else {
setState(() => isDataValid = false);
}
}
@override
void initState() {
limitRequestsController.text = widget.serversProvider.dnsInfo.data!.ratelimit.toString();
@ -42,6 +88,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
blockingMode = widget.serversProvider.dnsInfo.data!.blockingMode;
ipv4controller.text = widget.serversProvider.dnsInfo.data!.blockingIpv4;
ipv6controller.text = widget.serversProvider.dnsInfo.data!.blockingIpv6;
isDataValid = true;
super.initState();
}
@ -56,11 +103,22 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
ipv6error = null;
}
setState(() => blockingMode = mode);
validateData();
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.dnsServerSettings),
actions: [
IconButton(
onPressed: isDataValid == true
? () => {}
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: ListView(
padding: const EdgeInsets.only(top: 10),
@ -76,6 +134,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
else {
setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber);
}
validateData();
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.looks_one_rounded),
@ -156,7 +215,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: ipv4controller,
// onChanged: onChanged,
onChanged: validateIpv4,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
@ -177,7 +236,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: ipv6controller,
// onChanged: onChanged,
onChanged: validateIpv6,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(

View file

@ -20,12 +20,46 @@ class PrivateReverseDnsServersScreen extends StatefulWidget {
class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServersScreen> {
List<String> defaultReverseResolvers = [];
bool editReverseResolvers = false;
List<TextEditingController> reverseResolversControllers = [
TextEditingController()
List<Map<String, dynamic>> reverseResolversControllers = [
{
'controller': TextEditingController(),
'error': null
}
];
bool usePrivateReverseDnsResolvers = false;
bool enableReverseResolve = false;
bool validValues = false;
void validateAddress(Map<String, dynamic> item ,String value) {
RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)');
RegExp domain = RegExp(r'^((http|https|tls|udp|tcp|quic|sdns):\/\/)?([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) {
setState(() => item['error'] = null);
}
else {
setState(() => item['error'] = AppLocalizations.of(context)!.invalidIpDomain);
}
checkDataValid();
}
void checkDataValid() {
if (
(
editReverseResolvers == true &&
reverseResolversControllers.isNotEmpty &&
reverseResolversControllers.every((element) => element['controller'].text != '') &&
reverseResolversControllers.every((element) => element['error'] == null)
) == true
) {
setState(() => validValues = true);
}
else {
setState(() => validValues = false);
}
}
@override
void initState() {
for (var item in widget.serversProvider.dnsInfo.data!.defaultLocalPtrUpstreams) {
@ -34,13 +68,17 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
for (var item in widget.serversProvider.dnsInfo.data!.localPtrUpstreams) {
final controller = TextEditingController();
controller.text = item;
reverseResolversControllers.add(controller);
reverseResolversControllers.add({
'controller': controller,
'error': null
});
}
if (widget.serversProvider.dnsInfo.data!.localPtrUpstreams.isNotEmpty) {
editReverseResolvers = true;
}
usePrivateReverseDnsResolvers = widget.serversProvider.dnsInfo.data!.usePrivatePtrResolvers;
enableReverseResolve = widget.serversProvider.dnsInfo.data!.resolveClients;
validValues = true;
super.initState();
}
@ -49,6 +87,16 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.privateReverseDnsServers),
actions: [
IconButton(
onPressed: validValues == true
? () => {}
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: ListView(
padding: const EdgeInsets.only(top: 10),
@ -90,7 +138,10 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () => setState(() => editReverseResolvers = true),
onPressed: () {
setState(() => editReverseResolvers = true);
checkDataValid();
},
icon: const Icon(Icons.edit),
label: Text(AppLocalizations.of(context)!.edit)
),
@ -110,8 +161,8 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
SizedBox(
width: MediaQuery.of(context).size.width-90,
child: TextFormField(
controller: c,
// onChanged: (_) => checkValidValues(),
controller: c['controller'],
onChanged: (value) => validateAddress(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
@ -119,12 +170,16 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.serverAddress,
)
),
),
IconButton(
onPressed: () => setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList()),
onPressed: () {
setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList());
checkDataValid();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
@ -152,7 +207,13 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () => setState(() => reverseResolversControllers.add(TextEditingController())),
onPressed: () {
setState(() => reverseResolversControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkDataValid();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),

View file

@ -21,7 +21,21 @@ class UpstreamDnsScreen extends StatefulWidget {
class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
List<TextEditingController> upstreamControllers = [];
String upstreamMode = "load_balancing";
String upstreamMode = "";
bool validValues = false;
checkValidValues() {
if (
upstreamControllers.isNotEmpty &&
upstreamControllers.every((element) => element.text != '')
) {
setState(() => validValues = true);
}
else {
setState(() => validValues = false);
}
}
@override
void initState() {
@ -31,6 +45,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
upstreamControllers.add(controller);
}
upstreamMode = widget.serversProvider.dnsInfo.data!.upstreamMode;
validValues = true;
super.initState();
}
@ -39,6 +54,16 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.upstreamDns),
actions: [
IconButton(
onPressed: validValues == true
? () => {}
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: ListView(
padding: const EdgeInsets.only(top: 10),
@ -71,7 +96,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
width: MediaQuery.of(context).size.width-90,
child: TextFormField(
controller: c,
// onChanged: (_) => checkValidValues(),
onChanged: (_) => checkValidValues(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
@ -84,7 +109,10 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
),
),
IconButton(
onPressed: () => setState(() => upstreamControllers = upstreamControllers.where((con) => con != c).toList()),
onPressed: () {
setState(() => upstreamControllers = upstreamControllers.where((con) => con != c).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
@ -95,7 +123,10 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () => setState(() => upstreamControllers.add(TextEditingController())),
onPressed: () {
setState(() => upstreamControllers.add(TextEditingController()));
checkValidValues();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),