Improved add custom rule modal

This commit is contained in:
Juan Gilsanz Polo 2022-10-10 01:05:15 +02:00
parent 1035257361
commit b42218fac1
5 changed files with 363 additions and 114 deletions

View file

@ -291,5 +291,16 @@
"domainNotValid": "Domain not valid",
"check": "Check",
"checkingHost": "Checking host...",
"errorCheckingHost": "Host couldn't be checked"
"errorCheckingHost": "Host couldn't be checked",
"block": "Block",
"unblock": "Unblock",
"custom": "Custom",
"addImportant": "Add $important",
"howCreateRules": "How to create custom rules",
"examples": "Examples",
"example1": "Block access to example.org and all its subdomains.",
"example2": "Unblocks access to example.org and all its subdomains.",
"example3": "Adds a comment.",
"example4": "Block access to domains matching the specified regular expression.",
"moreInformation": "More information"
}

View file

@ -291,5 +291,16 @@
"domainNotValid": "Dominio no válido",
"check": "Comprobar",
"checkingHost": "Comprobando host...",
"errorCheckingHost": "No se pudo comprobar el host"
"errorCheckingHost": "No se pudo comprobar el host",
"block": "Bloquear",
"unblock": "Desbloquear",
"custom": "Personalizado",
"addImportant": "Añadir $important",
"howCreateRules": "Cómo crear reglas personalizadas",
"examples": "Ejemplos",
"example1": "Bloquea el acceso al dominio ejemplo.org y a todos sus subdominios.",
"example2": "Desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios.",
"example3": "Añade un comentario.",
"example4": "Bloquea el acceso a los dominios que coincidan con la expresión regular especificada.",
"moreInformation": "Más información"
}

View file

@ -28,59 +28,6 @@ class ClientsFab extends StatelessWidget {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
void confirmRemoveDomain(String ip) async {
Map<String, List<String>> body = {};
final List<String> clients = [...serversProvider.clients.data!.clientsAllowedBlocked?.disallowedClients ?? [], ip];
body = {
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
"disallowed_clients": clients,
"blocked_hosts": serversProvider.clients.data!.clientsAllowedBlocked?.blockedHosts ?? [],
};
ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.addingClient);
final result = await requestAllowedBlockedClientsHosts(serversProvider.selectedServer!, body);
processModal.close();
if (result['result'] == 'success') {
serversProvider.setAllowedDisallowedClientsBlockedDomains(
ClientsAllowedBlocked(
allowedClients: body['allowed_clients'] ?? [],
disallowedClients: body['disallowed_clients'] ?? [],
blockedHosts: body['blocked_hosts'] ?? [],
)
);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientAddedSuccessfully,
color: Colors.green
);
}
else if (result['result'] == 'error' && result['message'] == 'client_another_list') {
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientAnotherList,
color: Colors.red
);
}
else {
appConfigProvider.addLog(result['log']);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotAdded,
color: Colors.red
);
}
}
void confirmAddClient(Client client) async {
ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.addingClient);

View file

@ -1,11 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/custom_radio_toggle.dart';
class AddCustomRule extends StatefulWidget {
final ScrollController scrollController;
final void Function(String) onConfirm;
const AddCustomRule({
Key? key,
required this.scrollController,
required this.onConfirm
}) : super(key: key);
@ -14,26 +18,59 @@ class AddCustomRule extends StatefulWidget {
}
class _AddCustomRuleState extends State<AddCustomRule> {
TextEditingController ruleController = TextEditingController();
final TextEditingController domainController = TextEditingController();
String? domainError;
bool validValues = false;
String preset = "block";
bool addImportant = false;
void checkValidValues() {
if (ruleController.text != '') {
if (domainController.text != '') {
setState(() => validValues = true);
}
else {
setState(() => validValues = false);
}
}
void validateDomain(String value) {
final domainRegex = RegExp(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$');
if (domainRegex.hasMatch(value)) {
setState(() => domainError = null);
}
else {
setState(() => domainError = AppLocalizations.of(context)!.domainNotValid);
}
}
String buildRule() {
String rule = "";
if (preset == 'block') {
rule = "||${domainController.text.trim()}^";
}
else if (preset == 'unblock') {
rule = "@@||${domainController.text.trim()}^";
}
else {
rule = domainController.text.trim();
}
if (addImportant == true) {
rule = "$rule\$important";
}
return rule;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
height: 300,
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
color: Theme.of(context).dialogBackgroundColor,
borderRadius: const BorderRadius.only(
@ -43,64 +80,300 @@ class _AddCustomRuleState extends State<AddCustomRule> {
),
child: Column(
children: [
const Icon(
Icons.shield_rounded,
size: 26,
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.addCustomRule,
style: const TextStyle(
fontSize: 24
),
),
const SizedBox(height: 30),
TextFormField(
controller: ruleController,
onChanged: (_) => checkValidValues(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.rule),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.rule,
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
child: ListView(
controller: widget.scrollController,
children: [
Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
const Padding(
padding: EdgeInsets.only(top: 28),
child: Icon(
Icons.shield_rounded,
size: 26,
),
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.addCustomRule,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24
),
),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5
),
const SizedBox(width: 20),
TextButton(
onPressed: validValues == true
? () {
Navigator.pop(context);
widget.onConfirm(ruleController.text);
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: validValues == true
? Theme.of(context).primaryColor
: Colors.grey
),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Theme.of(context).primaryColor
)
),
],
child: Text(
buildRule(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500
),
)
),
],
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: TextFormField(
controller: domainController,
onChanged: validateDomain,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: domainError,
labelText: AppLocalizations.of(context)!.domain,
),
),
),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CustomRadioToggle(
groupSelected: preset,
value: 'block',
label: AppLocalizations.of(context)!.block,
onTap: (value) => setState(() => preset = value)
),
CustomRadioToggle(
groupSelected: preset,
value: 'unblock',
label: AppLocalizations.of(context)!.unblock,
onTap: (value) => setState(() => preset = value)
),
CustomRadioToggle(
groupSelected: preset,
value: 'custom',
label: AppLocalizations.of(context)!.custom,
onTap: (value) => setState(() => preset = value)
),
],
),
const SizedBox(height: 20),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => setState(() => addImportant = !addImportant),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
AppLocalizations.of(context)!.addImportant,
style: const TextStyle(
fontSize: 16
),
),
),
Switch(
value: addImportant,
onChanged: (value) => setState(() => addImportant = value),
activeColor: Theme.of(context).primaryColor,
)
],
),
),
),
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
const Icon(Icons.info),
const SizedBox(width: 20),
Text(
AppLocalizations.of(context)!.examples,
style: const TextStyle(
fontSize: 18
),
)
],
),
const SizedBox(height: 20),
SizedBox(
width: double.maxFinite,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"||example.org^",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 5),
Text(
AppLocalizations.of(context)!.example1,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 20),
Text(
"@@||example.org^",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 5),
Text(
AppLocalizations.of(context)!.example2,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 20),
Text(
"! Here goes a comment",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryColor
),
),
Text(
"# Also a comment",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 5),
Text(
AppLocalizations.of(context)!.example3,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 20),
Text(
"/REGEX/",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryColor
),
),
const SizedBox(height: 5),
Text(
AppLocalizations.of(context)!.example4,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor
),
),
],
),
)
],
),
),
),
),
const SizedBox(height: 20),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => {},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
AppLocalizations.of(context)!.moreInformation,
style: const TextStyle(
fontSize: 16
),
),
),
const Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(Icons.open_in_new),
)
],
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 28,
right: 28,
top: 20,
bottom: 28
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
),
const SizedBox(width: 20),
TextButton(
onPressed: validValues == true
? () {
Navigator.pop(context);
widget.onConfirm(domainController.text);
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: validValues == true
? Theme.of(context).primaryColor
: Colors.grey
),
)
),
],
),
)

View file

@ -2,6 +2,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:bottom_sheet/bottom_sheet.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/filters/add_custom_rule.dart';
@ -63,13 +64,19 @@ class FiltersFab extends StatelessWidget {
}
void openAddCustomRule() {
showModalBottomSheet(
showFlexibleBottomSheet(
minHeight: 0.7,
initHeight: 0.7,
maxHeight: 0.95,
isCollapsible: true,
duration: const Duration(milliseconds: 250),
anchors: [0.7, 0.95],
context: context,
builder: (ctx) => AddCustomRule(
builder: (ctx, controller, offset) => AddCustomRule(
scrollController: controller,
onConfirm: confirmAddRule
),
isScrollControlled: true,
backgroundColor: Colors.transparent
bottomSheetColor: Colors.transparent
);
}