diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2799597..0ad60e7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -487,5 +487,8 @@ "bootstrapDnsDescription": "Configure the bootstrap DNS servers", "privateReverseDnsDescription": "Configure custom DNS resolvers and enable private reverse DNS resolving", "dnsServerSettingsDescription": "Configure a rate limit, the blocking mode and more", - "dnsCacheConfigDescription": "Configure how the server should manage the DNS cache" + "dnsCacheConfigDescription": "Configure how the server should manage the DNS cache", + "comment": "Comment", + "address": "Address", + "commentsDescription": "Comments are always preceded by #. You don't have to add it, it will be added automatically." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index bf8a893..2b1b55d 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -487,5 +487,8 @@ "bootstrapDnsDescription": "Configura los servidores DNS de arranque", "privateReverseDnsDescription": "Configura DNS resolutores personalizados y habilita resolutores internos y privados", "dnsServerSettingsDescription": "Configura el límite de peticiones, el modo de bloqueo y más", - "dnsCacheConfigDescription": "Configura cómo el servidor debe manejar la caché del DNS" + "dnsCacheConfigDescription": "Configura cómo el servidor debe manejar la caché del DNS", + "comment": "Comentario", + "address": "Dirección", + "commentsDescription": "Los comentarios siempre van precedidos por #. No necesitas añadirlo. Se añadirá automáticamente." } \ No newline at end of file diff --git a/lib/screens/settings/dns/comment_modal.dart b/lib/screens/settings/dns/comment_modal.dart new file mode 100644 index 0000000..0eb2694 --- /dev/null +++ b/lib/screens/settings/dns/comment_modal.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class CommentModal extends StatefulWidget { + final String? comment; + final void Function(String) onConfirm; + + const CommentModal({ + Key? key, + this.comment, + required this.onConfirm + }) : super(key: key); + + @override + State createState() => _CommentModalState(); +} + +class _CommentModalState extends State { + final TextEditingController commentController = TextEditingController(); + + bool validData = false; + + @override + void initState() { + if (widget.comment != null) { + commentController.text = widget.comment!.replaceFirst(RegExp(r'#(\s)?'), ""); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + height: 330, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ), + color: Theme.of(context).dialogBackgroundColor + ), + child: Column( + children: [ + Expanded( + child: ListView( + physics: MediaQuery.of(context).size.height >= 330 == true + ? const NeverScrollableScrollPhysics() + : null, + children: [ + const Padding( + padding: EdgeInsets.only(top: 28), + child: Icon( + Icons.comment_rounded, + size: 26, + ), + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.comment, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 24 + ), + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: TextFormField( + controller: commentController, + onChanged: (value) { + if (value != '') { + setState(() => validData = true); + } + else { + setState(() => validData = false); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.comment_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.comment, + helperText: AppLocalizations.of(context)!.commentsDescription, + helperMaxLines: 3 + ) + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm("# ${commentController.text}"); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).primaryColor + : Colors.grey + ), + ) + ), + ], + ), + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart index 1f3b2eb..da05009 100644 --- a/lib/screens/settings/dns/upstream_dns.dart +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.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/comment_modal.dart'; import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; import 'package:adguard_home_manager/models/dns_info.dart'; @@ -27,7 +28,7 @@ class UpstreamDnsScreen extends StatefulWidget { } class _UpstreamDnsScreenState extends State { - List upstreamControllers = []; + List> dnsServers = []; String upstreamMode = ""; @@ -35,8 +36,8 @@ class _UpstreamDnsScreenState extends State { checkValidValues() { if ( - upstreamControllers.isNotEmpty && - upstreamControllers.every((element) => element.text != '') + dnsServers.isNotEmpty && + dnsServers.every((element) => element['controller'] != null ? element['controller.text'] != '' : true) ) { setState(() => validValues = true); } @@ -48,9 +49,18 @@ class _UpstreamDnsScreenState extends State { @override void initState() { for (var item in widget.serversProvider.dnsInfo.data!.upstreamDns) { - final controller = TextEditingController(); - controller.text = item; - upstreamControllers.add(controller); + if (item[0] == '#') { + dnsServers.add({ + 'comment': item + }); + } + else { + final controller = TextEditingController(); + controller.text = item; + dnsServers.add({ + 'controller': controller + }); + } } upstreamMode = widget.serversProvider.dnsInfo.data!.upstreamMode; validValues = true; @@ -62,12 +72,43 @@ class _UpstreamDnsScreenState extends State { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + void openAddCommentModal() { + showModalBottomSheet( + context: context, + builder: (context) => CommentModal( + onConfirm: (value) { + dnsServers.add({ + 'comment': value + }); + }, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true, + isDismissible: true + ); + } + + void openEditCommentModal(Map item, int position) { + showModalBottomSheet( + context: context, + builder: (context) => CommentModal( + comment: item['comment'], + onConfirm: (value) { + setState(() => dnsServers[position] = { 'comment': value }); + }, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true, + isDismissible: true + ); + } + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await setDnsConfig(server: serversProvider.selectedServer!, data: { - "upstream_dns": upstreamControllers.map((e) => e.text).toList(), + "upstream_dns": dnsServers.map((e) => e['controller'] != null ? e['controller'].text : e['comment']).toList(), "upstream_mode": upstreamMode }); @@ -75,7 +116,7 @@ class _UpstreamDnsScreenState extends State { if (result['result'] == 'success') { DnsInfoData data = serversProvider.dnsInfo.data!; - data.upstreamDns = upstreamControllers.map((e) => e.text).toList(); + data.upstreamDns = List.from(dnsServers.map((e) => e['controller'] != null ? e['controller'].text : e['comment'])); data.upstreamMode = upstreamMode; serversProvider.setDnsInfoData(data); @@ -125,7 +166,7 @@ class _UpstreamDnsScreenState extends State { body: ListView( padding: const EdgeInsets.only(top: 10), children: [ - if (upstreamControllers.isEmpty) Column( + if (dnsServers.isEmpty) Column( children: [ Padding( padding: const EdgeInsets.all(10), @@ -142,17 +183,17 @@ class _UpstreamDnsScreenState extends State { const SizedBox(height: 20), ], ), - ...upstreamControllers.map((c) => Padding( + ...dnsServers.map((item) => Padding( padding: const EdgeInsets.only( left: 24, right: 10, bottom: 20 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( + if (item['controller'] != null) SizedBox( width: MediaQuery.of(context).size.width-90, child: TextFormField( - controller: c, + controller: item['controller'], onChanged: (_) => checkValidValues(), decoration: InputDecoration( prefixIcon: const Icon(Icons.dns_rounded), @@ -165,27 +206,54 @@ class _UpstreamDnsScreenState extends State { ) ), ), + if (item['comment'] != null) Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + item['comment'], + style: TextStyle( + fontSize: 16, + color: Theme.of(context).listTileTheme.iconColor + ), + ), + IconButton( + onPressed: () => openEditCommentModal(item, dnsServers.indexOf(item)), + icon: const Icon(Icons.edit), + tooltip: AppLocalizations.of(context)!.edit, + ) + ], + ), + ), IconButton( onPressed: () { - setState(() => upstreamControllers = upstreamControllers.where((con) => con != c).toList()); + setState(() => dnsServers = dnsServers.where((i) => i != item).toList()); checkValidValues(); }, - icon: const Icon(Icons.remove_circle_outline) + icon: const Icon(Icons.remove_circle_outline), + tooltip: AppLocalizations.of(context)!.remove, ) ], ), )).toList(), Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.min, children: [ + ElevatedButton.icon( + onPressed: openAddCommentModal, + icon: const Icon(Icons.add), + label: Text(AppLocalizations.of(context)!.comment) + ), ElevatedButton.icon( onPressed: () { - setState(() => upstreamControllers.add(TextEditingController())); + setState(() => dnsServers.add({ + 'controller': TextEditingController() + })); checkValidValues(); }, icon: const Icon(Icons.add), - label: Text(AppLocalizations.of(context)!.addItem) + label: Text(AppLocalizations.of(context)!.address) ), ], ),