Added add client to lists

This commit is contained in:
Juan Gilsanz Polo 2022-09-29 23:12:24 +02:00
parent 9f248d18a1
commit 6b51102dd2
8 changed files with 350 additions and 4 deletions

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:adguard_home_manager/screens/connect/fab.dart'; import 'package:adguard_home_manager/screens/connect/fab.dart';
import 'package:adguard_home_manager/screens/home/appbar.dart'; import 'package:adguard_home_manager/screens/home/appbar.dart';
import 'package:adguard_home_manager/screens/connect/appbar.dart'; import 'package:adguard_home_manager/screens/connect/appbar.dart';
import 'package:adguard_home_manager/screens/clients/fab.dart';
import 'package:adguard_home_manager/screens/connect/connect.dart'; import 'package:adguard_home_manager/screens/connect/connect.dart';
import 'package:adguard_home_manager/screens/home/home.dart'; import 'package:adguard_home_manager/screens/home/home.dart';
import 'package:adguard_home_manager/screens/clients/clients.dart'; import 'package:adguard_home_manager/screens/clients/clients.dart';
@ -40,6 +41,7 @@ List<AppScreen> screensServerConnected = [
name: "clients", name: "clients",
icon: Icons.devices, icon: Icons.devices,
body: Clients(), body: Clients(),
fab: ClientsFab()
), ),
const AppScreen( const AppScreen(
name: "settings", name: "settings",

View file

@ -87,5 +87,10 @@
"removeClientMessage": "Are you sure you want to remove this client from the list?", "removeClientMessage": "Are you sure you want to remove this client from the list?",
"confirm": "Confirm", "confirm": "Confirm",
"removingClient": "Removing client...", "removingClient": "Removing client...",
"clientNotRemoved": "Client could not be removed from the list" "clientNotRemoved": "Client could not be removed from the list",
"addClient": "Add client",
"list": "List",
"ipAddress": "IP address",
"ipNotValid": "IP address not valid",
"clientAddedSuccessfully": "Client added to the list successfully"
} }

View file

@ -87,5 +87,10 @@
"removeClientMessage": "Estás seguro que deseas eliminar este cliente de la lista?", "removeClientMessage": "Estás seguro que deseas eliminar este cliente de la lista?",
"confirm": "Confirmar", "confirm": "Confirmar",
"removingClient": "Eliminando cliente...", "removingClient": "Eliminando cliente...",
"clientNotRemoved": "El cliente no pudo ser eliminado de la lista" "clientNotRemoved": "El cliente no pudo ser eliminado de la lista",
"addClient": "Agregar cliente",
"list": "Lista",
"ipAddress": "Dirección IP",
"ipNotValid": "Dirección IP no válida",
"clientAddedSuccessfully": "Cliente añadido a la lista satisfactoriamente"
} }

View file

@ -13,6 +13,8 @@ class AppConfigProvider with ChangeNotifier {
int _selectedTheme = 0; int _selectedTheme = 0;
int _selectedClientsTab = 0;
PackageInfo? get getAppInfo { PackageInfo? get getAppInfo {
return _appInfo; return _appInfo;
} }
@ -47,6 +49,9 @@ class AppConfigProvider with ChangeNotifier {
return _selectedTheme; return _selectedTheme;
} }
int get selectedClientsTab {
return _selectedClientsTab;
}
void setDbInstance(Database db) { void setDbInstance(Database db) {
_dbInstance = db; _dbInstance = db;
@ -64,6 +69,11 @@ class AppConfigProvider with ChangeNotifier {
_iosDeviceInfo = deviceInfo; _iosDeviceInfo = deviceInfo;
} }
void setSelectedClientsTab(int tab) {
_selectedClientsTab = tab;
notifyListeners();
}
Future<bool> setSelectedTheme(int value) async { Future<bool> setSelectedTheme(int value) async {
final updated = await _updateThemeDb(value); final updated = await _updateThemeDb(value);
if (updated == true) { if (updated == true) {

View file

@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class AddClientModal extends StatefulWidget {
final String list;
final void Function(String, String) onConfirm;
const AddClientModal({
Key? key,
required this.list,
required this.onConfirm
}) : super(key: key);
@override
State<AddClientModal> createState() => _AddClientModalState();
}
class _AddClientModalState extends State<AddClientModal> {
String list = '';
TextEditingController ipController = TextEditingController();
String? ipError;
@override
void initState() {
list = widget.list;
super.initState();
}
void validateIp(String value) {
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
if (ipAddress.hasMatch(value) == true) {
setState(() => ipError = null);
}
else {
setState(() => ipError = AppLocalizations.of(context)!.ipNotValid);
}
}
bool checkValidValues() {
if (
(list == 'allowed' ||
list == 'blocked') &&
ipController.text != '' &&
ipError == null
) {
return true;
}
else {
return false;
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Column(
children: [
const Icon(
Icons.add,
size: 26,
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.addClient,
)
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Material(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
bottomLeft: Radius.circular(15)
),
color: Colors.transparent,
child: InkWell(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
bottomLeft: Radius.circular(15)
),
onTap: () => setState(() => list = 'allowed'),
child: AnimatedContainer(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
bottomLeft: Radius.circular(15)
),
border: Border.all(
color: Theme.of(context).primaryColor
),
color: list == 'allowed'
? Theme.of(context).primaryColor
: Theme.of(context).primaryColor.withOpacity(0.05)
),
child: Text(
AppLocalizations.of(context)!.allowed,
style: TextStyle(
color: list == 'allowed'
? Colors.white
: null
),
),
),
),
),
Material(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(15),
bottomRight: Radius.circular(15)
),
color: Colors.transparent,
child: InkWell(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(15),
bottomRight: Radius.circular(15)
),
onTap: () => setState(() => list = 'blocked'),
child: AnimatedContainer(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(15),
bottomRight: Radius.circular(15)
),
border: Border.all(
color: Theme.of(context).primaryColor
),
color: list == 'blocked'
? Theme.of(context).primaryColor
: Theme.of(context).primaryColor.withOpacity(0.05)
),
child: Text(
AppLocalizations.of(context)!.blocked,
style: TextStyle(
color: list == 'blocked'
? Colors.white
: null
),
),
),
),
)
],
),
const SizedBox(height: 30),
TextFormField(
controller: ipController,
onChanged: validateIp,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
errorText: ipError,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: AppLocalizations.of(context)!.ipAddress,
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
),
TextButton(
onPressed: checkValidValues() == true
? () {
Navigator.pop(context);
widget.onConfirm(list, ipController.text);
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: checkValidValues() == true
? Theme.of(context).primaryColor
: Colors.grey
),
)
),
],
);
}
}

View file

@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/clients_list.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart';
import 'package:adguard_home_manager/screens/clients/blocked_allowed_list.dart'; import 'package:adguard_home_manager/screens/clients/blocked_allowed_list.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/models/server.dart'; import 'package:adguard_home_manager/models/server.dart';
import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/clients.dart';
@ -16,11 +17,13 @@ class Clients extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context); final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
return ClientsWidget( return ClientsWidget(
server: serversProvider.selectedServer!, server: serversProvider.selectedServer!,
setLoadingStatus: serversProvider.setClientsLoadStatus, setLoadingStatus: serversProvider.setClientsLoadStatus,
setClientsData: serversProvider.setClientsData, setClientsData: serversProvider.setClientsData,
setSelectedClientsTab: appConfigProvider.setSelectedClientsTab,
); );
} }
} }
@ -29,19 +32,23 @@ class ClientsWidget extends StatefulWidget {
final Server server; final Server server;
final void Function(int, bool) setLoadingStatus; final void Function(int, bool) setLoadingStatus;
final void Function(ClientsData) setClientsData; final void Function(ClientsData) setClientsData;
final void Function(int) setSelectedClientsTab;
const ClientsWidget({ const ClientsWidget({
Key? key, Key? key,
required this.server, required this.server,
required this.setLoadingStatus, required this.setLoadingStatus,
required this.setClientsData, required this.setClientsData,
required this.setSelectedClientsTab
}) : super(key: key); }) : super(key: key);
@override @override
State<ClientsWidget> createState() => _ClientsWidgetState(); State<ClientsWidget> createState() => _ClientsWidgetState();
} }
class _ClientsWidgetState extends State<ClientsWidget> { class _ClientsWidgetState extends State<ClientsWidget> with TickerProviderStateMixin {
late TabController tabController;
void fetchClients() async { void fetchClients() async {
widget.setLoadingStatus(0, false); widget.setLoadingStatus(0, false);
final result = await getClients(widget.server); final result = await getClients(widget.server);
@ -58,6 +65,12 @@ class _ClientsWidgetState extends State<ClientsWidget> {
void initState() { void initState() {
fetchClients(); fetchClients();
super.initState(); super.initState();
tabController = TabController(
initialIndex: 0,
length: 3,
vsync: this,
);
tabController.addListener(() => widget.setSelectedClientsTab(tabController.index));
} }
List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) { List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) {
@ -103,6 +116,7 @@ class _ClientsWidgetState extends State<ClientsWidget> {
floating: true, floating: true,
forceElevated: innerBoxIsScrolled, forceElevated: innerBoxIsScrolled,
bottom: TabBar( bottom: TabBar(
controller: tabController,
tabs: [ tabs: [
Tab( Tab(
icon: const Icon(Icons.devices), icon: const Icon(Icons.devices),
@ -132,6 +146,7 @@ class _ClientsWidgetState extends State<ClientsWidget> {
) )
), ),
child: TabBarView( child: TabBarView(
controller: tabController,
children: [ children: [
ClientsList( ClientsList(
data: serversProvider.clients.data!.autoClientsData, data: serversProvider.clients.data!.autoClientsData,

View file

@ -0,0 +1,101 @@
// 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/clients/add_client_modal.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/models/clients_allowed_blocked.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
class ClientsFab extends StatelessWidget {
const ClientsFab({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
void confirmRemoveDomain(String list, String ip) async {
Map<String, List<String>> body = {};
if (list == 'allowed') {
final List<String> clients = [...serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [], ip];
body = {
"allowed_clients": clients,
"disallowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.disallowedClients ?? [],
"blocked_hosts": serversProvider.clients.data!.clientsAllowedBlocked?.blockedHosts ?? [],
};
}
else if (list == 'blocked') {
final List<String> clients = [...serversProvider.clients.data!.clientsAllowedBlocked?.disallowedClients ?? [], ip];
body = {
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
"disallowed_clients": clients,
"blocked_hosts": serversProvider.clients.data!.clientsAllowedBlocked?.blockedHosts ?? [],
};
}
ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.removingClient);
final result = await requestAllowedBlockedClientsHosts(serversProvider.selectedServer!, body);
processModal.close();
if (result['result'] == 'success') {
serversProvider.setAllowedDisallowedClientsBlockedDomains(
ClientsAllowedBlocked(
allowedClients: body['allowed_clients'] ?? [],
disallowedClients: body['disallowed_clients'] ?? [],
blockedHosts: body['blocked_hosts'] ?? [],
)
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.clientAddedSuccessfully),
backgroundColor: Colors.green,
)
);
}
else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.clientNotRemoved),
backgroundColor: Colors.red,
)
);
}
}
void openAddClient(String list) {
showDialog(
context: context,
builder: (ctx) => AddClientModal(
list: list,
onConfirm: confirmRemoveDomain
)
);
}
if (appConfigProvider.selectedClientsTab == 1) {
return FloatingActionButton(
onPressed: () => openAddClient('allowed'),
child: const Icon(Icons.add),
);
}
else if (appConfigProvider.selectedClientsTab == 2) {
return FloatingActionButton(
onPressed: () => openAddClient('blocked'),
child: const Icon(Icons.add),
);
}
else {
return const SizedBox();
}
}
}

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/models/app_screen.dart'; import 'package:adguard_home_manager/models/app_screen.dart';
class BottomNavBar extends StatelessWidget { class BottomNavBar extends StatelessWidget {
@ -17,6 +19,8 @@ class BottomNavBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appConfigProvider = Provider.of<AppConfigProvider>(context);
String translatedName(String key) { String translatedName(String key) {
switch (key) { switch (key) {
case 'home': case 'home':
@ -42,7 +46,13 @@ class BottomNavBar extends StatelessWidget {
icon: Icon(screen.icon), icon: Icon(screen.icon),
label: translatedName(screen.name) label: translatedName(screen.name)
)).toList(), )).toList(),
onDestinationSelected: onSelect, onDestinationSelected: (value) {
// Reset clients tab to 0 when changing screen
if (value != 1) {
appConfigProvider.setSelectedClientsTab(0);
}
onSelect(value);
},
); );
} }
} }