Added cache configuration and changed added client validation

This commit is contained in:
Juan Gilsanz Polo 2023-12-18 02:30:32 +01:00
parent 569ab7d569
commit 313b76740a
11 changed files with 302 additions and 167 deletions

View file

@ -718,5 +718,12 @@
"noData": "No data", "noData": "No data",
"unblockClient": "Unblock client", "unblockClient": "Unblock client",
"blockingClient": "Blocking client...", "blockingClient": "Blocking client...",
"unblockingClient": "Unblocking client..." "unblockingClient": "Unblocking client...",
"upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream",
"enableDnsCachingClient": "Enable DNS caching for this client",
"dnsCacheSize": "DNS cache size",
"nameInvalid": "Name is required",
"oneIdentifierRequired": "At least one identifier is required",
"dnsCacheNumber": "DNS cache size must be a number",
"errors": "Errors"
} }

View file

@ -718,5 +718,12 @@
"noData": "No hay datos", "noData": "No hay datos",
"unblockClient": "Desbloquear cliente", "unblockClient": "Desbloquear cliente",
"blockingClient": "Bloqueando cliente...", "blockingClient": "Bloqueando cliente...",
"unblockingClient": "Desbloqueando cliente..." "unblockingClient": "Desbloqueando cliente...",
"upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream",
"enableDnsCachingClient": "Habilitar caché de DNS para este cliente",
"dnsCacheSize": "Tamaño de caché de DNS",
"nameInvalid": "Se requiere un nombre",
"oneIdentifierRequired": "Se require al menos un identificador",
"dnsCacheNumber": "El tamaño de caché de DNS debe ser un número",
"errors": "Errores"
} }

View file

@ -89,6 +89,8 @@ class Client {
final SafeSearch? safeSearch; final SafeSearch? safeSearch;
final bool? ignoreQuerylog; final bool? ignoreQuerylog;
final bool? ignoreStatistics; final bool? ignoreStatistics;
final bool? upstreamsCacheEnabled;
final int? upstreamsCacheSize;
Client({ Client({
required this.name, required this.name,
@ -104,6 +106,8 @@ class Client {
required this.safeSearch, required this.safeSearch,
required this.ignoreQuerylog, required this.ignoreQuerylog,
required this.ignoreStatistics, required this.ignoreStatistics,
required this.upstreamsCacheEnabled,
required this.upstreamsCacheSize,
}); });
factory Client.fromJson(Map<String, dynamic> json) => Client( factory Client.fromJson(Map<String, dynamic> json) => Client(
@ -121,7 +125,9 @@ class Client {
? SafeSearch.fromJson(json["safe_search"]) ? SafeSearch.fromJson(json["safe_search"])
: null, : null,
ignoreQuerylog: json["ignore_querylog"], ignoreQuerylog: json["ignore_querylog"],
ignoreStatistics: json["ignore_statistics"] ignoreStatistics: json["ignore_statistics"],
upstreamsCacheEnabled: json["upstreams_cache_enabled"],
upstreamsCacheSize: json["upstreams_cache_size"]
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -138,5 +144,7 @@ class Client {
"use_global_settings": useGlobalSettings, "use_global_settings": useGlobalSettings,
"ignore_querylog": ignoreQuerylog, "ignore_querylog": ignoreQuerylog,
"ignore_statistics": ignoreStatistics, "ignore_statistics": ignoreStatistics,
"upstreams_cache_enabled": upstreamsCacheEnabled,
"upstreams_cache_size": upstreamsCacheSize
}; };
} }

View file

@ -22,7 +22,7 @@ class BlockedServicesSection extends StatelessWidget {
return Column( return Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Material( child: Material(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
@ -31,8 +31,8 @@ class BlockedServicesSection extends StatelessWidget {
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 16,
vertical: 5 vertical: 6
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -56,7 +56,7 @@ class BlockedServicesSection extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 12),
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -69,7 +69,7 @@ class BlockedServicesSection extends StatelessWidget {
: null, : null,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 24 vertical: 8, horizontal: 32
), ),
child: Row( child: Row(
children: [ children: [

View file

@ -1,6 +1,3 @@
import 'dart:io';
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -12,6 +9,7 @@ import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart';
import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; import 'package:adguard_home_manager/screens/clients/client/tags_section.dart';
import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart'; import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart';
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/section_label.dart';
@ -22,7 +20,6 @@ class ClientForm extends StatelessWidget {
final bool isFullScreen; final bool isFullScreen;
final Client? client; final Client? client;
final TextEditingController nameController; final TextEditingController nameController;
final void Function(bool) updateValidValues;
final List<ControllerListItem> identifiersControllers; final List<ControllerListItem> identifiersControllers;
final List<String> selectedTags; final List<String> selectedTags;
final bool useGlobalSettingsFiltering; final bool useGlobalSettingsFiltering;
@ -50,13 +47,17 @@ class ClientForm extends StatelessWidget {
final void Function(bool) updateIgnoreClientQueryLog; final void Function(bool) updateIgnoreClientQueryLog;
final bool ignoreClientStatistics; final bool ignoreClientStatistics;
final void Function(bool) updateIgnoreClientStatistics; final void Function(bool) updateIgnoreClientStatistics;
final bool enableDnsCache;
final void Function(bool) updateEnableDnsCache;
final TextEditingController dnsCacheField;
final String? dnsCacheError;
final void Function(String?) updateDnsCacheError;
const ClientForm({ const ClientForm({
super.key, super.key,
required this.isFullScreen, required this.isFullScreen,
required this.client, required this.client,
required this.nameController, required this.nameController,
required this.updateValidValues,
required this.identifiersControllers, required this.identifiersControllers,
required this.selectedTags, required this.selectedTags,
required this.useGlobalSettingsFiltering, required this.useGlobalSettingsFiltering,
@ -84,26 +85,24 @@ class ClientForm extends StatelessWidget {
required this.ignoreClientStatistics, required this.ignoreClientStatistics,
required this.updateIgnoreClientQueryLog, required this.updateIgnoreClientQueryLog,
required this.updateIgnoreClientStatistics, required this.updateIgnoreClientStatistics,
required this.enableDnsCache,
required this.updateEnableDnsCache,
required this.dnsCacheField,
required this.dnsCacheError,
required this.updateDnsCacheError,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return Column(
padding: const EdgeInsets.only(top: 0),
children: [ children: [
if (isFullScreen == true) const SizedBox(height: 24), const SizedBox(height: 8),
if (isFullScreen == false) const SizedBox(height: 6),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField( child: TextFormField(
enabled: client != null ? false : true, enabled: client != null ? false : true,
controller: nameController, controller: nameController,
onChanged: (_) => updateValidValues( onChanged: (_) => {},
checkValidValues(
identifiersControllers: identifiersControllers,
nameController: nameController
)
),
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge_rounded), prefixIcon: const Icon(Icons.badge_rounded),
border: const OutlineInputBorder( border: const OutlineInputBorder(
@ -117,7 +116,7 @@ class ClientForm extends StatelessWidget {
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.tags, label: AppLocalizations.of(context)!.tags,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
TagsSection( TagsSection(
selectedTags: selectedTags, selectedTags: selectedTags,
@ -127,28 +126,17 @@ class ClientForm extends StatelessWidget {
identifiersControllers: identifiersControllers, identifiersControllers: identifiersControllers,
onUpdateIdentifiersControllers: (c) { onUpdateIdentifiersControllers: (c) {
updateIdentifiersControllers(c); updateIdentifiersControllers(c);
updateValidValues(
checkValidValues(
nameController: nameController,
identifiersControllers: identifiersControllers
)
);
}, },
onCheckValidValues: () => updateValidValues( onCheckValidValues: () => {}
checkValidValues(
identifiersControllers: identifiersControllers,
nameController: nameController
)
),
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.settings, label: AppLocalizations.of(context)!.settings,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 24, right: 24, top: 12, bottom: 24 left: 16, right: 16, top: 12, bottom: 24
) )
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Material( child: Material(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
@ -157,8 +145,8 @@ class ClientForm extends StatelessWidget {
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 16,
vertical: 5 vertical: 6
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -182,7 +170,7 @@ class ClientForm extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
SettingsTile( SettingsTile(
label: AppLocalizations.of(context)!.enableFiltering, label: AppLocalizations.of(context)!.enableFiltering,
value: enableFiltering, value: enableFiltering,
@ -204,7 +192,7 @@ class ClientForm extends StatelessWidget {
CustomListTile( CustomListTile(
title: AppLocalizations.of(context)!.safeSearch, title: AppLocalizations.of(context)!.safeSearch,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 42, horizontal: 34,
vertical: 16 vertical: 16
), ),
trailing: Padding( trailing: Padding(
@ -228,15 +216,15 @@ class ClientForm extends StatelessWidget {
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.queryLogsAndStatistics, label: AppLocalizations.of(context)!.queryLogsAndStatistics,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
CustomSwitchListTile( CustomSwitchListTile(
title: AppLocalizations.of(context)!.ignoreClientQueryLog, title: AppLocalizations.of(context)!.ignoreClientQueryLog,
value: ignoreClientQueryLog, value: ignoreClientQueryLog,
onChanged: updateIgnoreClientQueryLog, onChanged: updateIgnoreClientQueryLog,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 16,
vertical: 4 vertical: 6
), ),
), ),
CustomSwitchListTile( CustomSwitchListTile(
@ -244,13 +232,13 @@ class ClientForm extends StatelessWidget {
value: ignoreClientStatistics, value: ignoreClientStatistics,
onChanged: updateIgnoreClientStatistics, onChanged: updateIgnoreClientStatistics,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 16,
vertical: 4 vertical: 6
), ),
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.blockedServices, label: AppLocalizations.of(context)!.blockedServices,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
BlockedServicesSection( BlockedServicesSection(
useGlobalSettingsServices: useGlobalSettingsServices, useGlobalSettingsServices: useGlobalSettingsServices,
@ -260,15 +248,40 @@ class ClientForm extends StatelessWidget {
), ),
UpstreamServersSection( UpstreamServersSection(
upstreamServers: upstreamServers, upstreamServers: upstreamServers,
onCheckValidValues: () => updateValidValues( onCheckValidValues: () => {},
checkValidValues(
identifiersControllers: identifiersControllers,
nameController: nameController
)
),
onUpdateUpstreamServers: updateUpstreamServers onUpdateUpstreamServers: updateUpstreamServers
), ),
SizedBox(height: Platform.isIOS ? 48 : 24) SectionLabel(
label: AppLocalizations.of(context)!.upstreamDnsCacheConfiguration,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
),
CustomSwitchListTile(
title: AppLocalizations.of(context)!.enableDnsCachingClient,
value: enableDnsCache,
onChanged: updateEnableDnsCache,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 6
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: TextFormField(
controller: dnsCacheField,
onChanged: (v) => updateDnsCacheError(!validateNumber(v) ? AppLocalizations.of(context)!.invalidValue : null),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.storage_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.dnsCacheSize,
errorText: dnsCacheError
),
keyboardType: TextInputType.number,
),
),
], ],
); );
} }

View file

@ -39,6 +39,8 @@ class ClientScreen extends StatefulWidget {
} }
class _ClientScreenState extends State<ClientScreen> { class _ClientScreenState extends State<ClientScreen> {
final _scrollController = ScrollController();
final Uuid uuid = const Uuid(); final Uuid uuid = const Uuid();
bool validValues = false; bool validValues = false;
@ -76,6 +78,15 @@ class _ClientScreenState extends State<ClientScreen> {
bool _ignoreClientQueryLog = false; bool _ignoreClientQueryLog = false;
bool _ignoreClientStatistics = false; bool _ignoreClientStatistics = false;
bool _enableDnsCache = false;
final _dnsCacheField = TextEditingController();
String? _dnsCacheError;
// VALIDATIONS
bool _nameValid = true;
bool _identifiersValid = true;
bool _dnsCacheValid = true;
void enableDisableGlobalSettingsFiltering() { void enableDisableGlobalSettingsFiltering() {
if (useGlobalSettingsFiltering == true) { if (useGlobalSettingsFiltering == true) {
setState(() { setState(() {
@ -125,6 +136,10 @@ class _ClientScreenState extends State<ClientScreen> {
)).toList(); )).toList();
_ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false; _ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false;
_ignoreClientStatistics = widget.client!.ignoreStatistics ?? false; _ignoreClientStatistics = widget.client!.ignoreStatistics ?? false;
_enableDnsCache = widget.client!.upstreamsCacheEnabled ?? false;
_dnsCacheField.text = widget.client!.upstreamsCacheSize != null
? widget.client!.upstreamsCacheSize.toString()
: "";
} }
super.initState(); super.initState();
} }
@ -147,20 +162,37 @@ class _ClientScreenState extends State<ClientScreen> {
upstreams: List<String>.from(upstreamServers.map((e) => e.controller.text)), upstreams: List<String>.from(upstreamServers.map((e) => e.controller.text)),
tags: selectedTags, tags: selectedTags,
ignoreQuerylog: _ignoreClientQueryLog, ignoreQuerylog: _ignoreClientQueryLog,
ignoreStatistics: _ignoreClientStatistics ignoreStatistics: _ignoreClientStatistics,
upstreamsCacheEnabled: _enableDnsCache,
upstreamsCacheSize: _dnsCacheField.text != ""
? int.parse(_dnsCacheField.text)
: null
); );
widget.onConfirm(client); widget.onConfirm(client);
} }
void validateValues() {
_nameValid = nameController.text != '';
_identifiersValid = identifiersControllers.isNotEmpty && identifiersControllers[0].controller.text != '';
_dnsCacheValid = (_dnsCacheField.text == "" || _dnsCacheField.text != "" && RegExp(r'^\d+$').hasMatch(_dnsCacheField.text));
if (_nameValid && _identifiersValid && _dnsCacheValid) {
createClient();
Navigator.pop(context);
}
else {
_scrollController.animateTo(
0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500)
);
setState(() => {});
}
}
List<Widget> actions() { List<Widget> actions() {
return [ return [
IconButton( IconButton(
onPressed: validValues == true onPressed: validateValues,
? () {
createClient();
Navigator.pop(context);
}
: null,
icon: const Icon(Icons.save_rounded), icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save, tooltip: AppLocalizations.of(context)!.save,
), ),
@ -193,11 +225,18 @@ class _ClientScreenState extends State<ClientScreen> {
actions: actions(), actions: actions(),
), ),
body: SafeArea( body: SafeArea(
child: ClientForm( child: ListView(
controller: _scrollController,
children: [
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
nameValid: _nameValid,
identifiersValid: _identifiersValid,
dnsCacheValid: _dnsCacheValid
),
ClientForm(
isFullScreen: true, isFullScreen: true,
client: widget.client, client: widget.client,
nameController: nameController, nameController: nameController,
updateValidValues: (v) => setState(() => validValues = v),
identifiersControllers: identifiersControllers, identifiersControllers: identifiersControllers,
selectedTags: selectedTags, selectedTags: selectedTags,
useGlobalSettingsFiltering: useGlobalSettingsFiltering, useGlobalSettingsFiltering: useGlobalSettingsFiltering,
@ -225,6 +264,13 @@ class _ClientScreenState extends State<ClientScreen> {
ignoreClientStatistics: _ignoreClientStatistics, ignoreClientStatistics: _ignoreClientStatistics,
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
enableDnsCache: _enableDnsCache,
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
dnsCacheField: _dnsCacheField,
dnsCacheError: _dnsCacheError,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v)
),
],
), ),
), ),
), ),
@ -264,11 +310,18 @@ class _ClientScreenState extends State<ClientScreen> {
), ),
), ),
Flexible( Flexible(
child: ClientForm( child: ListView(
controller: _scrollController,
children: [
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
nameValid: _nameValid,
identifiersValid: _identifiersValid,
dnsCacheValid: _dnsCacheValid
),
ClientForm(
isFullScreen: false, isFullScreen: false,
client: widget.client, client: widget.client,
nameController: nameController, nameController: nameController,
updateValidValues: (v) => setState(() => validValues = v),
identifiersControllers: identifiersControllers, identifiersControllers: identifiersControllers,
selectedTags: selectedTags, selectedTags: selectedTags,
useGlobalSettingsFiltering: useGlobalSettingsFiltering, useGlobalSettingsFiltering: useGlobalSettingsFiltering,
@ -296,6 +349,13 @@ class _ClientScreenState extends State<ClientScreen> {
ignoreClientStatistics: _ignoreClientStatistics, ignoreClientStatistics: _ignoreClientStatistics,
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
enableDnsCache: _enableDnsCache,
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
dnsCacheField: _dnsCacheField,
dnsCacheError: _dnsCacheError,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v)
),
],
), ),
) )
], ],
@ -306,3 +366,56 @@ class _ClientScreenState extends State<ClientScreen> {
} }
} }
class _Errors extends StatelessWidget {
final bool nameValid;
final bool identifiersValid;
final bool dnsCacheValid;
const _Errors({
required this.nameValid,
required this.identifiersValid,
required this.dnsCacheValid,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 0,
color: Colors.red.withOpacity(0.2),
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.errors,
style: const TextStyle(
fontSize: 18
),
),
const SizedBox(height: 8),
if (!nameValid) Text(
"${AppLocalizations.of(context)!.nameInvalid}",
style: const TextStyle(
fontSize: 14
),
),
if (!identifiersValid) Text(
"${AppLocalizations.of(context)!.oneIdentifierRequired}",
style: const TextStyle(
fontSize: 14
),
),
if (!dnsCacheValid) Text(
"${AppLocalizations.of(context)!.dnsCacheNumber}",
style: const TextStyle(
fontSize: 14
),
),
],
),
),
);
}
}

View file

@ -74,22 +74,6 @@ void openSafeSearchModal({
); );
} }
bool checkValidValues({
required TextEditingController nameController,
required List<ControllerListItem> identifiersControllers
}) {
if (
nameController.text != '' &&
identifiersControllers.isNotEmpty &&
identifiersControllers[0].controller.text != ''
) {
return true;
}
else {
return false;
}
}
void openClientFormModal({ void openClientFormModal({
required BuildContext context, required BuildContext context,
required double width, required double width,
@ -124,3 +108,9 @@ void openClientFormModal({
), ),
); );
} }
bool validateNumber(String value) {
if (value == "") return true;
final regexp = RegExp(r'^\d+$');
return regexp.hasMatch(value);
}

View file

@ -11,11 +11,11 @@ class IdentifiersSection extends StatefulWidget {
final void Function() onCheckValidValues; final void Function() onCheckValidValues;
const IdentifiersSection({ const IdentifiersSection({
Key? key, super.key,
required this.identifiersControllers, required this.identifiersControllers,
required this.onUpdateIdentifiersControllers, required this.onUpdateIdentifiersControllers,
required this.onCheckValidValues required this.onCheckValidValues
}) : super(key: key); });
@override @override
State<IdentifiersSection> createState() => _IdentifiersSectionState(); State<IdentifiersSection> createState() => _IdentifiersSectionState();
@ -34,11 +34,11 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.identifiers, label: AppLocalizations.of(context)!.identifiers,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 24, right: 24, top: 24, bottom: 12 left: 16, right: 16, top: 24, bottom: 12
) )
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 20), padding: const EdgeInsets.only(right: 10),
child: IconButton( child: IconButton(
onPressed: () => widget.onUpdateIdentifiersControllers([ onPressed: () => widget.onUpdateIdentifiersControllers([
...widget.identifiersControllers, ...widget.identifiersControllers,
@ -54,7 +54,7 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
), ),
if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 12, bottom: 12, left: 24, right: 20 top: 12, bottom: 12, left: 16, right: 10
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -75,9 +75,9 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
), ),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 12),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 25), padding: const EdgeInsets.only(bottom: 24),
child: IconButton( child: IconButton(
onPressed: () => widget.onUpdateIdentifiersControllers( onPressed: () => widget.onUpdateIdentifiersControllers(
widget.identifiersControllers.where((e) => e.id != controller.id).toList() widget.identifiersControllers.where((e) => e.id != controller.id).toList()
@ -87,7 +87,7 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
) )
], ],
), ),
)).toList(), )),
if (widget.identifiersControllers.isEmpty) Container( if (widget.identifiersControllers.isEmpty) Container(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Text( child: Text(

View file

@ -7,12 +7,12 @@ class SettingsTile extends StatelessWidget {
final bool useGlobalSettingsFiltering; final bool useGlobalSettingsFiltering;
const SettingsTile({ const SettingsTile({
Key? key, super.key,
required this.label, required this.label,
required this.value, required this.value,
this.onChange, this.onChange,
required this.useGlobalSettingsFiltering required this.useGlobalSettingsFiltering
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,10 +23,7 @@ class SettingsTile extends StatelessWidget {
? value != null ? () => onChange!(!value!) : null ? value != null ? () => onChange!(!value!) : null
: null, : null,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6),
horizontal: 42,
vertical: 5
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View file

@ -7,10 +7,10 @@ class TagsSection extends StatelessWidget {
final void Function(List<String>) onTagsSelected; final void Function(List<String>) onTagsSelected;
const TagsSection({ const TagsSection({
Key? key, super.key,
required this.selectedTags, required this.selectedTags,
required this.onTagsSelected required this.onTagsSelected
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -24,7 +24,7 @@ class TagsSection extends StatelessWidget {
) , ) ,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 0, horizontal: 24 vertical: 0, horizontal: 16
), ),
child: Row( child: Row(
children: [ children: [

View file

@ -11,11 +11,11 @@ class UpstreamServersSection extends StatefulWidget {
final void Function(List<ControllerListItem>) onUpdateUpstreamServers; final void Function(List<ControllerListItem>) onUpdateUpstreamServers;
const UpstreamServersSection({ const UpstreamServersSection({
Key? key, super.key,
required this.upstreamServers, required this.upstreamServers,
required this.onCheckValidValues, required this.onCheckValidValues,
required this.onUpdateUpstreamServers required this.onUpdateUpstreamServers
}) : super(key: key); });
@override @override
State<UpstreamServersSection> createState() => _UpstreamServersSectionState(); State<UpstreamServersSection> createState() => _UpstreamServersSectionState();
@ -33,10 +33,10 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
children: [ children: [
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.upstreamServers, label: AppLocalizations.of(context)!.upstreamServers,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 20), padding: const EdgeInsets.only(right: 12),
child: IconButton( child: IconButton(
onPressed: () => setState(() => widget.upstreamServers.add( onPressed: () => setState(() => widget.upstreamServers.add(
ControllerListItem( ControllerListItem(
@ -50,7 +50,7 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
], ],
), ),
if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding( if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.only(left: 16, right: 12),
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.only(bottom: 20),
child: Row( child: Row(
@ -71,7 +71,7 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
), ),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 12),
IconButton( IconButton(
onPressed: () => widget.onUpdateUpstreamServers( onPressed: () => widget.onUpdateUpstreamServers(
widget.upstreamServers.where((e) => e.id != controller.id).toList() widget.upstreamServers.where((e) => e.id != controller.id).toList()
@ -81,7 +81,7 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
], ],
), ),
), ),
)).toList(), )),
if (widget.upstreamServers.isEmpty) Container( if (widget.upstreamServers.isEmpty) Container(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Column( child: Column(