From 1508c49536e10f7389c8e2d8f615562c7a3b987a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 6 Apr 2023 18:10:23 +0200 Subject: [PATCH] New tab list on clients --- lib/constants/enums.dart | 1 + lib/models/clients.dart | 3 +- lib/providers/servers_provider.dart | 5 +- lib/screens/clients/added_list.dart | 315 +++++++++--------- lib/screens/clients/clients.dart | 110 +++--- lib/screens/clients/clients_list.dart | 207 ++++++------ lib/screens/clients/fab.dart | 19 +- .../access_settings/access_settings.dart | 13 +- .../access_settings/clients_list.dart | 9 +- lib/widgets/tab_content_list.dart | 109 ++++++ 10 files changed, 440 insertions(+), 351 deletions(-) create mode 100644 lib/constants/enums.dart create mode 100644 lib/widgets/tab_content_list.dart diff --git a/lib/constants/enums.dart b/lib/constants/enums.dart new file mode 100644 index 0000000..58f5509 --- /dev/null +++ b/lib/constants/enums.dart @@ -0,0 +1 @@ +enum LoadStatus { loading, loaded, error } \ No newline at end of file diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 2be193c..9fc9b06 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -1,9 +1,10 @@ import 'dart:convert'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/clients_allowed_blocked.dart'; class Clients { - int loadStatus; + LoadStatus loadStatus; ClientsData? data; Clients({ diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 5b8a1d1..ad6cde4 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -1,3 +1,4 @@ +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/blocked_services.dart'; import 'package:flutter/material.dart'; import 'package:sqflite/sqflite.dart'; @@ -26,7 +27,7 @@ class ServersProvider with ChangeNotifier { List _protectionsManagementProcess = []; // protections that are currenty being enabled or disabled final Clients _clients = Clients( - loadStatus: 0, // 0 = loading, 1 = loaded, 2 = error + loadStatus: LoadStatus.loading, data: null ); @@ -125,7 +126,7 @@ class ServersProvider with ChangeNotifier { notifyListeners(); } - void setClientsLoadStatus(int status, bool notify) { + void setClientsLoadStatus(LoadStatus status, bool notify) { _clients.loadStatus = status; if (notify == true) { notifyListeners(); diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 76d5a03..207ce9f 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -10,8 +10,10 @@ import 'package:adguard_home_manager/screens/clients/client_screen.dart'; import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart'; import 'package:adguard_home_manager/screens/clients/fab.dart'; import 'package:adguard_home_manager/screens/clients/options_modal.dart'; +import 'package:adguard_home_manager/widgets/tab_content_list.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/clients.dart'; @@ -20,7 +22,7 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; class AddedList extends StatefulWidget { final ScrollController scrollController; - final int loadStatus; + final LoadStatus loadStatus; final List data; final Future Function() fetchClients; @@ -169,180 +171,163 @@ class _AddedListState extends State { ); } - switch (widget.loadStatus) { - case 0: - return SizedBox( - width: double.maxFinite, - height: MediaQuery.of(context).size.height-171, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingStatus, - textAlign: TextAlign.center, + return Stack( + children: [ + CustomTabContentList( + loadingGenerator: () => SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height-171, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingStatus, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ), + itemsCount: widget.data.length, + contentWidget: (index) => ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + isThreeLine: true, + onLongPress: () => openOptionsModal(widget.data[index]), + onTap: () => openClientModal(widget.data[index]), + title: Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Text( + widget.data[index].name, style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 18, + fontWeight: FontWeight.normal, + color: Theme.of(context).colorScheme.onSurface ), - ) - ], - ), - ); - - case 1: - return Stack( - children: [ - if (widget.data.isNotEmpty) ListView.builder( - padding: const EdgeInsets.only(top: 0), - itemCount: widget.data.length, - itemBuilder: (context, index) => ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - isThreeLine: true, - onLongPress: () => openOptionsModal(widget.data[index]), - onTap: () => openClientModal(widget.data[index]), - title: Padding( - padding: const EdgeInsets.only(bottom: 5), - child: Text( - widget.data[index].name, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.normal, - color: Theme.of(context).colorScheme.onSurface - ), + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.data[index].ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), + style: TextStyle( + color: Theme.of(context).listTileTheme.textColor ), ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, + const SizedBox(height: 7), + Row( children: [ - Text( - widget.data[index].ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor - ), + Icon( + Icons.filter_list_rounded, + size: 19, + color: widget.data[index].filteringEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, ), - const SizedBox(height: 7), - Row( - children: [ - Icon( - Icons.filter_list_rounded, - size: 19, - color: widget.data[index].filteringEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.vpn_lock_rounded, - size: 18, - color: widget.data[index].safebrowsingEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.block, - size: 18, - color: widget.data[index].parentalEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.search_rounded, - size: 19, - color: widget.data[index].safesearchEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ) - ], + const SizedBox(width: 10), + Icon( + Icons.vpn_lock_rounded, + size: 18, + color: widget.data[index].safebrowsingEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ), + const SizedBox(width: 10), + Icon( + Icons.block, + size: 18, + color: widget.data[index].parentalEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ), + const SizedBox(width: 10), + Icon( + Icons.search_rounded, + size: 19, + color: widget.data[index].safesearchEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, ) ], - ), - ) + ) + ], ), - if (widget.data.isEmpty) SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - AppLocalizations.of(context)!.noClientsList, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + ), + noData: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.noClientsList, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - const SizedBox(height: 30), - TextButton.icon( - onPressed: widget.fetchClients, - icon: const Icon(Icons.refresh_rounded), - label: Text(AppLocalizations.of(context)!.refresh), - ) - ], - ), - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: isVisible ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: const ClientsFab(tab: 1), - ) - ], - ); - - case 2: - return SizedBox( - width: double.maxFinite, - height: MediaQuery.of(context).size.height-171, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.errorLoadServerStatus, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - + const SizedBox(height: 30), + TextButton.icon( + onPressed: widget.fetchClients, + icon: const Icon(Icons.refresh_rounded), + label: Text(AppLocalizations.of(context)!.refresh), + ) + ], + ), + ), + errorGenerator: () => SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height-171, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.errorLoadServerStatus, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ), + loadStatus: widget.loadStatus, + onRefresh: widget.fetchClients + ), + ClientsFab( + isVisible: isVisible, + ) + ], + ); } } \ No newline at end of file diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index f64f3fd..549b242 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -9,6 +9,7 @@ import 'package:adguard_home_manager/screens/clients/added_list.dart'; import 'package:adguard_home_manager/models/app_log.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/server.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -33,7 +34,7 @@ class Clients extends StatelessWidget { class ClientsWidget extends StatefulWidget { final Server server; - final void Function(int, bool) setLoadingStatus; + final void Function(LoadStatus, bool) setLoadingStatus; final void Function(ClientsData) setClientsData; final void Function(int) setSelectedClientsTab; final void Function(AppLog) addLog; @@ -56,16 +57,16 @@ class _ClientsWidgetState extends State with TickerProviderStateM final ScrollController scrollController = ScrollController(); Future fetchClients() async { - widget.setLoadingStatus(0, false); + widget.setLoadingStatus(LoadStatus.loading, false); final result = await getClients(widget.server); if (mounted) { if (result['result'] == 'success') { widget.setClientsData(result['data']); - widget.setLoadingStatus(1, true); + widget.setLoadingStatus(LoadStatus.loaded, true); } else { widget.addLog(result['log']); - widget.setLoadingStatus(2, true); + widget.setLoadingStatus(LoadStatus.error, true); } } } @@ -98,43 +99,40 @@ class _ClientsWidgetState extends State with TickerProviderStateM return [ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverSafeArea( - top: false, - sliver: SliverAppBar( - title: Text(AppLocalizations.of(context)!.clients), - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - actions: [ - if (serversProvider.clients.loadStatus == 1) ...[ - IconButton( - onPressed: () => { - Navigator.push(context, MaterialPageRoute( - builder: (context) => const SearchClients() - )) - }, - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.searchClients, - ), - const SizedBox(width: 10), - ] - ], - bottom: TabBar( - controller: tabController, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - icon: const Icon(Icons.devices), - text: AppLocalizations.of(context)!.activeClients, - ), - Tab( - icon: const Icon(Icons.add_rounded), - text: AppLocalizations.of(context)!.added, - ), - ] - ) - ), + sliver: SliverAppBar( + title: Text(AppLocalizations.of(context)!.clients), + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + actions: [ + if (serversProvider.clients.loadStatus == LoadStatus.loaded) ...[ + IconButton( + onPressed: () => { + Navigator.push(context, MaterialPageRoute( + builder: (context) => const SearchClients() + )) + }, + icon: const Icon(Icons.search), + tooltip: AppLocalizations.of(context)!.searchClients, + ), + const SizedBox(width: 10), + ] + ], + bottom: TabBar( + controller: tabController, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabs: [ + Tab( + icon: const Icon(Icons.devices), + text: AppLocalizations.of(context)!.activeClients, + ), + Tab( + icon: const Icon(Icons.add_rounded), + text: AppLocalizations.of(context)!.added, + ), + ] + ) ), ) ]; @@ -151,25 +149,19 @@ class _ClientsWidgetState extends State with TickerProviderStateM child: TabBarView( controller: tabController, children: [ - RefreshIndicator( - onRefresh: fetchClients, - child: ClientsList( - scrollController: scrollController, - loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == 1 - ? serversProvider.clients.data!.autoClientsData : [], - fetchClients: fetchClients, - ), + ClientsList( + scrollController: scrollController, + loadStatus: serversProvider.clients.loadStatus, + data: serversProvider.clients.loadStatus == LoadStatus.loaded + ? serversProvider.clients.data!.autoClientsData : [], + fetchClients: fetchClients, ), - RefreshIndicator( - onRefresh: fetchClients, - child: AddedList( - scrollController: scrollController, - loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == 1 - ? serversProvider.clients.data!.clients : [], - fetchClients: fetchClients, - ) + AddedList( + scrollController: scrollController, + loadStatus: serversProvider.clients.loadStatus, + data: serversProvider.clients.loadStatus == LoadStatus.loaded + ? serversProvider.clients.data!.clients : [], + fetchClients: fetchClients, ), ] ) diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index f56c731..af1ee86 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -1,17 +1,19 @@ -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; 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_list_tile.dart'; +import 'package:adguard_home_manager/widgets/tab_content_list.dart'; import 'package:adguard_home_manager/models/clients.dart'; +import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; class ClientsList extends StatelessWidget { final ScrollController scrollController; - final int loadStatus; + final LoadStatus loadStatus; final List data; final Future Function() fetchClients; @@ -28,115 +30,102 @@ class ClientsList extends StatelessWidget { final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); - switch (loadStatus) { - case 0: - return SizedBox( - width: double.maxFinite, - height: MediaQuery.of(context).size.height-171, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingStatus, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case 1: - if (data.isNotEmpty) { - return ListView.builder( - padding: const EdgeInsets.only(top: 0), - itemCount: data.length, - itemBuilder: (context, index) => CustomListTile( - title: data[index].name != '' - ? data[index].name! - : data[index].ip, - subtitle: data[index].name != '' - ? data[index].ip - : null, - trailing: Text( - data[index].source, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), + return CustomTabContentList( + loadingGenerator: () => SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height-171, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingStatus, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - onTap: () { - logsProvider.setSearchText(null); - logsProvider.setSelectedClients([data[index].ip]); - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: null, - clients: [data[index].ip] - ) - ); - appConfigProvider.setSelectedScreen(2); - }, + ) + ], + ), + ), + itemsCount: data.length, + contentWidget: (index) => CustomListTile( + title: data[index].name != '' + ? data[index].name! + : data[index].ip, + subtitle: data[index].name != '' + ? data[index].ip + : null, + trailing: Text( + data[index].source, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + onTap: () { + logsProvider.setSearchText(null); + logsProvider.setSelectedClients([data[index].ip]); + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: null, + clients: [data[index].ip] ) ); - } - else { - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - AppLocalizations.of(context)!.noClientsList, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 30), - TextButton.icon( - onPressed: fetchClients, - icon: const Icon(Icons.refresh_rounded), - label: Text(AppLocalizations.of(context)!.refresh) - ) - ], - ), - ); - } - - case 2: - return SizedBox( - width: double.maxFinite, - height: MediaQuery.of(context).size.height-171, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, + appConfigProvider.setSelectedScreen(2); + }, + ), + noData: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.noClientsList, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.errorLoadServerStatus, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } + ), + const SizedBox(height: 30), + TextButton.icon( + onPressed: fetchClients, + icon: const Icon(Icons.refresh_rounded), + label: Text(AppLocalizations.of(context)!.refresh) + ) + ], + ), + ), + errorGenerator: () => SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height-171, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.errorLoadServerStatus, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ), + loadStatus: loadStatus, + onRefresh: fetchClients + ); } } \ No newline at end of file diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 74477af..59d9d43 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -14,11 +14,11 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class ClientsFab extends StatelessWidget { - final int tab; + final bool isVisible; const ClientsFab({ Key? key, - required this.tab, + required this.isVisible }) : super(key: key); @override @@ -67,9 +67,18 @@ class ClientsFab extends StatelessWidget { )); } - return FloatingActionButton( - onPressed: () => openAddClient(), - child: const Icon(Icons.add), + return AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: isVisible ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: FloatingActionButton( + onPressed: openAddClient, + child: const Icon(Icons.add), + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index 08f96a2..a452857 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -1,3 +1,4 @@ +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -42,16 +43,16 @@ class _AccessSettingsWidgetState extends State with Ticker late TabController tabController; Future fetchClients() async { - widget.serversProvider.setClientsLoadStatus(0, false); + widget.serversProvider.setClientsLoadStatus(LoadStatus.loading, false); final result = await getClients(widget.serversProvider.selectedServer!); if (mounted) { if (result['result'] == 'success') { widget.serversProvider.setClientsData(result['data']); - widget.serversProvider.setClientsLoadStatus(1, true); + widget.serversProvider.setClientsLoadStatus(LoadStatus.loaded, true); } else { widget.appConfigProvider.addLog(result['log']); - widget.serversProvider.setClientsLoadStatus(2, true); + widget.serversProvider.setClientsLoadStatus(LoadStatus.error, true); } } } @@ -120,7 +121,7 @@ class _AccessSettingsWidgetState extends State with Ticker type: 'allowed', scrollController: scrollController, loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == 1 + data: serversProvider.clients.loadStatus == LoadStatus.loaded ? serversProvider.clients.data!.clientsAllowedBlocked!.allowedClients : [], fetchClients: fetchClients ), @@ -128,7 +129,7 @@ class _AccessSettingsWidgetState extends State with Ticker type: 'disallowed', scrollController: scrollController, loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == 1 + data: serversProvider.clients.loadStatus == LoadStatus.loaded ? serversProvider.clients.data!.clientsAllowedBlocked!.disallowedClients : [], fetchClients: fetchClients ), @@ -136,7 +137,7 @@ class _AccessSettingsWidgetState extends State with Ticker type: 'domains', scrollController: scrollController, loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == 1 + data: serversProvider.clients.loadStatus == LoadStatus.loaded ? serversProvider.clients.data!.clientsAllowedBlocked!.blockedHosts : [], fetchClients: fetchClients ), diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 2d6f787..10ae3df 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; @@ -20,7 +21,7 @@ import 'package:adguard_home_manager/classes/process_modal.dart'; class ClientsList extends StatefulWidget { final String type; final ScrollController scrollController; - final int loadStatus; + final LoadStatus loadStatus; final List data; final Future Function() fetchClients; @@ -209,7 +210,7 @@ class _ClientsListState extends State { } switch (widget.loadStatus) { - case 0: + case LoadStatus.loading: return SizedBox( width: double.maxFinite, height: MediaQuery.of(context).size.height-171, @@ -231,7 +232,7 @@ class _ClientsListState extends State { ), ); - case 1: + case LoadStatus.loaded: return Stack( children: [ RefreshIndicator( @@ -340,7 +341,7 @@ class _ClientsListState extends State { ] ); - case 2: + case LoadStatus.error: return SizedBox( width: double.maxFinite, height: MediaQuery.of(context).size.height-101, diff --git a/lib/widgets/tab_content_list.dart b/lib/widgets/tab_content_list.dart new file mode 100644 index 0000000..866d000 --- /dev/null +++ b/lib/widgets/tab_content_list.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; + +class CustomTabContentList extends StatelessWidget { + final Widget Function() loadingGenerator; + final int itemsCount; + final Widget Function(int index) contentWidget; + final Widget noData; + final Widget Function() errorGenerator; + final LoadStatus loadStatus; + final Future Function() onRefresh; + + const CustomTabContentList({ + Key? key, + required this.loadingGenerator, + required this.itemsCount, + required this.contentWidget, + required this.noData, + required this.errorGenerator, + required this.loadStatus, + required this.onRefresh, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + switch (loadStatus) { + case LoadStatus.loading: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: loadingGenerator() + ), + ) + ], + ), + ) + ); + + + case LoadStatus.loaded: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return RefreshIndicator( + onRefresh: onRefresh, + edgeOffset: 95, + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + if (itemsCount > 0) SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => contentWidget(index), + childCount: itemsCount + ), + ), + if (itemsCount == 0) SliverFillRemaining( + child: noData, + ) + ], + ), + ); + }, + ), + ); + + case LoadStatus.error: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: Padding( + padding: const EdgeInsets.only( + top: 95, + left: 16, + right: 16 + ), + child: errorGenerator() + ), + ) + ], + ), + ) + ); + + default: + return const SizedBox(); + } + } +} \ No newline at end of file