From 0b0f38cd2e18b785890474965434e0b2300f135c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 27 Jan 2024 23:08:04 +0100 Subject: [PATCH] Changed filter clients modal design --- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 3 +- lib/providers/logs_provider.dart | 6 +- .../filters/details/list_details_screen.dart | 4 +- lib/screens/logs/filters/clients_modal.dart | 262 ++++++++++-------- .../logs/filters/logs_filters_modal.dart | 5 +- .../settings/dhcp/select_interface_modal.dart | 79 +----- lib/widgets/list_bottom_sheet.dart | 80 ++++++ 8 files changed, 253 insertions(+), 189 deletions(-) create mode 100644 lib/widgets/list_bottom_sheet.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4b92566..460e98d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -747,5 +747,6 @@ "selectEndTime": "Select end time", "startTimeBeforeEndTime": "Start time must be before end time.", "noBlockingScheduleThisDevice": "There's no blocking schedule for this device.", - "selectTimezone": "Select a timezone" + "selectTimezone": "Select a timezone", + "selectClientsFiltersInfo": "Select the clients you want to display. If no clients are selected, all will be displayed." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ff5bb7d..c730a7c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -747,5 +747,6 @@ "selectEndTime": "Seleccionar hora de fin", "startTimeBeforeEndTime": "La hora de inicio debe ser anterior a la hora de fin.", "noBlockingScheduleThisDevice": "No hay programación de bloqueo para este dispositivo.", - "selectTimezone": "Selecciona una zona horaria" + "selectTimezone": "Selecciona una zona horaria", + "selectClientsFiltersInfo": "Selecciona los clientes que quieres mostrar. Si no hay clientes seleccionados, se mostrarán todos." } \ No newline at end of file diff --git a/lib/providers/logs_provider.dart b/lib/providers/logs_provider.dart index d9bc55f..63e81cd 100644 --- a/lib/providers/logs_provider.dart +++ b/lib/providers/logs_provider.dart @@ -20,7 +20,7 @@ class LogsProvider with ChangeNotifier { DateTime? _logsOlderThan; String _selectedResultStatus = 'all'; String? _searchText; - List? _selectedClients; + List _selectedClients = []; int _logsQuantity = 100; int _offset = 0; @@ -65,7 +65,7 @@ class LogsProvider with ChangeNotifier { return _offset; } - List? get selectedClients { + List get selectedClients { return _selectedClients; } @@ -131,7 +131,7 @@ class LogsProvider with ChangeNotifier { } void setSelectedClients(List? clients) { - _selectedClients = clients; + _selectedClients = clients ?? []; notifyListeners(); } diff --git a/lib/screens/filters/details/list_details_screen.dart b/lib/screens/filters/details/list_details_screen.dart index 07d3aa0..95da744 100644 --- a/lib/screens/filters/details/list_details_screen.dart +++ b/lib/screens/filters/details/list_details_screen.dart @@ -64,8 +64,6 @@ class _ListDetailsScreenState extends State { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - Filter? list; try { list = filteringProvider.filtering != null @@ -322,7 +320,7 @@ class _Content extends StatelessWidget { ) : null, trailing: IconButton( - onPressed: () => openUrl(list!.url), + onPressed: () => openUrl(list.url), icon: const Icon(Icons.open_in_browser_rounded), tooltip: AppLocalizations.of(context)!.openListUrl, ), diff --git a/lib/screens/logs/filters/clients_modal.dart b/lib/screens/logs/filters/clients_modal.dart index 84728f8..cf8a94f 100644 --- a/lib/screens/logs/filters/clients_modal.dart +++ b/lib/screens/logs/filters/clients_modal.dart @@ -4,10 +4,13 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart'; +import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart'; + import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -class ClientsModal extends StatefulWidget { +class ClientsModal extends StatelessWidget { final List? value; final bool dialog; @@ -17,56 +20,90 @@ class ClientsModal extends StatefulWidget { required this.dialog }); - @override - State createState() => _ClientsModalState(); -} - -class _ClientsModalState extends State { - List selectedClients = []; - - @override - void initState() { - setState(() => selectedClients = widget.value ?? []); - super.initState(); - } - @override Widget build(BuildContext context) { - final height = MediaQuery.of(context).size.height; + final clientsProvider = Provider.of(context); + final logsProvider = Provider.of(context); - if (widget.dialog == true) { + if (dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 500 ), child: _ModalContent( - selectedClients: selectedClients, - onClientsSelected: (v) => setState(() => selectedClients = v), + selectedClients: logsProvider.selectedClients, + onClientsSelected: (v) => logsProvider.setSelectedClients(v), ) ), ); } else { - return ConstrainedBox( - constraints: BoxConstraints( - maxHeight: height-50 - ), - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) + return ListBottomSheet( + icon: Icons.smartphone_rounded, + title: AppLocalizations.of(context)!.clients, + children: [ + Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Flexible( + child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo) + ) + ], + ), ), - color: Theme.of(context).dialogBackgroundColor ), - child: SafeArea( - child: _ModalContent( - selectedClients: selectedClients, - onClientsSelected: (v) => setState(() => selectedClients = v), + CustomCheckboxListTile( + padding: const EdgeInsets.only( + left: 24, + top: 8, + right: 12, + bottom: 8 ), + value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length, + onChanged: (v) { + if (v == true) { + logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList()); + } + else { + logsProvider.setSelectedClients([]); + } + }, + title: AppLocalizations.of(context)!.selectAll + ), + ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: clientsProvider.clients!.autoClients.length, + itemBuilder: (context, index) => _ListItem( + label: clientsProvider.clients!.autoClients[index].ip, + checkboxActive: logsProvider.selectedClients.contains(clientsProvider.clients!.autoClients[index].ip), + onChanged: (isSelected) { + if (isSelected == true) { + logsProvider.setSelectedClients([ + ...logsProvider.selectedClients, + clientsProvider.clients!.autoClients[index].ip + ]); + } + else { + logsProvider.setSelectedClients( + logsProvider.selectedClients.where( + (item) => item != clientsProvider.clients!.autoClients[index].ip + ).toList() + ); + } + } + ) ) - ), + ] ); } } @@ -86,97 +123,92 @@ class _ModalContent extends StatelessWidget { final clientsProvider = Provider.of(context); final logsProvider = Provider.of(context); - void apply() async { - logsProvider.setSelectedClients( - selectedClients.isNotEmpty ? selectedClients : null - ); - - Navigator.pop(context); - } - - void selectAll() { - onClientsSelected( - clientsProvider.clients!.autoClients.map((item) => item.ip).toList() - ); - } - - void unselectAll() { - onClientsSelected([]); - } - return Column( mainAxisSize: MainAxisSize.min, children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 24, - bottom: 16, - ), - child: Icon( - Icons.smartphone_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - Text( - AppLocalizations.of(context)!.clients, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 16), - ], - ), - Flexible( - child: ListView.builder( - itemCount: clientsProvider.clients!.autoClients.length, - itemBuilder: (context, index) => _ListItem( - label: clientsProvider.clients!.autoClients[index].ip, - checkboxActive: selectedClients.contains(clientsProvider.clients!.autoClients[index].ip), - onChanged: (isSelected) { - if (isSelected == true) { - onClientsSelected([ - ...selectedClients, - clientsProvider.clients!.autoClients[index].ip - ]); - } - else { - onClientsSelected( - selectedClients.where( - (item) => item != clientsProvider.clients!.autoClients[index].ip - ).toList() - ); - } - } - ) - ) - ), Padding( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(16), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - TextButton( - onPressed: selectedClients.length == clientsProvider.clients!.autoClients.length - ? () => unselectAll() - : () => selectAll(), - child: Text( - selectedClients.length == clientsProvider.clients!.autoClients.length - ? AppLocalizations.of(context)!.unselectAll - : AppLocalizations.of(context)!.selectAll - ) + CloseButton( + onPressed: () => Navigator.pop(context), ), - TextButton( - onPressed: apply, - child: Text(AppLocalizations.of(context)!.apply) + const SizedBox(width: 12), + Text( + AppLocalizations.of(context)!.clients, + style: const TextStyle( + fontSize: 22 + ), ) ], ), ), + Flexible( + child: ListView( + children: [ + Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Flexible( + child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo) + ) + ], + ), + ), + ), + CustomCheckboxListTile( + padding: const EdgeInsets.only( + left: 24, + top: 8, + right: 12, + bottom: 8 + ), + value: logsProvider.selectedClients.length == clientsProvider.clients!.autoClients.length, + onChanged: (v) { + if (v == true) { + logsProvider.setSelectedClients(clientsProvider.clients!.autoClients.map((e) => e.ip).toList()); + } + else { + logsProvider.setSelectedClients([]); + } + }, + title: AppLocalizations.of(context)!.selectAll + ), + ListView.builder( + primary: false, + shrinkWrap: true, + itemCount: clientsProvider.clients!.autoClients.length, + itemBuilder: (context, index) => _ListItem( + label: clientsProvider.clients!.autoClients[index].ip, + checkboxActive: selectedClients.contains(clientsProvider.clients!.autoClients[index].ip), + onChanged: (isSelected) { + if (isSelected == true) { + onClientsSelected([ + ...selectedClients, + clientsProvider.clients!.autoClients[index].ip + ]); + } + else { + onClientsSelected( + selectedClients.where( + (item) => item != clientsProvider.clients!.autoClients[index].ip + ).toList() + ); + } + } + ) + ), + ], + ) + ), if (Platform.isIOS) const SizedBox(height: 16) ], ); @@ -203,9 +235,9 @@ class _ListItem extends StatelessWidget { child: Padding( padding: const EdgeInsets.only( left: 24, - top: 8, + top: 4, right: 12, - bottom: 8 + bottom: 4 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/screens/logs/filters/logs_filters_modal.dart b/lib/screens/logs/filters/logs_filters_modal.dart index aec6b27..9f77666 100644 --- a/lib/screens/logs/filters/logs_filters_modal.dart +++ b/lib/screens/logs/filters/logs_filters_modal.dart @@ -150,6 +150,7 @@ class _FiltersList extends StatelessWidget { dialog: false, ), isScrollControlled: true, + useSafeArea: true, backgroundColor: Colors.transparent ); } @@ -225,8 +226,8 @@ class _FiltersList extends StatelessWidget { Container(height: 16), CustomListTile( title: AppLocalizations.of(context)!.client, - subtitle: logsProvider.selectedClients != null - ? "${logsProvider.selectedClients!.length} ${AppLocalizations.of(context)!.clientsSelected}" + subtitle: logsProvider.selectedClients.isNotEmpty + ? "${logsProvider.selectedClients.length} ${AppLocalizations.of(context)!.clientsSelected}" : AppLocalizations.of(context)!.all, onTap: clientsProvider.loadStatus == LoadStatus.loaded ? openSelectClients diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index a9c9c67..40291e9 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_interface_item.dart'; +import 'package:adguard_home_manager/widgets/list_bottom_sheet.dart'; import 'package:adguard_home_manager/models/dhcp.dart'; @@ -89,70 +90,20 @@ class SelectInterfaceModal extends StatelessWidget { ); } else { - return GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: DraggableScrollableSheet( - initialChildSize: 0.6, - minChildSize: 0.3, - maxChildSize: 1, - builder: (context, controller) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - ), - child: Column( - children: [ - Container( - margin: const EdgeInsets.all(16), - width: 36, - height: 4, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - children: [ - Icon( - Icons.settings_ethernet_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.selectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ), - Expanded( - child: SafeArea( - child: ListView.builder( - controller: controller, - itemCount: interfaces.length, - itemBuilder: (context, index) => DhcpInterfaceItem( - networkInterface: interfaces[index], - onSelect: onSelect - ) - ), - ) - ), - ], - ), - ); - }, - ), + return ListBottomSheet( + icon: Icons.settings_ethernet_rounded, + title: AppLocalizations.of(context)!.selectInterface, + children: [ + ListView.builder( + primary: false, + shrinkWrap: true, + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ), + ] ); } } diff --git a/lib/widgets/list_bottom_sheet.dart b/lib/widgets/list_bottom_sheet.dart new file mode 100644 index 0000000..2f8bd03 --- /dev/null +++ b/lib/widgets/list_bottom_sheet.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; + +class ListBottomSheet extends StatelessWidget { + final IconData icon; + final String title; + final List children; + + const ListBottomSheet({ + super.key, + required this.icon, + required this.title, + required this.children + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 1, + builder: (context, controller) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + ), + child: SafeArea( + child: ListView( + controller: controller, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(16), + width: 36, + height: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.grey + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Icon( + icon, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + ...children + ], + ), + ), + ); + }, + ), + ); + } +} \ No newline at end of file