diff --git a/lib/screens/logs/filters/clients_modal.dart b/lib/screens/logs/filters/clients_modal.dart index 6500717..ca15219 100644 --- a/lib/screens/logs/filters/clients_modal.dart +++ b/lib/screens/logs/filters/clients_modal.dart @@ -11,7 +11,17 @@ import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; -class ClientsModal extends StatelessWidget { +class _ClientLog { + final String ip; + final String? name; + + const _ClientLog({ + required this.ip, + required this.name + }); +} + +class ClientsModal extends StatefulWidget { final List? value; final bool dialog; @@ -22,196 +32,278 @@ class ClientsModal extends StatelessWidget { }); @override - Widget build(BuildContext context) { - final clientsProvider = Provider.of(context); - final logsProvider = Provider.of(context); - - if (dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: _ModalContent( - selectedClients: logsProvider.selectedClients, - onClientsSelected: (v) => logsProvider.setSelectedClients(v), - ) - ), - ); - } - else { - 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) - ) - ], - ), - ), - ), - 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() - ); - } - } - ) - ) - ] - ); - } - } + State createState() => _ClientsModalState(); } -class _ModalContent extends StatelessWidget { - final List selectedClients; - final void Function(List) onClientsSelected; +class _ClientsModalState extends State { + List<_ClientLog> _filteredClients = []; + final _searchController = TextEditingController(); - const _ModalContent({ - required this.selectedClients, - required this.onClientsSelected, - }); + @override + void initState() { + final clientsProvider = Provider.of(context, listen: false); + final statusProvider = Provider.of(context, listen: false); + _filteredClients = clientsProvider.clients!.autoClients.map((e) { + String? name; + try { + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name; + } catch (e) { + // ---- // + } + return _ClientLog( + ip: e.ip, + name: name + ); + }).toList(); + + super.initState(); + } @override Widget build(BuildContext context) { final clientsProvider = Provider.of(context); final logsProvider = Provider.of(context); + final statusProvider = Provider.of(context); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - CloseButton( - onPressed: () => Navigator.pop(context), - ), - const SizedBox(width: 12), - Text( - AppLocalizations.of(context)!.clients, - style: const TextStyle( - fontSize: 22 - ), - ) - ], + void onSearch(String value) { + final filtered = clientsProvider.clients!.autoClients.map((e) { + String? name; + try { + name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(e.ip)).name; + } catch (e) { + // ---- // + } + return _ClientLog( + ip: e.ip, + name: name + ); + }).where( + (c) => c.ip.contains(value.toLowerCase()) || (c.name != null && c.name!.toLowerCase().contains(value.toLowerCase())) + ).toList(); + setState(() => _filteredClients = filtered); + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 ), - ), - Flexible( - child: ListView( + child: Column( + mainAxisSize: MainAxisSize.min, 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, + Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + CloseButton( + onPressed: () => Navigator.pop(context), + ), + const SizedBox(width: 12), + Text( + AppLocalizations.of(context)!.clients, + style: const TextStyle( + fontSize: 22 ), - const SizedBox(width: 16), - Flexible( - child: Text(AppLocalizations.of(context)!.selectClientsFiltersInfo) + ) + ], + ), + ), + Flexible( + child: ListView( + children: [ + _SearchField( + controller: _searchController, + onClear: () => setState(() => _searchController.text = ""), + onSearch: onSearch + ), + 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: _filteredClients.length, + itemBuilder: (context, index) => _ListItem( + label: _filteredClients[index].ip, + checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip), + onChanged: (isSelected) { + if (isSelected == true) { + logsProvider.setSelectedClients([ + ...logsProvider.selectedClients, + _filteredClients[index].ip + ]); + } + else { + logsProvider.setSelectedClients( + logsProvider.selectedClients.where( + (item) => item != _filteredClients[index].ip + ).toList() + ); + } + } ) - ], - ), - ), - ), - 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) ], ) ), - if (Platform.isIOS) const SizedBox(height: 16) - ], + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: ListBottomSheet( + icon: Icons.smartphone_rounded, + title: AppLocalizations.of(context)!.clients, + children: [ + _SearchField( + controller: _searchController, + onClear: () => setState(() => _searchController.text = ""), + onSearch: onSearch + ), + 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( + shrinkWrap: true, + primary: false, + itemCount: _filteredClients.length, + itemBuilder: (context, index) => _ListItem( + label: _filteredClients[index].ip, + checkboxActive: logsProvider.selectedClients.contains(_filteredClients[index].ip), + onChanged: (isSelected) { + if (isSelected == true) { + logsProvider.setSelectedClients([ + ...logsProvider.selectedClients, + _filteredClients[index].ip + ]); + } + else { + logsProvider.setSelectedClients( + logsProvider.selectedClients.where( + (item) => item != _filteredClients[index].ip + ).toList() + ); + } + } + ) + ) + ] + ), + ); + } + } +} + +class _SearchField extends StatelessWidget { + final TextEditingController controller; + final void Function(String) onSearch; + final void Function() onClear; + + const _SearchField({ + required this.controller, + required this.onClear, + required this.onSearch, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: TextFormField( + controller: controller, + onChanged: onSearch, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.search, + prefixIcon: const Icon(Icons.search_rounded), + border: InputBorder.none, + filled: true, + fillColor: Colors.grey.withOpacity(0.2), + suffixIcon: controller.text != "" + ? IconButton( + onPressed: onClear, + icon: const Icon( + Icons.close_rounded, + size: 20, + ), + tooltip: AppLocalizations.of(context)!.clearSearch, + ) + : null + ), + ), + ), ); } } diff --git a/lib/widgets/list_bottom_sheet.dart b/lib/widgets/list_bottom_sheet.dart index 219df8c..83f9c78 100644 --- a/lib/widgets/list_bottom_sheet.dart +++ b/lib/widgets/list_bottom_sheet.dart @@ -20,67 +20,64 @@ class ListBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: DraggableScrollableSheet( - initialChildSize: initialChildSize ?? 0.6, - minChildSize: minChildSize ?? 0.3, - maxChildSize: 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), - ), + return DraggableScrollableSheet( + initialChildSize: initialChildSize ?? 0.6, + minChildSize: minChildSize ?? 0.3, + maxChildSize: 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, + ), + 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: [ - Container( - margin: const EdgeInsets.all(16), - width: 36, - height: 4, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey + 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 ), ), ], ), - 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 - ], - ), + ), + ...children + ], ), - ); - }, - ), + ), + ); + }, ); } } \ No newline at end of file