diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index 8cf79a5..a06d27c 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -8,6 +8,8 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/models/clients_allowed_blocked.dart'; import 'package:adguard_home_manager/constants/enums.dart'; +enum AccessSettingsList { allowed, disallowed, domains } + class ClientsProvider with ChangeNotifier { ServersProvider? _serversProvider; StatusProvider? _statusProvider; @@ -195,20 +197,20 @@ class ClientsProvider with ChangeNotifier { } } - Future> addClientList(String item, String type) async { + Future> addClientList(String item, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], "disallowed_clients": clients!.clientsAllowedBlocked?.disallowedClients ?? [], "blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [], }; - if (type == 'allowed') { + if (type == AccessSettingsList.allowed) { body['allowed_clients']!.add(item); } - else if (type == 'disallowed') { + else if (type == AccessSettingsList.disallowed) { body['disallowed_clients']!.add(item); } - else if (type == 'domains') { + else if (type == AccessSettingsList.domains) { body['blocked_hosts']!.add(item); } @@ -239,20 +241,20 @@ class ClientsProvider with ChangeNotifier { } } - Future> removeClientList(String client, String type) async { + Future> removeClientList(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], "disallowed_clients": clients!.clientsAllowedBlocked?.disallowedClients ?? [], "blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [], }; - if (type == 'allowed') { + if (type == AccessSettingsList.allowed) { body['allowed_clients'] = body['allowed_clients']!.where((c) => c != client).toList(); } - else if (type == 'disallowed') { + else if (type == AccessSettingsList.disallowed) { body['disallowed_clients'] = body['disallowed_clients']!.where((c) => c != client).toList(); } - else if (type == 'domains') { + else if (type == AccessSettingsList.domains) { body['blocked_hosts'] = body['blocked_hosts']!.where((c) => c != client).toList(); } diff --git a/lib/screens/filters/filters_tabs_view.dart b/lib/screens/filters/filters_tabs_view.dart index f6016e5..bc96bc8 100644 --- a/lib/screens/filters/filters_tabs_view.dart +++ b/lib/screens/filters/filters_tabs_view.dart @@ -18,12 +18,12 @@ class FiltersTabsView extends StatefulWidget { final void Function(Filter, String) onOpenDetailsModal; const FiltersTabsView({ - Key? key, + super.key, required this.appConfigProvider, required this.actions, required this.onOpenDetailsModal, required this.onRemoveCustomRule - }) : super(key: key); + }); @override State createState() => _FiltersTabsViewState(); @@ -70,6 +70,7 @@ class _FiltersTabsViewState extends State with TickerProviderSt controller: tabController, isScrollable: true, unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabAlignment: TabAlignment.start, tabs: [ Tab( child: Row( diff --git a/lib/screens/filters/selection/selection_lists.dart b/lib/screens/filters/selection/selection_lists.dart index 1525e24..9562e74 100644 --- a/lib/screens/filters/selection/selection_lists.dart +++ b/lib/screens/filters/selection/selection_lists.dart @@ -152,11 +152,10 @@ class _Tile extends StatelessWidget { final bool isSelected; const _Tile({ - Key? key, required this.list, required this.onSelect, required this.isSelected, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/screens/filters/selection/selection_screen.dart b/lib/screens/filters/selection/selection_screen.dart index 793a077..108544e 100644 --- a/lib/screens/filters/selection/selection_screen.dart +++ b/lib/screens/filters/selection/selection_screen.dart @@ -310,7 +310,6 @@ class _SelectionScreenState extends State with TickerProviderSt ) ) ), - ], ), ); diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index ca9cf12..d5673f7 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -11,105 +11,38 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; class AccessSettings extends StatefulWidget { - const AccessSettings({Key? key}) : super(key: key); + const AccessSettings({super.key}); @override State createState() => _AccessSettingsState(); } class _AccessSettingsState extends State with TickerProviderStateMixin { - final ScrollController scrollController = ScrollController(); - late TabController tabController; + late ScrollController _scrollController; + late TabController _tabController; @override void initState() { Provider.of(context, listen: false).fetchClients(updateLoading: true); super.initState(); - tabController = TabController( + _tabController = TabController( initialIndex: 0, length: 3, vsync: this, ); + _scrollController = ScrollController(); } @override Widget build(BuildContext context) { - final clientsProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - Widget body() { - return TabBarView( - controller: tabController, - children: [ - ClientsList( - type: 'allowed', - scrollController: scrollController, - loadStatus: clientsProvider.loadStatus, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.clients!.clientsAllowedBlocked!.allowedClients : [], - ), - ClientsList( - type: 'disallowed', - scrollController: scrollController, - loadStatus: clientsProvider.loadStatus, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.clients!.clientsAllowedBlocked!.disallowedClients : [], - ), - ClientsList( - type: 'domains', - scrollController: scrollController, - loadStatus: clientsProvider.loadStatus, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.clients!.clientsAllowedBlocked!.blockedHosts : [], - ), - ] - ); - } - - PreferredSizeWidget tabBar() { - return TabBar( - controller: tabController, - isScrollable: true, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - children: [ - const Icon(Icons.check), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.allowedClients) - ], - ), - ), - Tab( - child: Row( - children: [ - const Icon(Icons.block), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.disallowedClients) - ], - ), - ), - Tab( - child: Row( - children: [ - const Icon(Icons.link_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.disallowedDomains) - ], - ), - ), - ] - ); - } - if (Platform.isAndroid || Platform.isIOS) { return Scaffold( body: DefaultTabController( length: 3, child: NestedScrollView( - controller: scrollController, + controller: _scrollController, headerSliverBuilder: ((context, innerBoxIsScrolled) { return [ SliverOverlapAbsorber( @@ -123,13 +56,19 @@ class _AccessSettingsState extends State with TickerProviderStat centerTitle: false, forceElevated: innerBoxIsScrolled, surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - bottom: tabBar() + bottom: PreferredSize( + preferredSize: const Size(double.maxFinite, 50), + child: _Tabs(tabController: _tabController) + ) ), ), ) ]; }), - body: body() + body: _TabsView( + tabController: _tabController, + scrollController: _scrollController + ) ) ), ); @@ -139,10 +78,105 @@ class _AccessSettingsState extends State with TickerProviderStat appBar: AppBar( title: Text(AppLocalizations.of(context)!.accessSettings), centerTitle: false, - bottom: tabBar() + bottom: PreferredSize( + preferredSize: const Size(double.maxFinite, 50), + child: _Tabs(tabController: _tabController) + ) ), - body: body(), + body: _TabsView( + tabController: _tabController, + scrollController: _scrollController + ) ); } } +} + +class _Tabs extends StatelessWidget { + final TabController tabController; + + const _Tabs({ + required this.tabController, + }); + + @override + Widget build(BuildContext context) { + return TabBar( + controller: tabController, + isScrollable: true, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabAlignment: TabAlignment.start, + tabs: [ + Tab( + child: Row( + children: [ + const Icon(Icons.check), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.allowedClients) + ], + ), + ), + Tab( + child: Row( + children: [ + const Icon(Icons.block), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.disallowedClients) + ], + ), + ), + Tab( + child: Row( + children: [ + const Icon(Icons.link_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.disallowedDomains) + ], + ), + ), + ] + ); + } +} + +class _TabsView extends StatelessWidget { + final TabController tabController; + final ScrollController scrollController; + + const _TabsView({ + required this.tabController, + required this.scrollController, + }); + + @override + Widget build(BuildContext context) { + final clientsProvider = Provider.of(context); + + return TabBarView( + controller: tabController, + children: [ + ClientsList( + type: AccessSettingsList.allowed, + scrollController: scrollController, + loadStatus: clientsProvider.loadStatus, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.clients!.clientsAllowedBlocked!.allowedClients : [], + ), + ClientsList( + type: AccessSettingsList.disallowed, + scrollController: scrollController, + loadStatus: clientsProvider.loadStatus, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.clients!.clientsAllowedBlocked!.disallowedClients : [], + ), + ClientsList( + type: AccessSettingsList.domains, + scrollController: scrollController, + loadStatus: clientsProvider.loadStatus, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.clients!.clientsAllowedBlocked!.blockedHosts : [], + ), + ] + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/access_settings/add_client_modal.dart b/lib/screens/settings/access_settings/add_client_modal.dart index 14ba98a..014babd 100644 --- a/lib/screens/settings/access_settings/add_client_modal.dart +++ b/lib/screens/settings/access_settings/add_client_modal.dart @@ -1,162 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class AddClientModal extends StatefulWidget { - final String type; - final void Function(String, String) onConfirm; +import 'package:adguard_home_manager/providers/clients_provider.dart'; + +class AddClientModal extends StatelessWidget { + final AccessSettingsList type; + final void Function(String, AccessSettingsList) onConfirm; final bool dialog; const AddClientModal({ - Key? key, + super.key, required this.type, required this.onConfirm, required this.dialog, - }) : super(key: key); + }); - @override - State createState() => _AddClientModalState(); -} - -class _AddClientModalState extends State { - TextEditingController fieldController = TextEditingController(); - - bool validData = false; - - void checkValidValues() { - if (fieldController.text != '') { - setState(() => validData = true); - } - else { - setState(() => validData = false); - } - } - @override Widget build(BuildContext context) { - IconData icon() { - switch (widget.type) { - case 'allowed': - return Icons.check; - - case 'disallowed': - return Icons.block; - - case 'domains': - return Icons.link_rounded; - - default: - return Icons.check; - } - } - - String title() { - switch (widget.type) { - case 'allowed': - return AppLocalizations.of(context)!.allowClient; - - case 'disallowed': - return AppLocalizations.of(context)!.disallowClient; - - case 'domains': - return AppLocalizations.of(context)!.disallowedDomains; - - default: - return ""; - } - } - - Widget content() { - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon(), - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - title(), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ), - TextFormField( - controller: fieldController, - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - helperText: widget.type == 'allowed' || widget.type == 'disallowed' - ? AppLocalizations.of(context)!.addClientFieldDescription : null, - labelText: widget.type == 'allowed' || widget.type == 'disallowed' - ? AppLocalizations.of(context)!.clientIdentifier - : AppLocalizations.of(context)!.domain, - ), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 16), - TextButton( - onPressed: validData == true - ? () { - Navigator.pop(context); - widget.onConfirm(fieldController.text, widget.type); - } - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: validData == true - ? Theme.of(context).colorScheme.primary - : Colors.grey - ), - ) - ), - ], - ), - ), - ], - ), - ); - } - - if (widget.dialog == true) { + if (dialog == true) { return Padding( padding: MediaQuery.of(context).viewInsets, child: Dialog( @@ -164,7 +25,10 @@ class _AddClientModalState extends State { constraints: const BoxConstraints( maxWidth: 400 ), - child: content() + child: _Content( + type: type, + onConfirm: onConfirm, + ) ), ), ); @@ -180,9 +44,163 @@ class _AddClientModalState extends State { topRight: Radius.circular(28) ) ), - child: content() + child: _Content( + type: type, + onConfirm: onConfirm, + ) ), ); } } +} + +class _Content extends StatefulWidget { + final AccessSettingsList type; + final void Function(String, AccessSettingsList) onConfirm; + + const _Content({ + required this.type, + required this.onConfirm, + }); + + @override + State<_Content> createState() => _ContentState(); +} + +class _ContentState extends State<_Content> { + TextEditingController fieldController = TextEditingController(); + bool validData = false; + + void checkValidValues() { + if (fieldController.text != '') { + setState(() => validData = true); + } + else { + setState(() => validData = false); + } + } + + @override + Widget build(BuildContext context) { + IconData icon() { + switch (widget.type) { + case AccessSettingsList.allowed: + return Icons.check; + + case AccessSettingsList.disallowed: + return Icons.block; + + case AccessSettingsList.domains: + return Icons.link_rounded; + + default: + return Icons.check; + } + } + + String title() { + switch (widget.type) { + case AccessSettingsList.allowed: + return AppLocalizations.of(context)!.allowClient; + + case AccessSettingsList.disallowed: + return AppLocalizations.of(context)!.disallowClient; + + case AccessSettingsList.domains: + return AppLocalizations.of(context)!.disallowedDomains; + + default: + return ""; + } + } + + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon(), + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title(), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + TextFormField( + controller: fieldController, + onChanged: (_) => checkValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + helperText: widget.type == AccessSettingsList.allowed || widget.type == AccessSettingsList.disallowed + ? AppLocalizations.of(context)!.addClientFieldDescription : null, + labelText: widget.type == AccessSettingsList.allowed || widget.type == AccessSettingsList.disallowed + ? AppLocalizations.of(context)!.clientIdentifier + : AppLocalizations.of(context)!.domain, + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 16), + TextButton( + onPressed: validData == true + ? () { + Navigator.pop(context); + widget.onConfirm(fieldController.text, widget.type); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: validData == true + ? Theme.of(context).colorScheme.primary + : Colors.grey + ), + ) + ), + ], + ), + ), + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index fcffbef..a9b1662 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -18,18 +18,18 @@ import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; class ClientsList extends StatefulWidget { - final String type; + final AccessSettingsList type; final ScrollController scrollController; final LoadStatus loadStatus; final List data; const ClientsList({ - Key? key, + super.key, required this.type, required this.scrollController, required this.loadStatus, required this.data, - }) : super(key: key); + }); @override State createState() => _ClientsListState(); @@ -79,20 +79,20 @@ class _ClientsListState extends State { } } - void confirmRemoveItem(String client, String type) async { + void confirmRemoveItem(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clientsProvider.clients!.clientsAllowedBlocked?.allowedClients ?? [], "disallowed_clients": clientsProvider.clients!.clientsAllowedBlocked?.disallowedClients ?? [], "blocked_hosts": clientsProvider.clients!.clientsAllowedBlocked?.blockedHosts ?? [], }; - if (type == 'allowed') { + if (type == AccessSettingsList.allowed) { body['allowed_clients'] = body['allowed_clients']!.where((c) => c != client).toList(); } - else if (type == 'disallowed') { + else if (type == AccessSettingsList.disallowed) { body['disallowed_clients'] = body['disallowed_clients']!.where((c) => c != client).toList(); } - else if (type == 'domains') { + else if (type == AccessSettingsList.domains) { body['blocked_hosts'] = body['blocked_hosts']!.where((c) => c != client).toList(); } @@ -128,7 +128,7 @@ class _ClientsListState extends State { } } - void confirmAddItem(String item, String type) async { + void confirmAddItem(String item, AccessSettingsList type) async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.removingClient); @@ -163,13 +163,13 @@ class _ClientsListState extends State { String description() { switch (widget.type) { - case 'allowed': + case AccessSettingsList.allowed: return AppLocalizations.of(context)!.allowedClientsDescription; - case 'disallowed': + case AccessSettingsList.disallowed: return AppLocalizations.of(context)!.blockedClientsDescription; - case 'domains': + case AccessSettingsList.domains: return AppLocalizations.of(context)!.disallowedDomainsDescription; default: @@ -179,13 +179,13 @@ class _ClientsListState extends State { String noItems() { switch (widget.type) { - case 'allowed': + case AccessSettingsList.allowed: return AppLocalizations.of(context)!.noAllowedClients; - case 'disallowed': + case AccessSettingsList.disallowed: return AppLocalizations.of(context)!.noBlockedClients; - case 'domains': + case AccessSettingsList.domains: return AppLocalizations.of(context)!.noDisallowedDomains; default: