diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0c01bdc..9ea60af 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -170,5 +170,20 @@ "webAdminPanel": "Web admin. panel", "visitGooglePlay": "Visit Google Play page", "gitHub": "App code available on GitHub", - "blockClient": "Block client" + "blockClient": "Block client", + "selectTags": "Select tags", + "noTagsSelected": "No tags selected", + "tags": "Tags", + "identifiers": "Identifiers", + "identifier": "Identifier", + "identifierHelper": "IP address, CIDR, MAC address, or ClientID", + "noIdentifiers": "No identifiers added", + "useGlobalSettings": "Use global settings", + "enableFiltering": "Enable filtering", + "enableSafeBrowsing": "Enable safe browsing", + "enableParentalControl": "Enable parental control", + "enableSafeSearch": "Enable safe search", + "blockedServices": "Blocked services", + "selectBlockedServices": "Select services to block", + "noBlockedServicesSelected": "No blocked services" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index e4b1c53..d725a1e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -170,5 +170,20 @@ "webAdminPanel": "Panel de admin. web", "visitGooglePlay": "Visita la página de Google Play", "gitHub": "Código de la app disponible en GitHub", - "blockClient": "Bloquear cliente" + "blockClient": "Bloquear cliente", + "selectTags": "Seleccionar etiquetas", + "noTagsSelected": "No hay etiquetas seleccionadas", + "tags": "Etiquetas", + "identifiers": "Identificadores", + "identifier": "Identificador", + "identifierHelper": "Dirección IP, CIDR, dirección MAC, o ClientID", + "noIdentifiers": "No hay identificadores añadidos", + "useGlobalSettings": "Usar configuración global", + "enableFiltering": "Activar filtrado", + "enableSafeBrowsing": "Activar navegación segura", + "enableParentalControl": "Activar control parental", + "enableSafeSearch": "Activar búsqueda segura", + "blockedServices": "Servicios bloqueados", + "selectBlockedServices": "Seleccionar servicios para bloquear", + "noBlockedServicesSelected": "No hay servicios bloqueados" } \ No newline at end of file diff --git a/lib/models/add_client.dart b/lib/models/add_client.dart new file mode 100644 index 0000000..8633993 --- /dev/null +++ b/lib/models/add_client.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +AddClient addClientFromJson(String str) => AddClient.fromJson(json.decode(str)); + +String addClientToJson(AddClient data) => json.encode(data.toJson()); + +class AddClient { + AddClient({ + required this.name, + required this.ids, + required this.useGlobalSettings, + required this.filteringEnabled, + required this.parentalEnabled, + required this.safebrowsingEnabled, + required this.safesearchEnabled, + required this.useGlobalBlockedServices, + required this.blockedServices, + required this.upstreams, + required this.tags, + }); + + final String name; + final List ids; + final bool useGlobalSettings; + final bool filteringEnabled; + final bool parentalEnabled; + final bool safebrowsingEnabled; + final bool safesearchEnabled; + final bool useGlobalBlockedServices; + final List blockedServices; + final List upstreams; + final List tags; + + factory AddClient.fromJson(Map json) => AddClient( + name: json["name"], + ids: List.from(json["ids"].map((x) => x)), + useGlobalSettings: json["use_global_settings"], + filteringEnabled: json["filtering_enabled"], + parentalEnabled: json["parental_enabled"], + safebrowsingEnabled: json["safebrowsing_enabled"], + safesearchEnabled: json["safesearch_enabled"], + useGlobalBlockedServices: json["use_global_blocked_services"], + blockedServices: List.from(json["blocked_services"].map((x) => x)), + upstreams: List.from(json["upstreams"].map((x) => x)), + tags: List.from(json["tags"].map((x) => x)), + ); + + Map toJson() => { + "name": name, + "ids": List.from(ids.map((x) => x)), + "use_global_settings": useGlobalSettings, + "filtering_enabled": filteringEnabled, + "parental_enabled": parentalEnabled, + "safebrowsing_enabled": safebrowsingEnabled, + "safesearch_enabled": safesearchEnabled, + "use_global_blocked_services": useGlobalBlockedServices, + "blocked_services": List.from(blockedServices.map((x) => x)), + "upstreams": List.from(upstreams.map((x) => x)), + "tags": List.from(tags.map((x) => x)), + }; +} diff --git a/lib/screens/clients/add_client_modal.dart b/lib/screens/clients/add_client_modal.dart new file mode 100644 index 0000000..bb6ba25 --- /dev/null +++ b/lib/screens/clients/add_client_modal.dart @@ -0,0 +1,464 @@ +import 'package:flutter/material.dart'; +import 'package:uuid/uuid.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/add_client.dart'; + +class AddClientModal extends StatefulWidget { + final void Function(AddClient) onConfirm; + + const AddClientModal({ + Key? key, + required this.onConfirm + }) : super(key: key); + + @override + State createState() => _AddClientModalState(); +} + +class _AddClientModalState extends State { + final Uuid uuid = const Uuid(); + + TextEditingController nameController = TextEditingController(); + + List> identifiersControllers = [ + { + 'id': 0, + 'controller': TextEditingController() + } + ]; + + bool useGlobalSettingsFiltering = true; + bool? enableFiltering; + bool? enableSafeBrowsing; + bool? enableParentalControl; + bool? enableSafeSearch; + + bool useGlobalSettingsServices = true; + + + bool checkValidValues() { + if ( + nameController.text != '' + ) { + return true; + } + else { + return false; + } + } + + @override + Widget build(BuildContext context) { + void createClient() { + + } + + Widget sectionLabel(String label) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 30, + horizontal: 20 + ), + child: Text( + label, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Theme.of(context).primaryColor + ), + ), + ); + } + + void enableDisableGlobalSettingsFiltering() { + if (useGlobalSettingsFiltering == true) { + setState(() { + useGlobalSettingsFiltering = false; + + enableFiltering = false; + enableSafeBrowsing = false; + enableParentalControl = false; + enableSafeSearch = false; + }); + } + else if (useGlobalSettingsFiltering == false) { + setState(() { + useGlobalSettingsFiltering = true; + + enableFiltering = null; + enableSafeBrowsing = null; + enableParentalControl = null; + enableSafeSearch = null; + }); + } + } + + Widget settignsTile({ + required String label, + required bool? value, + required void Function(bool) onChange + }) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: value != null ? () => onChange(!value) : null, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 15 + ), + ), + value != null + ? Switch( + value: value, + onChanged: onChange, + activeColor: Theme.of(context).primaryColor, + ) + : const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: Text( + "Global", + style: TextStyle( + color: Colors.grey + ), + ), + ) + ], + ), + ), + ), + ); + } + + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.6, + maxChildSize: 0.95, + builder: (context, scrollController) => Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) + ), + child: Column( + children: [ + Expanded( + child: ListView( + controller: scrollController, + children: [ + const SizedBox(height: 28), + const Icon( + Icons.add, + size: 26, + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.addClient, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 24 + ), + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: TextFormField( + controller: nameController, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.badge_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.name, + ), + ), + ), + sectionLabel(AppLocalizations.of(context)!.tags), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => {}, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20 + ), + child: Row( + children: [ + const Icon( + Icons.label_rounded, + color: Colors.grey, + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.selectTags, + style: const TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.noTagsSelected, + style: const TextStyle( + color: Colors.grey + ), + ) + ], + ) + ], + ), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + sectionLabel(AppLocalizations.of(context)!.identifiers), + Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => setState(() => identifiersControllers.add({ + 'id': uuid.v4(), + 'controller': TextEditingController() + })), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (identifiersControllers.isNotEmpty) ...identifiersControllers.map((controller) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width - 108, + child: TextFormField( + controller: controller['controller'], + decoration: InputDecoration( + prefixIcon: const Icon(Icons.tag), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + helperText: AppLocalizations.of(context)!.identifierHelper, + labelText: AppLocalizations.of(context)!.identifier, + ), + ), + ), + const SizedBox(width: 20), + Padding( + padding: const EdgeInsets.only(bottom: 25), + child: IconButton( + onPressed: () => setState( + () => identifiersControllers = identifiersControllers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ), + ) + ], + ), + ), + )).toList(), + if (identifiersControllers.isEmpty) Container( + padding: const EdgeInsets.only(top: 10), + child: Text( + AppLocalizations.of(context)!.noIdentifiers, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + color: Colors.grey + ), + ), + ), + sectionLabel(AppLocalizations.of(context)!.settings), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Material( + color: Theme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: enableDisableGlobalSettingsFiltering, + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: const TextStyle( + fontSize: 16, + ), + ), + Switch( + value: useGlobalSettingsFiltering, + onChanged: (value) => enableDisableGlobalSettingsFiltering(), + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + settignsTile( + label: AppLocalizations.of(context)!.enableFiltering, + value: enableFiltering, + onChange: (value) => setState(() => enableFiltering = value) + ), + settignsTile( + label: AppLocalizations.of(context)!.enableSafeBrowsing, + value: enableSafeBrowsing, + onChange: (value) => setState(() => enableSafeBrowsing = value) + ), + settignsTile( + label: AppLocalizations.of(context)!.enableParentalControl, + value: enableParentalControl, + onChange: (value) => setState(() => enableParentalControl = value) + ), + settignsTile( + label: AppLocalizations.of(context)!.enableSafeSearch, + value: enableSafeSearch, + onChange: (value) => setState(() => enableSafeSearch = value) + ), + sectionLabel(AppLocalizations.of(context)!.blockedServices), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Material( + color: Theme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: () => setState(() => useGlobalSettingsServices = !useGlobalSettingsServices), + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: const TextStyle( + fontSize: 16, + ), + ), + Switch( + value: useGlobalSettingsServices, + onChanged: (value) => setState(() => useGlobalSettingsServices = value), + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + Material( + color: Colors.transparent, + child: InkWell( + onTap: useGlobalSettingsServices == false + ? () => {} + : null, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20 + ), + child: Row( + children: [ + const Icon( + Icons.public, + color: Colors.grey, + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.selectBlockedServices, + style: TextStyle( + fontSize: 16, + color: useGlobalSettingsServices == false + ? null + : Colors.grey + ), + ), + if (useGlobalSettingsServices == false) ...[ + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.noBlockedServicesSelected, + style: const TextStyle( + color: Colors.grey + ), + ) + ] + ], + ) + ], + ), + ), + ), + ), + ], + ), + ), + 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: checkValidValues() == true + ? createClient + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: checkValidValues() == true + ? Theme.of(context).primaryColor + : Colors.grey + ), + ) + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 6a283c5..d9c897e 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -5,7 +5,9 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/block_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/add_client_modal.dart'; +import 'package:adguard_home_manager/models/add_client.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/clients_allowed_blocked.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -72,6 +74,10 @@ class ClientsFab extends StatelessWidget { } } + void confirmAddClient(AddClient client) { + + } + void openBlockClient() { showModalBottomSheet( context: context, @@ -84,14 +90,14 @@ class ClientsFab extends StatelessWidget { } void openAddClient() { - // showModalBottomSheet( - // context: context, - // builder: (ctx) => BlockClientModal( - // onConfirm: confirmRemoveDomain - // ), - // isScrollControlled: true, - // backgroundColor: Colors.transparent - // ); + showModalBottomSheet( + context: context, + builder: (ctx) => AddClientModal( + onConfirm: confirmAddClient + ), + isScrollControlled: true, + backgroundColor: Colors.transparent + ); } if (appConfigProvider.selectedClientsTab == 1) {