adguard-home-manager/lib/screens/clients/client_modal.dart

706 lines
28 KiB
Dart
Raw Normal View History

2022-10-06 02:09:14 +02:00
import 'package:flutter/material.dart';
2022-10-06 23:19:51 +02:00
import 'package:provider/provider.dart';
2022-10-06 02:09:14 +02:00
import 'package:uuid/uuid.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
2022-10-08 23:04:28 +02:00
import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart';
import 'package:adguard_home_manager/screens/clients/services_modal.dart';
2022-10-06 23:19:51 +02:00
import 'package:adguard_home_manager/screens/clients/tags_modal.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
2022-10-07 16:10:27 +02:00
import 'package:adguard_home_manager/models/clients.dart';
2022-10-06 02:09:14 +02:00
2022-10-07 17:08:23 +02:00
class ClientModal extends StatefulWidget {
2022-10-09 17:45:18 +02:00
final ScrollController scrollController;
2022-10-07 17:08:23 +02:00
final Client? client;
2022-10-07 16:10:27 +02:00
final void Function(Client) onConfirm;
2022-10-07 18:16:22 +02:00
final void Function(Client)? onDelete;
2022-10-06 02:09:14 +02:00
2022-10-07 17:08:23 +02:00
const ClientModal({
2022-10-06 02:09:14 +02:00
Key? key,
2022-10-09 17:45:18 +02:00
required this.scrollController,
2022-10-07 17:08:23 +02:00
this.client,
2022-10-07 18:16:22 +02:00
required this.onConfirm,
this.onDelete,
2022-10-06 02:09:14 +02:00
}) : super(key: key);
@override
2022-10-07 17:08:23 +02:00
State<ClientModal> createState() => _ClientModalState();
2022-10-06 02:09:14 +02:00
}
2022-10-07 17:08:23 +02:00
class _ClientModalState extends State<ClientModal> {
2022-10-06 02:09:14 +02:00
final Uuid uuid = const Uuid();
2022-10-07 17:08:23 +02:00
bool editMode = true;
2022-10-06 02:09:14 +02:00
TextEditingController nameController = TextEditingController();
List<String> selectedTags = [];
2022-10-06 23:19:51 +02:00
2022-10-06 02:09:14 +02:00
List<Map<dynamic, dynamic>> identifiersControllers = [
{
'id': 0,
'controller': TextEditingController()
}
];
bool useGlobalSettingsFiltering = true;
bool? enableFiltering;
bool? enableSafeBrowsing;
bool? enableParentalControl;
bool? enableSafeSearch;
bool useGlobalSettingsServices = true;
List<String> blockedServices = [];
List<Map<dynamic, dynamic>> upstreamServers = [];
2022-10-06 02:09:14 +02:00
bool checkValidValues() {
if (
nameController.text != '' &&
identifiersControllers.isNotEmpty &&
identifiersControllers[0]['controller'].text != ''
2022-10-06 02:09:14 +02:00
) {
return true;
}
else {
return false;
}
}
2022-10-07 17:08:23 +02:00
@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;
2022-10-07 18:29:23 +02:00
upstreamServers = widget.client!.upstreams.map((e) => {
'id': uuid.v4(),
'controller': TextEditingController(text: e)
}).toList();
2022-10-07 17:08:23 +02:00
}
super.initState();
}
2022-10-06 02:09:14 +02:00
@override
Widget build(BuildContext context) {
2022-10-06 23:19:51 +02:00
final serversProvider = Provider.of<ServersProvider>(context);
2022-10-06 02:09:14 +02:00
void createClient() {
2022-10-07 16:10:27 +02:00
final Client client = Client(
name: nameController.text,
ids: List<String>.from(identifiersControllers.map((e) => e['controller'].text)),
useGlobalSettings: useGlobalSettingsFiltering,
filteringEnabled: enableFiltering ?? false,
parentalEnabled: enableParentalControl ?? false,
safebrowsingEnabled: enableSafeBrowsing ?? false,
safesearchEnabled: enableSafeSearch ?? false,
useGlobalBlockedServices: useGlobalSettingsServices,
blockedServices: blockedServices,
upstreams: List<String>.from(upstreamServers.map((e) => e['controller'].text)),
tags: selectedTags
);
widget.onConfirm(client);
2022-10-06 02:09:14 +02:00
}
Widget sectionLabel(String label) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 30,
horizontal: 20
),
child: Text(
label,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
color: Theme.of(context).primaryColor
),
),
);
}
void enableDisableGlobalSettingsFiltering() {
if (useGlobalSettingsFiltering == true) {
setState(() {
useGlobalSettingsFiltering = false;
enableFiltering = false;
enableSafeBrowsing = false;
enableParentalControl = false;
enableSafeSearch = false;
});
}
else if (useGlobalSettingsFiltering == false) {
setState(() {
useGlobalSettingsFiltering = true;
enableFiltering = null;
enableSafeBrowsing = null;
enableParentalControl = null;
enableSafeSearch = null;
});
}
}
2022-10-06 23:19:51 +02:00
void openTagsModal() {
showDialog(
context: context,
builder: (context) => TagsModal(
selectedTags: selectedTags,
2022-10-06 23:19:51 +02:00
tags: serversProvider.clients.data!.supportedTags,
onConfirm: (selected) => setState(() => selectedTags = selected),
)
);
}
void openServicesModal() {
showDialog(
context: context,
builder: (context) => ServicesModal(
blockedServices: blockedServices,
onConfirm: (values) => setState(() => blockedServices = values),
2022-10-06 23:19:51 +02:00
)
);
}
void updateServicesGlobalSettings(bool value) {
if (value == true) {
setState(() {
blockedServices = [];
useGlobalSettingsServices = true;
});
}
else if (value == false) {
setState(() {
useGlobalSettingsServices = false;
});
}
}
2022-10-07 18:16:22 +02:00
void openDeleteClientModal() {
showDialog(
context: context,
2022-10-08 23:04:28 +02:00
builder: (ctx) => RemoveClientModal(
2022-10-07 18:16:22 +02:00
onConfirm: () {
Navigator.pop(context);
widget.onDelete!(widget.client!);
}
)
);
}
2022-10-06 02:09:14 +02:00
Widget settignsTile({
required String label,
required bool? value,
2022-10-07 17:08:23 +02:00
void Function(bool)? onChange
2022-10-06 02:09:14 +02:00
}) {
return Material(
color: Colors.transparent,
child: InkWell(
2022-10-07 17:08:23 +02:00
onTap: onChange != null
? value != null ? () => onChange(!value) : null
: null,
2022-10-06 02:09:14 +02:00
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 30,
vertical: 5
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 15
),
),
2022-10-07 17:08:23 +02:00
useGlobalSettingsFiltering == false
2022-10-06 02:09:14 +02:00
? Switch(
2022-10-07 17:08:23 +02:00
value: value!,
2022-10-06 02:09:14 +02:00
onChanged: onChange,
activeColor: Theme.of(context).primaryColor,
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
2022-10-06 02:09:14 +02:00
child: Text(
"Global",
style: TextStyle(
color: Theme.of(context).listTileTheme.iconColor,
2022-10-06 02:09:14 +02:00
),
),
)
],
),
),
),
);
}
return Padding(
padding: MediaQuery.of(context).viewInsets,
2022-10-09 17:45:18 +02:00
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).dialogBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28)
)
),
child: Column(
children: [
Expanded(
child: ListView(
controller: widget.scrollController,
children: [
const SizedBox(height: 28),
const Icon(
Icons.add,
size: 26,
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.addClient,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
enabled: widget.client != null ? false : true,
controller: nameController,
onChanged: (_) => checkValidValues(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
labelText: AppLocalizations.of(context)!.name,
2022-10-06 02:09:14 +02:00
),
),
2022-10-09 17:45:18 +02:00
),
sectionLabel(AppLocalizations.of(context)!.tags),
Material(
color: Colors.transparent,
child: InkWell(
onTap: editMode == true ? () => openTagsModal() : null,
2022-10-06 02:09:14 +02:00
child: Padding(
2022-10-09 17:45:18 +02:00
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 20
),
2022-10-06 02:09:14 +02:00
child: Row(
children: [
2022-10-21 11:37:16 +02:00
const Icon(
2022-10-09 17:45:18 +02:00
Icons.label_rounded,
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
const SizedBox(width: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
2022-10-06 02:09:14 +02:00
children: [
Text(
2022-10-09 17:45:18 +02:00
AppLocalizations.of(context)!.selectTags,
2022-10-06 02:09:14 +02:00
style: const TextStyle(
fontSize: 16,
),
),
2022-10-09 17:45:18 +02:00
const SizedBox(height: 5),
Text(
selectedTags.isNotEmpty
? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}"
: AppLocalizations.of(context)!.noTagsSelected,
style: TextStyle(
color: Theme.of(context).listTileTheme.iconColor,
2022-10-09 17:45:18 +02:00
),
2022-10-06 02:09:14 +02:00
)
],
2022-10-09 17:45:18 +02:00
)
],
2022-10-06 02:09:14 +02:00
),
),
),
2022-10-09 17:45:18 +02:00
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
sectionLabel(AppLocalizations.of(context)!.identifiers),
if (editMode == true) Padding(
padding: const EdgeInsets.only(right: 20),
child: IconButton(
onPressed: () => setState(() => identifiersControllers.add({
'id': uuid.v4(),
'controller': TextEditingController()
})),
icon: const Icon(Icons.add)
),
)
],
),
if (identifiersControllers.isNotEmpty) ...identifiersControllers.map((controller) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 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(
prefixIcon: const Icon(Icons.tag),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
helperText: AppLocalizations.of(context)!.identifierHelper,
labelText: AppLocalizations.of(context)!.identifier,
),
),
),
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)
),
)
]
],
),
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
)).toList(),
if (identifiersControllers.isEmpty) Container(
padding: const EdgeInsets.only(top: 10),
child: Text(
AppLocalizations.of(context)!.noIdentifiers,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
color: Colors.grey
),
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
),
sectionLabel(AppLocalizations.of(context)!.settings),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Material(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
child: InkWell(
onTap: editMode
? () => enableDisableGlobalSettingsFiltering()
: null,
2022-10-06 02:09:14 +02:00
borderRadius: BorderRadius.circular(28),
2022-10-09 17:45:18 +02:00
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.useGlobalSettings,
style: const TextStyle(
fontSize: 16,
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
),
Switch(
value: useGlobalSettingsFiltering,
onChanged: editMode == true
? (value) => enableDisableGlobalSettingsFiltering()
: null,
activeColor: Theme.of(context).primaryColor,
)
],
2022-10-06 02:09:14 +02:00
),
),
),
),
2022-10-09 17:45:18 +02:00
),
const SizedBox(height: 10),
settignsTile(
label: AppLocalizations.of(context)!.enableFiltering,
value: enableFiltering,
onChange: editMode == true
? (value) => setState(() => enableFiltering = value)
: null
),
settignsTile(
label: AppLocalizations.of(context)!.enableSafeBrowsing,
value: enableSafeBrowsing,
onChange: editMode == true
? (value) => setState(() => enableSafeBrowsing = value)
: null
),
settignsTile(
label: AppLocalizations.of(context)!.enableParentalControl,
value: enableParentalControl,
onChange: editMode == true
? (value) => setState(() => enableParentalControl = value)
: null
),
settignsTile(
label: AppLocalizations.of(context)!.enableSafeSearch,
value: enableSafeSearch,
onChange: editMode == true
? (value) => setState(() => enableSafeSearch = value)
: null
),
sectionLabel(AppLocalizations.of(context)!.blockedServices),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Material(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
2022-10-06 02:09:14 +02:00
child: InkWell(
2022-10-07 17:08:23 +02:00
onTap: editMode == true
2022-10-09 17:45:18 +02:00
? () => updateServicesGlobalSettings(!useGlobalSettingsServices)
2022-10-06 02:09:14 +02:00
: null,
2022-10-09 17:45:18 +02:00
borderRadius: BorderRadius.circular(28),
2022-10-06 02:09:14 +02:00
child: Padding(
padding: const EdgeInsets.symmetric(
2022-10-09 17:45:18 +02:00
horizontal: 20,
vertical: 5
2022-10-06 02:09:14 +02:00
),
child: Row(
2022-10-09 17:45:18 +02:00
mainAxisAlignment: MainAxisAlignment.spaceBetween,
2022-10-06 02:09:14 +02:00
children: [
2022-10-09 17:45:18 +02:00
Text(
AppLocalizations.of(context)!.useGlobalSettings,
style: const TextStyle(
fontSize: 16,
),
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
Switch(
value: useGlobalSettingsServices,
onChanged: editMode == true
? (value) => updateServicesGlobalSettings(value)
: null,
activeColor: Theme.of(context).primaryColor,
2022-10-06 02:09:14 +02:00
)
],
),
),
),
),
2022-10-09 17:45:18 +02:00
),
const SizedBox(height: 10),
Material(
color: Colors.transparent,
child: InkWell(
onTap: editMode == true
? useGlobalSettingsServices == false
? openServicesModal
: null
: null,
child: Padding(
2022-10-09 17:45:18 +02:00
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 20
),
child: Row(
children: [
Icon(
2022-10-09 17:45:18 +02:00
Icons.public,
color: useGlobalSettingsServices == false
2022-10-21 11:37:16 +02:00
? null
: Colors.grey,
2022-10-09 17:45:18 +02:00
),
const SizedBox(width: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.selectBlockedServices,
style: TextStyle(
fontSize: 16,
color: useGlobalSettingsServices == false
? null
: Theme.of(context).listTileTheme.iconColor,
),
),
2022-10-09 17:45:18 +02:00
if (useGlobalSettingsServices == false) ...[
const SizedBox(height: 5),
Text(
blockedServices.isNotEmpty
? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}"
: AppLocalizations.of(context)!.noBlockedServicesSelected,
style: TextStyle(
color: Theme.of(context).listTileTheme.iconColor,
2022-10-09 17:45:18 +02:00
),
)
]
],
)
],
),
),
2022-10-09 17:45:18 +02:00
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
sectionLabel(AppLocalizations.of(context)!.upstreamServers),
if (editMode == true) Padding(
padding: const EdgeInsets.only(right: 20),
child: IconButton(
onPressed: () => setState(() => upstreamServers.add({
'id': uuid.v4(),
'controller': TextEditingController()
})),
icon: const Icon(Icons.add)
),
)
],
),
if (upstreamServers.isNotEmpty) ...upstreamServers.map((controller) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
2022-10-09 17:45:18 +02:00
SizedBox(
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(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.serverAddress,
),
),
),
2022-10-09 17:45:18 +02:00
if (editMode == true) ...[
const SizedBox(width: 20),
IconButton(
onPressed: () => setState(
() => upstreamServers = upstreamServers.where((e) => e['id'] != controller['id']).toList()
),
icon: const Icon(Icons.remove_circle_outline_outlined)
)
]
],
),
),
2022-10-09 17:45:18 +02:00
)).toList(),
if (upstreamServers.isEmpty) Container(
padding: const EdgeInsets.only(top: 10),
child: Column(
2022-10-07 18:16:22 +02:00
children: [
2022-10-09 17:45:18 +02:00
Text(
AppLocalizations.of(context)!.noUpstreamServers,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
color: Colors.grey
),
),
const SizedBox(height: 10),
Text(
AppLocalizations.of(context)!.willBeUsedGeneralServers,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 15,
color: Colors.grey
2022-10-07 18:16:22 +02:00
),
),
],
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
),
],
),
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (widget.client != null && editMode == false) ...[
IconButton(
onPressed: () => setState(() => editMode = true),
icon: const Icon(Icons.edit)
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
const SizedBox(width: 10),
2022-10-07 17:08:23 +02:00
],
2022-10-09 17:45:18 +02:00
if (widget.client != null && widget.onDelete != null) IconButton(
onPressed: openDeleteClientModal,
icon: const Icon(Icons.delete)
),
],
),
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
),
)
),
]
],
)
],
2022-10-06 02:09:14 +02:00
),
2022-10-09 17:45:18 +02:00
),
],
2022-10-06 02:09:14 +02:00
),
),
);
}
}