From 3a2483e9d649c276ba1e0201424bacf945fb709f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 23 Oct 2022 04:50:12 +0200 Subject: [PATCH] Added check and save encryption settings --- lib/l10n/app_en.arb | 10 +- lib/l10n/app_es.arb | 10 +- lib/models/encryption.dart | 34 +- .../encryption/config_error_modal.dart | 25 ++ .../settings/encryption/encryption.dart | 397 +++++++++++++++++- lib/services/http_requests.dart | 71 ++++ lib/widgets/custom_switch_list_tile.dart | 19 +- 7 files changed, 535 insertions(+), 31 deletions(-) create mode 100644 lib/screens/settings/encryption/config_error_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a7cd559..45ce2b2 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -515,5 +515,13 @@ "privateKeyFile": "Set a private key file", "pastePrivateKey": "Paste the private key contents", "usePreviousKey": "Use the previously saved key", - "privateKeyPath": "Private key path" + "privateKeyPath": "Private key path", + "invalidCertificate": "Invalid certificate", + "invalidPrivateKey": "Invalid private key", + "validatingData": "Validating data", + "dataValid": "Data is valid", + "dataNotValid": "Data not valid", + "encryptionConfigSaved": "Encryption configuration saved successfully", + "encryptionConfigNotSaved": "Encryption configuration could not be saved", + "configError": "Configuration error" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 431b608..7b0fb1c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -515,5 +515,13 @@ "privateKeyFile": "Establecer un archivo de clave privada", "pastePrivateKey": "Pegar el contenido de la clave privada", "usePreviousKey": "Usar la clave privada guardada previamente", - "privateKeyPath": "Ruta de la clave privada" + "privateKeyPath": "Ruta de la clave privada", + "invalidCertificate": "Certificado no válido", + "invalidPrivateKey": "Clave privada no válida", + "validatingData": "Validando datos", + "dataValid": "Datos válidos", + "dataNotValid": "Datos no válidos", + "encryptionConfigSaved": "Configuración de cifrado guardada correctamente", + "encryptionConfigNotSaved": "No se pudo guardar la configuración de encriptado", + "configError": "Configuration error" } \ No newline at end of file diff --git a/lib/models/encryption.dart b/lib/models/encryption.dart index 9f90757..e0e0a4d 100644 --- a/lib/models/encryption.dart +++ b/lib/models/encryption.dart @@ -17,16 +17,20 @@ String encryptionDataToJson(EncryptionData data) => json.encode(data.toJson()); class EncryptionData { final bool validCert; final bool validChain; + final String? subject; + final String? issuer; final DateTime notBefore; final DateTime notAfter; - final dynamic dnsNames; + final List dnsNames; final bool validKey; + final String? keyType; final bool validPair; final bool enabled; + final String? serverName; final bool forceHttps; - final int portHttps; - final int portDnsOverTls; - final int portDnsOverQuic; + final int? portHttps; + final int? portDnsOverTls; + final int? portDnsOverQuic; final int portDnscrypt; final String dnscryptConfigFile; final bool allowUnencryptedDoh; @@ -39,16 +43,20 @@ class EncryptionData { EncryptionData({ required this.validCert, required this.validChain, + this.subject, + this.issuer, required this.notBefore, required this.notAfter, required this.dnsNames, required this.validKey, + this.keyType, required this.validPair, required this.enabled, + this.serverName, required this.forceHttps, - required this.portHttps, - required this.portDnsOverTls, - required this.portDnsOverQuic, + this.portHttps, + this.portDnsOverTls, + this.portDnsOverQuic, required this.portDnscrypt, required this.dnscryptConfigFile, required this.allowUnencryptedDoh, @@ -63,12 +71,16 @@ class EncryptionData { factory EncryptionData.fromJson(Map json) => EncryptionData( validCert: json["valid_cert"], validChain: json["valid_chain"], + subject: json["subject"], + issuer: json["issuer"], notBefore: DateTime.parse(json["not_before"]), notAfter: DateTime.parse(json["not_after"]), - dnsNames: json["dns_names"], + dnsNames: json["dns_names"] != null ? List.from(json["dns_names"].map((x) => x)) : [], validKey: json["valid_key"], + keyType: json["key_type"], validPair: json["valid_pair"], enabled: json["enabled"], + serverName: json["server_name"], forceHttps: json["force_https"], portHttps: json["port_https"], portDnsOverTls: json["port_dns_over_tls"], @@ -86,12 +98,16 @@ class EncryptionData { Map toJson() => { "valid_cert": validCert, "valid_chain": validChain, + "subject": subject, + "issuer": issuer, "not_before": notBefore.toIso8601String(), "not_after": notAfter.toIso8601String(), - "dns_names": dnsNames, + "dns_names": List.from(dnsNames.map((x) => x)), "valid_key": validKey, + "key_type": keyType, "valid_pair": validPair, "enabled": enabled, + "server_name": serverName, "force_https": forceHttps, "port_https": portHttps, "port_dns_over_tls": portDnsOverTls, diff --git a/lib/screens/settings/encryption/config_error_modal.dart b/lib/screens/settings/encryption/config_error_modal.dart new file mode 100644 index 0000000..80aa0bc --- /dev/null +++ b/lib/screens/settings/encryption/config_error_modal.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class EncryptionErrorModal extends StatelessWidget { + final String error; + + const EncryptionErrorModal({ + Key? key, + required this.error, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(AppLocalizations.of(context)!.configError), + content: Text(error), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index 75f5855..2830180 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -1,10 +1,15 @@ +// ignore_for_file: use_build_context_synchronously + import 'package:flutter/material.dart'; 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/encryption/config_error_modal.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -73,6 +78,9 @@ class _EncryptionSettingsWidgetState extends State { final TextEditingController pastePrivateKeyController = TextEditingController(); String? pastePrivateKeyError; + bool validData = false; + String? validDataError; + int dataValidApi = 0; void fetchData({bool? showRefreshIndicator}) async { widget.serversProvider.setEncryptionSettingsLoadStatus(0, showRefreshIndicator ?? false); @@ -80,7 +88,35 @@ class _EncryptionSettingsWidgetState extends State { final result = await getEncryptionSettings(server: widget.serversProvider.selectedServer!); if (mounted) { + await checkValidDataApi(); + if (result['result'] == 'success') { + setState(() { + enabled = result['data'].enabled; + domainNameController.text = result['data'].serverName ?? ''; + redirectHttps = result['data'].forceHttps; + httpsPortController.text = result['data'].portHttps != null ? result['data'].portHttps.toString() : ''; + tlsPortController.text = result['data'].portDnsOverTls != null ? result['data'].portDnsOverTls.toString() : ''; + dnsOverQuicPortController.text = result['data'].portDnsOverQuic != null ? result['data'].portDnsOverQuic.toString() : ''; + if (result['data'].certificateChain != '' && result['data'].certificatePath == '') { + certificateOption = 1; + certificateContentController.text = result['data'].certificateChain; + } + else if (result['data'].certificateChain == '' && result['data'].certificatePath != '') { + certificateOption = 0; + certificatePathController.text = result['data'].certificatePath; + } + if (result['data'].privateKey != '' && result['data'].privateKeyPath == '') { + privateKeyOption = 1; + pastePrivateKeyController.text = result['data'].privateKey; + } + else if (result['data'].privateKey == '' && result['data'].privateKeyPath != '') { + privateKeyOption = 0; + privateKeyPathController.text = result['data'].privateKeyPath; + } + usePreviouslySavedKey = result['data'].privateKeySaved; + }); + widget.serversProvider.setEncryptionSettings(result['data']); widget.serversProvider.setEncryptionSettingsLoadStatus(1, true); } @@ -91,9 +127,186 @@ class _EncryptionSettingsWidgetState extends State { } } + void validateDomain(String domain) { + RegExp regExp = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$'); + if (regExp.hasMatch(domain)) { + setState(() => domainError = null); + checkValidDataApi(); + } + else { + setState(() => domainError = AppLocalizations.of(context)!.domainNotValid); + } + checkDataValid(); + } + + void validatePort(String value, String portType) { + if (int.tryParse(value) != null && int.parse(value) <= 65535) { + setState(() { + switch (portType) { + case 'https': + setState(() => httpsPortError = null); + break; + + case 'tls': + setState(() => tlsPortError = null); + break; + + case 'quic': + setState(() => dnsOverQuicPortError = null); + break; + + default: + break; + } + }); + checkValidDataApi(); + } + else { + setState(() { + switch (portType) { + case 'https': + setState(() => httpsPortError = AppLocalizations.of(context)!.invalidPort); + break; + + case 'tls': + setState(() => tlsPortError = AppLocalizations.of(context)!.invalidPort); + break; + + case 'quic': + setState(() => dnsOverQuicPortError = AppLocalizations.of(context)!.invalidPort); + break; + + default: + break; + } + }); + } + checkDataValid(); + } + + void validateCertificate(String cert) { + final regExp = RegExp(r'(-{3,}(\bBEGIN CERTIFICATE\b))|(-{3,}-{3,}(\END CERTIFICATE\b)-{3,})', multiLine: true); + if (regExp.hasMatch(cert.replaceAll('\n', ''))) { + setState(() => certificateContentError = AppLocalizations.of(context)!.invalidCertificate); + } + else { + setState(() => certificateContentError = null); + } + checkDataValid(); + checkValidDataApi(); + } + + void validatePrivateKey(String cert) { + final regExp = RegExp(r'(-{3,}(\bBEGIN\b).*(PRIVATE KEY\b))|(-{3,}-{3,}(\bEND\b).*(PRIVATE KEY\b)-{3,})', multiLine: true); + if (regExp.hasMatch(cert.replaceAll('\n', ''))) { + setState(() => pastePrivateKeyError = AppLocalizations.of(context)!.invalidPrivateKey); + } + else { + setState(() => pastePrivateKeyError = null); + } + checkValidDataApi(); + } + + void validatePath(String cert, String item) { + final regExp = RegExp(r'^(\/{0,1}(?!\/))[A-Za-z0-9\/\-_]+(\.([a-zA-Z]+))?$'); + if (regExp.hasMatch(cert)) { + if (item == 'cert') { + setState(() => certificatePathError = null); + } + else if (item == 'private_key') { + setState(() => privateKeyPathError = null); + } + checkValidDataApi(); + } + else { + if (item == 'cert') { + setState(() => certificatePathError = AppLocalizations.of(context)!.invalidPath); + } + else if (item == 'private_key') { + setState(() => privateKeyPathError = AppLocalizations.of(context)!.invalidPath); + } + } + checkDataValid(); + } + + Future checkValidDataApi() async { + setState(() => dataValidApi = 0); + + final result = await checkEncryptionSettings(server: widget.serversProvider.selectedServer!, data: { + "enabled": enabled, + "server_name": domainNameController.text, + "force_https": redirectHttps, + "port_https": httpsPortController.text != '' ? int.parse(httpsPortController.text) : null, + "port_dns_over_tls": tlsPortController.text != '' ? int.parse(tlsPortController.text) : null, + "port_dns_over_quic": dnsOverQuicPortController.text != '' ? int.parse(dnsOverQuicPortController.text) : null, + "certificate_chain": certificateContentController.text.replaceAll('\n', ''), + "private_key": pastePrivateKeyController.text.replaceAll('\n', ''), + "private_key_saved": usePreviouslySavedKey, + "certificate_path": certificatePathController.text, + "private_key_path": privateKeyPathController.text, + }); + + if (result['result'] == 'success') { + setState(() { + if (result['data']['warning_validation'] != null && result['data']['warning_validation'] != '') { + dataValidApi = 2; + validDataError = result['data']['warning_validation']; + } + else { + dataValidApi = 1; + validDataError = null; + } + }); + } + else { + if (result['log'].resBody != null) { + setState(() => validDataError = result['log'].resBody); + } + setState(() => dataValidApi = 2); + } + } + + void checkDataValid() { + if ( + domainNameController.text != '' && + domainError == null && + httpsPortController.text != '' && + httpsPortError == null && + tlsPortController.text != '' && + tlsPortError == null && + dnsOverQuicPortController.text != '' && + dnsOverQuicPortError == null && + (( + certificateOption == 0 && + certificatePathController.text != '' && + certificatePathError == null + ) || ( + certificateOption == 1 && + certificateContentController.text != '' && + certificateContentError == null + )) && + (( + privateKeyOption == 0 && + privateKeyPathController.text != '' && + privateKeyPathError == null + ) || ( + privateKeyOption == 1 && + pastePrivateKeyController.text != '' && + pastePrivateKeyError == null + )) + ) { + setState(() => validData = true); + checkValidDataApi(); + } + else { + setState(() => validData = false); + } + } + @override void initState() { fetchData(); + super.initState(); } @@ -102,6 +315,88 @@ class _EncryptionSettingsWidgetState extends State { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + void saveData() async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.savingConfig); + + final result = await saveEncryptionSettings(server: serversProvider.selectedServer!, data: { + "enabled": enabled, + "server_name": domainNameController.text, + "force_https": redirectHttps, + "port_https": int.parse(httpsPortController.text), + "port_dns_over_tls": int.parse(tlsPortController.text), + "port_dns_over_quic": int.parse(dnsOverQuicPortController.text), + "certificate_chain": certificateContentController.text, + "private_key": pastePrivateKeyController.text, + "private_key_saved": usePreviouslySavedKey, + "certificate_path": certificatePathController.text, + "private_key_path": privateKeyPathController.text, + }); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.encryptionConfigSaved, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.encryptionConfigNotSaved, + color: Colors.red + ); + } + } + + Widget generateStatus() { + if (dataValidApi == 0) { + return const SizedBox( + height: 25, + width: 25, + child: CircularProgressIndicator( + strokeWidth: 3, + ) + ); + } + else if (dataValidApi == 1) { + return const Icon( + Icons.check_circle_rounded, + color: Colors.green, + ); + } + else if (dataValidApi == 2) { + return const Icon( + Icons.cancel_rounded, + color: Colors.red, + ); + } + else { + return const SizedBox(); + } + } + + String generateStatusString() { + if (dataValidApi == 0) { + return AppLocalizations.of(context)!.validatingData; + } + else if (dataValidApi == 1) { + return AppLocalizations.of(context)!.dataValid; + } + else if (dataValidApi == 2) { + return AppLocalizations.of(context)!.dataNotValid; + } + else { + return ""; + } + } + Widget generateBody() { switch (widget.serversProvider.encryptionSettings.loadStatus) { case 0: @@ -138,7 +433,11 @@ class _EncryptionSettingsWidgetState extends State { color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(28), child: InkWell( - onTap: () => setState(() => enabled = !enabled), + onTap: () { + setState(() => enabled = !enabled); + checkDataValid(); + checkValidDataApi(); + }, borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( @@ -171,7 +470,11 @@ class _EncryptionSettingsWidgetState extends State { ), Switch( value: enabled, - onChanged: (value) => setState(() => enabled = value), + onChanged: (value) { + setState(() => enabled = value); + checkDataValid(); + checkValidDataApi(); + }, activeColor: Theme.of(context).primaryColor, ), ], @@ -184,8 +487,9 @@ class _EncryptionSettingsWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: domainNameController, - // onChanged: + onChanged: validateDomain, decoration: InputDecoration( prefixIcon: const Icon(Icons.link_rounded), border: const OutlineInputBorder( @@ -196,6 +500,9 @@ class _EncryptionSettingsWidgetState extends State { errorText: domainError, labelText: AppLocalizations.of(context)!.domainName, helperText: AppLocalizations.of(context)!.domainNameDescription, + helperStyle: TextStyle( + color: Theme.of(context).listTileTheme.iconColor + ), helperMaxLines: 10 ), ), @@ -203,15 +510,21 @@ class _EncryptionSettingsWidgetState extends State { const SizedBox(height: 10), CustomSwitchListTile( value: redirectHttps, - onChanged: (value) => setState(() => redirectHttps = value), + onChanged: (value) { + setState(() => redirectHttps = value); + checkDataValid(); + checkValidDataApi(); + }, title: AppLocalizations.of(context)!.redirectHttps, + disabled: !enabled, ), const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: httpsPortController, - // onChanged: + onChanged: (value) => validatePort(value, 'https'), decoration: InputDecoration( prefixIcon: const Icon(Icons.numbers_rounded), border: const OutlineInputBorder( @@ -229,8 +542,9 @@ class _EncryptionSettingsWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: tlsPortController, - // onChanged: + onChanged: (value) => validatePort(value, 'tls'), decoration: InputDecoration( prefixIcon: const Icon(Icons.numbers_rounded), border: const OutlineInputBorder( @@ -248,8 +562,9 @@ class _EncryptionSettingsWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: dnsOverQuicPortController, - // onChanged: + onChanged: (value) => validatePort(value, 'quic'), decoration: InputDecoration( prefixIcon: const Icon(Icons.numbers_rounded), border: const OutlineInputBorder( @@ -283,7 +598,13 @@ class _EncryptionSettingsWidgetState extends State { RadioListTile( value: 0, groupValue: certificateOption, - onChanged: (value) => setState(() => certificateOption = value!), + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + checkDataValid(); + checkValidDataApi(); + } + : null, title: Text( AppLocalizations.of(context)!.certificateFilePath, style: const TextStyle( @@ -294,7 +615,13 @@ class _EncryptionSettingsWidgetState extends State { RadioListTile( value: 1, groupValue: certificateOption, - onChanged: (value) => setState(() => certificateOption = value!), + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + checkDataValid(); + checkValidDataApi(); + } + : null, title: Text( AppLocalizations.of(context)!.pasteCertificateContent, style: const TextStyle( @@ -306,8 +633,9 @@ class _EncryptionSettingsWidgetState extends State { if (certificateOption == 0) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: certificatePathController, - // onChanged: + onChanged: (value) => validatePath(value, 'cert'), decoration: InputDecoration( prefixIcon: const Icon(Icons.description_rounded), border: const OutlineInputBorder( @@ -323,8 +651,9 @@ class _EncryptionSettingsWidgetState extends State { if (certificateOption == 1) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: certificateContentController, - // onChanged: + onChanged: validateCertificate, decoration: InputDecoration( prefixIcon: const Icon(Icons.description_rounded), border: const OutlineInputBorder( @@ -343,7 +672,13 @@ class _EncryptionSettingsWidgetState extends State { RadioListTile( value: 0, groupValue: privateKeyOption, - onChanged: (value) => setState(() => privateKeyOption = value!), + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + checkDataValid(); + checkValidDataApi(); + } + : null, title: Text( AppLocalizations.of(context)!.privateKeyFile, style: const TextStyle( @@ -354,7 +689,13 @@ class _EncryptionSettingsWidgetState extends State { RadioListTile( value: 1, groupValue: privateKeyOption, - onChanged: (value) => setState(() => privateKeyOption = value!), + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + checkDataValid(); + checkValidDataApi(); + } + : null, title: Text( AppLocalizations.of(context)!.pastePrivateKey, style: const TextStyle( @@ -374,8 +715,9 @@ class _EncryptionSettingsWidgetState extends State { if (privateKeyOption == 0) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( + enabled: enabled, controller: privateKeyPathController, - // onChanged: + onChanged: (value) => validatePath(value, 'private_key'), decoration: InputDecoration( prefixIcon: const Icon(Icons.description_rounded), border: const OutlineInputBorder( @@ -391,9 +733,11 @@ class _EncryptionSettingsWidgetState extends State { if (privateKeyOption == 1) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextFormField( - enabled: !usePreviouslySavedKey, + enabled: enabled == true + ? !usePreviouslySavedKey + : false, controller: pastePrivateKeyController, - // onChanged: + onChanged: validatePrivateKey, decoration: InputDecoration( prefixIcon: const Icon(Icons.description_rounded), border: const OutlineInputBorder( @@ -445,6 +789,27 @@ class _EncryptionSettingsWidgetState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.encryptionSettings), + actions: [ + IconButton( + onPressed: validData == true && dataValidApi == 2 && validDataError != null + ? () => { + showDialog( + context: context, + builder: (context) => EncryptionErrorModal(error: validDataError!) + ) + } : null, + icon: generateStatus(), + tooltip: generateStatusString() + ), + IconButton( + onPressed: dataValidApi == 1 + ? () => saveData() + : null, + icon: const Icon(Icons.save), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 10), + ], ), body: generateBody(), ); diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index fb2aa10..7a90887 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -1740,4 +1740,75 @@ Future getEncryptionSettings({ else { return result; } +} + +Future checkEncryptionSettings({ + required Server server, + required Map data, +}) async { + final result = await apiRequest( + urlPath: '/tls/validate', + method: 'post', + server: server, + body: data, + type: 'check_encryption_settings' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { + 'result': 'success', + 'data': jsonDecode(result['body']) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'check_encryption_settings', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'], + ) + }; + } + } + else { + return result; + } +} + +Future saveEncryptionSettings({ + required Server server, + required Map data, +}) async { + final result = await apiRequest( + urlPath: '/tls/configure', + method: 'post', + server: server, + body: data, + type: 'update_encryption_settings' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { 'result': 'success' }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'update_encryption_settings', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'], + ) + }; + } + } + else { + return result; + } } \ No newline at end of file diff --git a/lib/widgets/custom_switch_list_tile.dart b/lib/widgets/custom_switch_list_tile.dart index 702956b..3dce6f1 100644 --- a/lib/widgets/custom_switch_list_tile.dart +++ b/lib/widgets/custom_switch_list_tile.dart @@ -5,12 +5,14 @@ class CustomSwitchListTile extends StatelessWidget { final void Function(bool) onChanged; final String title; final String? subtitle; + final bool? disabled; const CustomSwitchListTile({ Key? key, required this.value, required this.onChanged, required this.title, + this.disabled, this.subtitle, }) : super(key: key); @@ -19,7 +21,9 @@ class CustomSwitchListTile extends StatelessWidget { return Material( color: Colors.transparent, child: InkWell( - onTap: () => onChanged(!value), + onTap: disabled != null && disabled == true + ? null + : () => onChanged(!value), child: Padding( padding: const EdgeInsets.only( top: 20, left: 24, right: 10, bottom: 20 @@ -34,8 +38,11 @@ class CustomSwitchListTile extends StatelessWidget { width: MediaQuery.of(context).size.width-110, child: Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, + color: disabled != null && disabled == true + ? Colors.grey + : null, ), ), ), @@ -47,7 +54,9 @@ class CustomSwitchListTile extends StatelessWidget { subtitle!, style: TextStyle( fontSize: 14, - color: Theme.of(context).listTileTheme.iconColor, + color: disabled != null && disabled == true + ? Colors.grey + : Theme.of(context).listTileTheme.iconColor, ), ), ), @@ -56,7 +65,9 @@ class CustomSwitchListTile extends StatelessWidget { ), Switch( value: value, - onChanged: onChanged, + onChanged: disabled != null && disabled == true + ? null + : onChanged, activeColor: Theme.of(context).primaryColor, ) ],