From e78dbb232ff511523b6164df0237ac4fd4483828 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Fri, 7 Oct 2022 17:08:23 +0200 Subject: [PATCH] Added edit client --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/models/clients.dart | 6 +- lib/screens/clients/added_list.dart | 57 +++++- ...dd_client_modal.dart => client_modal.dart} | 170 ++++++++++++------ lib/screens/clients/fab.dart | 4 +- lib/services/http_requests.dart | 34 ++++ 7 files changed, 219 insertions(+), 60 deletions(-) rename lib/screens/clients/{add_client_modal.dart => client_modal.dart} (79%) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 524d723..22de9b8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -193,5 +193,7 @@ "serverAddress": "Server address", "noUpstreamServers": "No upstream servers.", "willBeUsedGeneralServers": "General upstream servers will be used.", - "added": "Added" + "added": "Added", + "clientUpdatedSuccessfully": "Client updated successfully", + "clientNotUpdated": "Client could not be updated" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 2b1f3b0..30a5a47 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -193,5 +193,7 @@ "serverAddress": "Dirección del servidor", "noUpstreamServers": "No hay servidores de salida.", "willBeUsedGeneralServers": "Se usarán los servidores de salida generales.", - "added": "Añadidos" + "added": "Añadidos", + "clientUpdatedSuccessfully": "Cliente actualizado correctamente", + "clientNotUpdated": "El cliente no pudo ser actualizado" } \ No newline at end of file diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 1e9f9d1..715b64a 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -17,7 +17,7 @@ ClientsData clientsFromJson(String str) => ClientsData.fromJson(json.decode(str) String clientsToJson(ClientsData data) => json.encode(data.toJson()); class ClientsData { - final List clients; + List clients; final List autoClientsData; final List supportedTags; ClientsAllowedBlocked? clientsAllowedBlocked; @@ -80,7 +80,7 @@ class WhoisInfo { class Client { final String name; - final dynamic blockedServices; + final List blockedServices; final List ids; final List tags; final List upstreams; @@ -107,7 +107,7 @@ class Client { factory Client.fromJson(Map json) => Client( name: json["name"], - blockedServices: json["blocked_services"], + blockedServices: List.from(json["blocked_services"]), ids: List.from(json["ids"].map((x) => x)), tags: List.from(json["tags"].map((x) => x)), upstreams: List.from(json["upstreams"].map((x) => x)), diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 33af30f..ed4fae3 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -5,7 +5,10 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/fab.dart'; +import 'package:adguard_home_manager/screens/clients/client_modal.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -25,6 +28,58 @@ class AddedList extends StatelessWidget { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + void confirmEditClient(Client client) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.addingClient); + + final result = await postUpdateClient(server: serversProvider.selectedServer!, data: { + 'name': client.name, + 'data': client.toJson() + }); + + processModal.close(); + + if (result['result'] == 'success') { + ClientsData clientsData = serversProvider.clients.data!; + clientsData.clients = clientsData.clients.map((e) { + if (e.name == client.name) { + return client; + } + else { + return e; + } + }).toList(); + serversProvider.setClientsData(clientsData); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.clientUpdatedSuccessfully), + backgroundColor: Colors.green, + ) + ); + } + else { + appConfigProvider.addLog(result['log']); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.clientNotUpdated), + backgroundColor: Colors.red, + ) + ); + } + } + + void openClientModal(Client client) { + showModalBottomSheet( + context: context, + builder: (ctx) => ClientModal( + client: client, + onConfirm: confirmEditClient + ), + isScrollControlled: true, + backgroundColor: Colors.transparent + ); + } + return Stack( children: [ if (data.isNotEmpty) RefreshIndicator( @@ -34,7 +89,7 @@ class AddedList extends StatelessWidget { itemCount: data.length, itemBuilder: (context, index) => ListTile( isThreeLine: true, - onTap: () => {}, + onTap: () => openClientModal(data[index]), title: Padding( padding: const EdgeInsets.only(bottom: 5), child: Text(data[index].name), diff --git a/lib/screens/clients/add_client_modal.dart b/lib/screens/clients/client_modal.dart similarity index 79% rename from lib/screens/clients/add_client_modal.dart rename to lib/screens/clients/client_modal.dart index c92251b..e30fcb9 100644 --- a/lib/screens/clients/add_client_modal.dart +++ b/lib/screens/clients/client_modal.dart @@ -9,20 +9,23 @@ import 'package:adguard_home_manager/screens/clients/tags_modal.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; -class AddClientModal extends StatefulWidget { +class ClientModal extends StatefulWidget { + final Client? client; final void Function(Client) onConfirm; - const AddClientModal({ + const ClientModal({ Key? key, + this.client, required this.onConfirm }) : super(key: key); @override - State createState() => _AddClientModalState(); + State createState() => _ClientModalState(); } -class _AddClientModalState extends State { +class _ClientModalState extends State { final Uuid uuid = const Uuid(); + bool editMode = true; TextEditingController nameController = TextEditingController(); @@ -59,6 +62,28 @@ class _AddClientModalState extends State { return false; } } + + @override + void initState() { + if (widget.client != null) { + editMode = false; + + nameController.text = widget.client!.name; + selectedTags = widget.client!.tags; + identifiersControllers = widget.client!.ids.map((e) => { + 'id': uuid.v4(), + 'controller': TextEditingController(text: e) + }).toList(); + useGlobalSettingsFiltering = widget.client!.useGlobalSettings; + enableFiltering = widget.client!.filteringEnabled; + enableParentalControl = widget.client!.parentalEnabled; + enableSafeBrowsing = widget.client!.safebrowsingEnabled; + enableSafeSearch = widget.client!.safesearchEnabled; + useGlobalSettingsServices = widget.client!.useGlobalBlockedServices; + blockedServices = widget.client!.blockedServices; + } + super.initState(); + } @override Widget build(BuildContext context) { @@ -159,12 +184,14 @@ class _AddClientModalState extends State { Widget settignsTile({ required String label, required bool? value, - required void Function(bool) onChange + void Function(bool)? onChange }) { return Material( color: Colors.transparent, child: InkWell( - onTap: value != null ? () => onChange(!value) : null, + onTap: onChange != null + ? value != null ? () => onChange(!value) : null + : null, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 30, @@ -179,9 +206,9 @@ class _AddClientModalState extends State { fontSize: 15 ), ), - value != null + useGlobalSettingsFiltering == false ? Switch( - value: value, + value: value!, onChanged: onChange, activeColor: Theme.of(context).primaryColor, ) @@ -238,6 +265,7 @@ class _AddClientModalState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: widget.client != null ? false : true, controller: nameController, onChanged: (_) => checkValidValues(), decoration: InputDecoration( @@ -255,7 +283,7 @@ class _AddClientModalState extends State { Material( color: Colors.transparent, child: InkWell( - onTap: openTagsModal, + onTap: editMode == true ? () => openTagsModal() : null, child: Padding( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 20 @@ -296,7 +324,7 @@ class _AddClientModalState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ sectionLabel(AppLocalizations.of(context)!.identifiers), - Padding( + if (editMode == true) Padding( padding: const EdgeInsets.only(right: 20), child: IconButton( onPressed: () => setState(() => identifiersControllers.add({ @@ -316,8 +344,11 @@ class _AddClientModalState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( - width: MediaQuery.of(context).size.width - 108, + width: editMode == true + ? MediaQuery.of(context).size.width - 108 + : MediaQuery.of(context).size.width - 40, child: TextFormField( + enabled: editMode, controller: controller['controller'], onChanged: (_) => checkValidValues(), decoration: InputDecoration( @@ -332,16 +363,18 @@ class _AddClientModalState extends State { ), ), ), - 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) - ), - ) + if (editMode == true) ...[ + 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) + ), + ) + ] ], ), ), @@ -364,7 +397,9 @@ class _AddClientModalState extends State { color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(28), child: InkWell( - onTap: enableDisableGlobalSettingsFiltering, + onTap: editMode + ? () => enableDisableGlobalSettingsFiltering() + : null, borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( @@ -382,7 +417,9 @@ class _AddClientModalState extends State { ), Switch( value: useGlobalSettingsFiltering, - onChanged: (value) => enableDisableGlobalSettingsFiltering(), + onChanged: editMode == true + ? (value) => enableDisableGlobalSettingsFiltering() + : null, activeColor: Theme.of(context).primaryColor, ) ], @@ -395,22 +432,30 @@ class _AddClientModalState extends State { settignsTile( label: AppLocalizations.of(context)!.enableFiltering, value: enableFiltering, - onChange: (value) => setState(() => enableFiltering = value) + onChange: editMode == true + ? (value) => setState(() => enableFiltering = value) + : null ), settignsTile( label: AppLocalizations.of(context)!.enableSafeBrowsing, value: enableSafeBrowsing, - onChange: (value) => setState(() => enableSafeBrowsing = value) + onChange: editMode == true + ? (value) => setState(() => enableSafeBrowsing = value) + : null ), settignsTile( label: AppLocalizations.of(context)!.enableParentalControl, value: enableParentalControl, - onChange: (value) => setState(() => enableParentalControl = value) + onChange: editMode == true + ? (value) => setState(() => enableParentalControl = value) + : null ), settignsTile( label: AppLocalizations.of(context)!.enableSafeSearch, value: enableSafeSearch, - onChange: (value) => setState(() => enableSafeSearch = value) + onChange: editMode == true + ? (value) => setState(() => enableSafeSearch = value) + : null ), sectionLabel(AppLocalizations.of(context)!.blockedServices), Padding( @@ -419,7 +464,9 @@ class _AddClientModalState extends State { color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(28), child: InkWell( - onTap: () => updateServicesGlobalSettings(!useGlobalSettingsServices), + onTap: editMode == true + ? () => updateServicesGlobalSettings(!useGlobalSettingsServices) + : null, borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( @@ -437,7 +484,9 @@ class _AddClientModalState extends State { ), Switch( value: useGlobalSettingsServices, - onChanged: updateServicesGlobalSettings, + onChanged: editMode == true + ? (value) => updateServicesGlobalSettings(value) + : null, activeColor: Theme.of(context).primaryColor, ) ], @@ -450,8 +499,10 @@ class _AddClientModalState extends State { Material( color: Colors.transparent, child: InkWell( - onTap: useGlobalSettingsServices == false - ? openServicesModal + onTap: editMode == true + ? useGlobalSettingsServices == false + ? openServicesModal + : null : null, child: Padding( padding: const EdgeInsets.symmetric( @@ -498,7 +549,7 @@ class _AddClientModalState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ sectionLabel(AppLocalizations.of(context)!.upstreamServers), - Padding( + if (editMode == true) Padding( padding: const EdgeInsets.only(right: 20), child: IconButton( onPressed: () => setState(() => upstreamServers.add({ @@ -520,6 +571,7 @@ class _AddClientModalState extends State { SizedBox( width: MediaQuery.of(context).size.width - 108, child: TextFormField( + enabled: editMode, controller: controller['controller'], onChanged: (_) => checkValidValues(), decoration: InputDecoration( @@ -574,29 +626,43 @@ class _AddClientModalState extends State { Padding( padding: const EdgeInsets.all(20), child: Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: widget.client == null || (widget.client != null && editMode == true) + ? MainAxisAlignment.end + : MainAxisAlignment.spaceBetween, children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) + if (widget.client != null && editMode == false) TextButton( + onPressed: () => setState(() => editMode = true), + child: Text(AppLocalizations.of(context)!.edit) ), - const SizedBox(width: 20), - TextButton( - onPressed: checkValidValues() == true - ? () { - createClient(); - Navigator.pop(context); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: checkValidValues() == true - ? Theme.of(context).primaryColor - : Colors.grey + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) ), - ) - ), + if (widget.client == null || (widget.client != null && editMode == true)) ...[ + const SizedBox(width: 20), + TextButton( + onPressed: checkValidValues() == true + ? () { + createClient(); + Navigator.pop(context); + } + : null, + child: Text( + widget.client != null && editMode == true + ? AppLocalizations.of(context)!.save + : AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: checkValidValues() == true + ? Theme.of(context).primaryColor + : Colors.grey + ), + ) + ), + ] + ], + ) ], ), ), diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 385d5ed..5e093db 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -5,7 +5,7 @@ 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/screens/clients/client_modal.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; @@ -118,7 +118,7 @@ class ClientsFab extends StatelessWidget { void openAddClient() { showModalBottomSheet( context: context, - builder: (ctx) => AddClientModal( + builder: (ctx) => ClientModal( onConfirm: confirmAddClient ), isScrollControlled: true, diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index f86e564..1c87b01 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -635,4 +635,38 @@ Future postAddClient({ else { return result; } +} + +Future postUpdateClient({ + required Server server, + required Map data, +}) async { + final result = await apiRequest( + urlPath: '/clients/update', + method: 'post', + server: server, + body: data, + type: 'update_client' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return {'result': 'success'}; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'update_client', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } + } + else { + return result; + } } \ No newline at end of file