Some improvements

This commit is contained in:
Juan Gilsanz Polo 2022-10-23 19:27:23 +02:00
parent d57572cc56
commit c3690902c8
7 changed files with 355 additions and 393 deletions

View file

@ -523,5 +523,7 @@
"dataNotValid": "Data not valid",
"encryptionConfigSaved": "Encryption configuration saved successfully",
"encryptionConfigNotSaved": "Encryption configuration could not be saved",
"configError": "Configuration error"
"configError": "Configuration error",
"enterOnlyCertificate": "Enter only the certificate. Do not input the ---BEGIN--- and ---END--- lines.",
"enterOnlyPrivateKey": "Enter only the key. Do not input the ---BEGIN--- and ---END--- lines."
}

View file

@ -523,5 +523,7 @@
"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"
"configError": "Configuration error",
"enterOnlyCertificate": "Introduce sólo el certificado. No introduzcas las líneas ---BEGIN--- y ---END---.",
"enterOnlyPrivateKey": "Introduce sólo la clave privada. No introduzcas las líneas ---BEGIN--- y ---END---."
}

View file

@ -50,11 +50,6 @@ class ServersProvider with ChangeNotifier {
data: null
);
final Encryption _encryptionSettings = Encryption(
loadStatus: 0, // 0 = loading, 1 = loaded, 2 = error,
data: null
);
FilteringStatus? _filteringStatus;
List<Server> get serversList {
@ -97,10 +92,6 @@ class ServersProvider with ChangeNotifier {
return _dnsInfo;
}
Encryption get encryptionSettings {
return _encryptionSettings;
}
void setDbInstance(Database db) {
_dbInstance = db;
}
@ -211,18 +202,6 @@ class ServersProvider with ChangeNotifier {
}
}
void setEncryptionSettings(EncryptionData data) {
_encryptionSettings.data = data;
notifyListeners();
}
void setEncryptionSettingsLoadStatus(int status, bool notify) {
_encryptionSettings.loadStatus = status;
if (notify == true) {
notifyListeners();
}
}
Future<bool> createServer(Server server) async {
final saved = await saveServerIntoDb(server);
if (saved == true) {

View file

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
class EncryptionTextField extends StatelessWidget {
final bool enabled;
final TextEditingController controller;
final IconData icon;
final void Function(String) onChanged;
final String? errorText;
final String label;
final TextInputType? keyboardType;
final bool? multiline;
final String? helperText;
const EncryptionTextField({
Key? key,
required this.enabled,
required this.controller,
required this.icon,
required this.onChanged,
this.errorText,
required this.label,
this.keyboardType,
this.multiline,
this.helperText,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 200
),
child: TextFormField(
enabled: enabled,
controller: controller,
onChanged: onChanged,
decoration: InputDecoration(
prefixIcon: Icon(icon),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: errorText,
labelText: label,
helperText: helperText,
helperMaxLines: 10,
helperStyle: TextStyle(
color: Theme.of(context).listTileTheme.iconColor
)
),
keyboardType: TextInputType.multiline,
maxLines: multiline == true ? null : 1,
),
),
);
}
}

View file

@ -6,6 +6,9 @@ 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/screens/settings/encryption/custom_text_field.dart';
import 'package:adguard_home_manager/screens/settings/encryption/master_switch.dart';
import 'package:adguard_home_manager/screens/settings/encryption/encryption_functions.dart';
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
@ -44,6 +47,8 @@ class EncryptionSettingsWidget extends StatefulWidget {
}
class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
int loadStatus = 0;
bool enabled = false;
final TextEditingController domainNameController = TextEditingController();
@ -78,12 +83,12 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
final TextEditingController pastePrivateKeyController = TextEditingController();
String? pastePrivateKeyError;
bool validData = false;
bool localValidationValid = false;
String? validDataError;
int dataValidApi = 0;
void fetchData({bool? showRefreshIndicator}) async {
widget.serversProvider.setEncryptionSettingsLoadStatus(0, showRefreshIndicator ?? false);
setState(() => loadStatus = 0);
final result = await getEncryptionSettings(server: widget.serversProvider.selectedServer!);
@ -115,120 +120,17 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
privateKeyPathController.text = result['data'].privateKeyPath;
}
usePreviouslySavedKey = result['data'].privateKeySaved;
});
widget.serversProvider.setEncryptionSettings(result['data']);
widget.serversProvider.setEncryptionSettingsLoadStatus(1, true);
loadStatus = 1;
});
}
else {
widget.appConfigProvider.addLog(result['log']);
widget.serversProvider.setEncryptionSettingsLoadStatus(2, true);
setState(() => loadStatus = 2);
}
}
}
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({Map<String, dynamic>? data}) async {
setState(() => dataValidApi = 0);
@ -295,11 +197,11 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
pastePrivateKeyError == null
))
) {
setState(() => validData = true);
setState(() => localValidationValid = true);
checkValidDataApi();
}
else {
setState(() => validData = false);
setState(() => localValidationValid = false);
}
}
@ -355,50 +257,8 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
}
}
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) {
switch (loadStatus) {
case 0:
return SizedBox(
width: double.maxFinite,
@ -423,89 +283,25 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
case 1:
return ListView(
children: [
Padding(
padding: const EdgeInsets.only(
top: 10,
left: 12,
right: 12
),
child: Material(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
child: InkWell(
onTap: () {
setState(() => enabled = !enabled);
checkDataValid();
checkValidDataApi();
},
borderRadius: BorderRadius.circular(28),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.enableEncryption,
style: const TextStyle(
fontSize: 18,
),
),
const SizedBox(height: 3),
Text(
AppLocalizations.of(context)!.enableEncryptionTypes,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).listTileTheme.iconColor,
),
)
],
),
),
Switch(
value: enabled,
onChanged: (value) {
setState(() => enabled = value);
checkDataValid();
checkValidDataApi();
},
activeColor: Theme.of(context).primaryColor,
),
],
),
),
),
),
EncryptionMasterSwitch(
value: enabled,
onChange: (value) {
setState(() => enabled = value);
checkDataValid();
}
),
SectionLabel(label: AppLocalizations.of(context)!.serverConfiguration),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled,
controller: domainNameController,
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)!.domainName,
helperText: AppLocalizations.of(context)!.domainNameDescription,
helperStyle: TextStyle(
color: Theme.of(context).listTileTheme.iconColor
),
helperMaxLines: 10
),
),
EncryptionTextField(
enabled: enabled,
controller: domainNameController,
icon: Icons.link_rounded,
onChanged: (value) {
setState(() => domainError = validateDomain(context, value));
checkDataValid();
},
errorText: domainError,
label: AppLocalizations.of(context)!.domainName,
helperText: AppLocalizations.of(context)!.domainNameDescription,
),
const SizedBox(height: 10),
CustomSwitchListTile(
@ -513,70 +309,48 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
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: (value) => validatePort(value, 'https'),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.numbers_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: httpsPortError,
labelText: AppLocalizations.of(context)!.httpsPort,
),
keyboardType: TextInputType.number,
),
EncryptionTextField(
enabled: enabled,
controller: httpsPortController,
icon: Icons.numbers_rounded,
onChanged: (value) {
setState(() => httpsPortError = validatePort(context, value));
checkDataValid();
},
errorText: httpsPortError,
label: AppLocalizations.of(context)!.httpsPort,
keyboardType: TextInputType.number,
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled,
controller: tlsPortController,
onChanged: (value) => validatePort(value, 'tls'),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.numbers_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: tlsPortError,
labelText: AppLocalizations.of(context)!.tlsPort,
),
keyboardType: TextInputType.number,
),
EncryptionTextField(
enabled: enabled,
controller: tlsPortController,
icon: Icons.numbers_rounded,
onChanged: (value) {
setState(() => tlsPortError = validatePort(context, value));
checkDataValid();
},
errorText: tlsPortError,
label: AppLocalizations.of(context)!.tlsPort,
keyboardType: TextInputType.number,
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled,
controller: dnsOverQuicPortController,
onChanged: (value) => validatePort(value, 'quic'),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.numbers_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: dnsOverQuicPortError,
labelText: AppLocalizations.of(context)!.dnsOverQuicPort,
),
keyboardType: TextInputType.number,
),
EncryptionTextField(
enabled: enabled,
controller: dnsOverQuicPortController,
icon: Icons.numbers_rounded,
onChanged: (value) {
setState(() => dnsOverQuicPortError = validatePort(context, value));
checkDataValid();
},
errorText: dnsOverQuicPortError,
label: AppLocalizations.of(context)!.dnsOverQuicPort,
keyboardType: TextInputType.number,
),
SectionLabel(label: AppLocalizations.of(context)!.certificates),
Card(
@ -602,7 +376,6 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
? (value) {
setState(() => certificateOption = int.parse(value.toString()));
checkDataValid();
checkValidDataApi();
}
: null,
title: Text(
@ -619,7 +392,6 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
? (value) {
setState(() => certificateOption = int.parse(value.toString()));
checkDataValid();
checkValidDataApi();
}
: null,
title: Text(
@ -630,43 +402,30 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
),
),
const SizedBox(height: 10),
if (certificateOption == 0) Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled,
controller: certificatePathController,
onChanged: (value) => validatePath(value, 'cert'),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.description_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: certificatePathError,
labelText: AppLocalizations.of(context)!.certificatePath,
),
),
if (certificateOption == 0) EncryptionTextField(
enabled: enabled,
controller: certificatePathController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => certificatePathError = validatePath(context, value));
checkDataValid();
},
label: AppLocalizations.of(context)!.certificatePath,
errorText: certificatePathError,
),
if (certificateOption == 1) Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled,
controller: certificateContentController,
onChanged: validateCertificate,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.description_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: certificateContentError,
labelText: AppLocalizations.of(context)!.certificateContent,
),
keyboardType: TextInputType.multiline,
maxLines: null,
),
if (certificateOption == 1) EncryptionTextField(
enabled: enabled,
controller: certificateContentController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => certificateContentError = validateCertificate(context, value));
checkDataValid();
},
label: AppLocalizations.of(context)!.certificateContent,
helperText: AppLocalizations.of(context)!.enterOnlyCertificate,
errorText: certificateContentError,
multiline: true,
keyboardType: TextInputType.multiline,
),
SectionLabel(label: AppLocalizations.of(context)!.privateKey),
RadioListTile(
@ -676,7 +435,6 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
? (value) {
setState(() => privateKeyOption = int.parse(value.toString()));
checkDataValid();
checkValidDataApi();
}
: null,
title: Text(
@ -693,7 +451,6 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
? (value) {
setState(() => privateKeyOption = int.parse(value.toString()));
checkDataValid();
checkValidDataApi();
}
: null,
title: Text(
@ -712,45 +469,32 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
),
const SizedBox(height: 10)
],
if (privateKeyOption == 0) Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled,
controller: privateKeyPathController,
onChanged: (value) => validatePath(value, 'private_key'),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.description_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: privateKeyPathError,
labelText: AppLocalizations.of(context)!.privateKeyPath,
),
),
if (privateKeyOption == 0) EncryptionTextField(
enabled: enabled,
controller: privateKeyPathController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => privateKeyPathError = validatePath(context, value));
checkDataValid();
},
label: AppLocalizations.of(context)!.privateKeyPath,
errorText: privateKeyPathError,
),
if (privateKeyOption == 1) Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: enabled == true
? !usePreviouslySavedKey
: false,
controller: pastePrivateKeyController,
onChanged: validatePrivateKey,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.description_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: pastePrivateKeyError,
labelText: AppLocalizations.of(context)!.pastePrivateKey,
),
keyboardType: TextInputType.multiline,
maxLines: null,
),
if (privateKeyOption == 1) EncryptionTextField(
enabled: enabled == true
? !usePreviouslySavedKey
: false,
controller: pastePrivateKeyController,
icon: Icons.description_rounded,
onChanged: (value) {
setState(() => pastePrivateKeyError = validatePrivateKey(context, value));
checkDataValid();
},
label: AppLocalizations.of(context)!.pastePrivateKey,
helperText: AppLocalizations.of(context)!.enterOnlyPrivateKey,
errorText: pastePrivateKeyError,
keyboardType: TextInputType.multiline,
multiline: true,
),
const SizedBox(height: 20),
],
@ -798,11 +542,11 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
builder: (context) => EncryptionErrorModal(error: validDataError!)
)
} : null,
icon: generateStatus(),
tooltip: generateStatusString()
icon: generateStatus(localValidationValid, dataValidApi),
tooltip: generateStatusString(context, localValidationValid, dataValidApi)
),
IconButton(
onPressed: validData == true && dataValidApi == 1
onPressed: localValidationValid == true && dataValidApi == 1
? () => saveData()
: null,
icon: const Icon(Icons.save),

View file

@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
String? validateDomain(BuildContext context, String domain) {
RegExp regExp = RegExp(r'^([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+$');
if (regExp.hasMatch(domain)) {
return null;
}
else {
return AppLocalizations.of(context)!.domainNotValid;
}
}
String? validatePort(BuildContext context, String value) {
if (int.tryParse(value) != null && int.parse(value) <= 65535) {
return null;
}
else {
return AppLocalizations.of(context)!.invalidPort;
}
}
String? validateCertificate(BuildContext context, String cert) {
final regExp = RegExp(r'(-{3,}(\bBEGIN CERTIFICATE\b))|(-{3,}-{3,}(\END CERTIFICATE\b)-{3,})', multiLine: true);
if (regExp.hasMatch(cert.replaceAll('\n', ''))) {
return AppLocalizations.of(context)!.invalidCertificate;
}
else {
return null;
}
}
String? validatePrivateKey(BuildContext context, 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', ''))) {
return AppLocalizations.of(context)!.invalidPrivateKey;
}
else {
return null;
}
}
String? validatePath(BuildContext context, String cert) {
final regExp = RegExp(r'^(\/{0,1}(?!\/))[A-Za-z0-9\/\-_]+(\.([a-zA-Z]+))?$');
if (regExp.hasMatch(cert)) {
return null;
}
else {
return AppLocalizations.of(context)!.invalidPath;
}
}
Widget generateStatus(bool localValidation, int dataValidApi) {
if (localValidation == true) {
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();
}
}
else {
return const Icon(
Icons.error_rounded,
color: Colors.grey,
);
}
}
String generateStatusString(BuildContext context, bool localValidation, int dataValidApi) {
if (localValidation == true) {
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 "";
}
}
else {
return AppLocalizations.of(context)!.dataNotValid;
}
}

View file

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class EncryptionMasterSwitch extends StatelessWidget {
final bool value;
final void Function(bool) onChange;
const EncryptionMasterSwitch({
Key? key,
required this.value,
required this.onChange
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 10,
left: 12,
right: 12
),
child: Material(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
child: InkWell(
onTap: () => onChange(!value),
borderRadius: BorderRadius.circular(28),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.enableEncryption,
style: const TextStyle(
fontSize: 18,
),
),
const SizedBox(height: 3),
Text(
AppLocalizations.of(context)!.enableEncryptionTypes,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).listTileTheme.iconColor,
),
)
],
),
),
Switch(
value: value,
onChanged: (value) => onChange(value),
activeColor: Theme.of(context).primaryColor,
),
],
),
),
),
),
);
}
}