diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index aed54f0..9f03328 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -60,7 +62,7 @@ class _AccessSettingsWidgetState extends State with Ticker @override void initState() { - if (mounted) fetchClients(); + fetchClients(); super.initState(); tabController = TabController( initialIndex: 0, @@ -72,78 +74,114 @@ class _AccessSettingsWidgetState extends State with Ticker @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); - return Scaffold( - body: DefaultTabController( - length: 3, - child: NestedScrollView( - controller: scrollController, - headerSliverBuilder: ((context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverSafeArea( - top: false, - sliver: SliverAppBar( - title: Text(AppLocalizations.of(context)!.accessSettings), - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - bottom: TabBar( - controller: tabController, - isScrollable: true, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - icon: const Icon(Icons.check), - text: AppLocalizations.of(context)!.allowedClients, - ), - Tab( - icon: const Icon(Icons.block), - text: AppLocalizations.of(context)!.disallowedClients, - ), - Tab( - icon: const Icon(Icons.link_rounded), - text: AppLocalizations.of(context)!.disallowedDomains, - ), - ] - ) + + Widget body() { + return TabBarView( + controller: tabController, + children: [ + ClientsList( + type: 'allowed', + scrollController: scrollController, + loadStatus: serversProvider.clients.loadStatus, + data: serversProvider.clients.loadStatus == LoadStatus.loaded + ? serversProvider.clients.data!.clientsAllowedBlocked!.allowedClients : [], + fetchClients: fetchClients + ), + ClientsList( + type: 'disallowed', + scrollController: scrollController, + loadStatus: serversProvider.clients.loadStatus, + data: serversProvider.clients.loadStatus == LoadStatus.loaded + ? serversProvider.clients.data!.clientsAllowedBlocked!.disallowedClients : [], + fetchClients: fetchClients + ), + ClientsList( + type: 'domains', + scrollController: scrollController, + loadStatus: serversProvider.clients.loadStatus, + data: serversProvider.clients.loadStatus == LoadStatus.loaded + ? serversProvider.clients.data!.clientsAllowedBlocked!.blockedHosts : [], + fetchClients: fetchClients + ), + ] + ); + } + + 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, + headerSliverBuilder: ((context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverSafeArea( + top: false, + sliver: SliverAppBar( + title: Text(AppLocalizations.of(context)!.accessSettings), + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + bottom: tabBar() + ), ), - ), - ) - ]; - }), - body: TabBarView( - controller: tabController, - children: [ - ClientsList( - type: 'allowed', - scrollController: scrollController, - loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == LoadStatus.loaded - ? serversProvider.clients.data!.clientsAllowedBlocked!.allowedClients : [], - fetchClients: fetchClients - ), - ClientsList( - type: 'disallowed', - scrollController: scrollController, - loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == LoadStatus.loaded - ? serversProvider.clients.data!.clientsAllowedBlocked!.disallowedClients : [], - fetchClients: fetchClients - ), - ClientsList( - type: 'domains', - scrollController: scrollController, - loadStatus: serversProvider.clients.loadStatus, - data: serversProvider.clients.loadStatus == LoadStatus.loaded - ? serversProvider.clients.data!.clientsAllowedBlocked!.blockedHosts : [], - fetchClients: fetchClients - ), - ] + ) + ]; + }), + body: body() ) - ) - ), - ); + ), + ); + } + else { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.accessSettings), + centerTitle: false, + bottom: tabBar() + ), + body: body(), + ); + } } } \ 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 385409b..2fbdd17 100644 --- a/lib/screens/settings/access_settings/add_client_modal.dart +++ b/lib/screens/settings/access_settings/add_client_modal.dart @@ -6,11 +6,13 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class AddClientModal extends StatefulWidget { final String type; final void Function(String, String) onConfirm; + final bool dialog; const AddClientModal({ Key? key, required this.type, - required this.onConfirm + required this.onConfirm, + required this.dialog, }) : super(key: key); @override @@ -65,32 +67,26 @@ class _AddClientModalState extends State { } } - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - height: Platform.isIOS ? 321 : 305, + Widget content() { + return Padding( padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) - ), - child: Column( + child: Wrap( children: [ - Expanded( - child: ListView( - physics: (Platform.isIOS ? 338 : 322) < MediaQuery.of(context).size.height - ? const NeverScrollableScrollPhysics() - : null, + 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: [ - Icon( - icon(), - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), Text( title(), textAlign: TextAlign.center, @@ -99,27 +95,26 @@ class _AddClientModalState extends State { color: Theme.of(context).colorScheme.onSurface ), ), - const SizedBox(height: 16), - 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, - ), - ), ], ), ), + 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( @@ -129,7 +124,7 @@ class _AddClientModalState extends State { onPressed: () => Navigator.pop(context), child: Text(AppLocalizations.of(context)!.cancel) ), - const SizedBox(width: 20), + const SizedBox(width: 16), TextButton( onPressed: validData == true ? () { @@ -152,7 +147,36 @@ class _AddClientModalState extends State { if (Platform.isIOS) const SizedBox(height: 16) ], ), - ), - ); + ); + } + + if (widget.dialog == true) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: content() + ), + ), + ); + } + else { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) + ), + child: content() + ), + ); + } } } \ 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 6e561db..d66e193 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -1,5 +1,7 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; @@ -68,6 +70,8 @@ class _ClientsListState extends State { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void confirmRemoveItem(String client, String type) async { Map> body = { "allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [], @@ -209,6 +213,7 @@ class _ClientsListState extends State { } return CustomTabContentList( + noSliver: !(Platform.isAndroid || Platform.isIOS) ? true : false, loadingGenerator: () => SizedBox( width: double.maxFinite, height: MediaQuery.of(context).size.height-171, @@ -362,15 +367,28 @@ class _ClientsListState extends State { refreshIndicatorOffset: 0, fab: FloatingActionButton( onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => AddClientModal( - type: widget.type, - onConfirm: confirmAddItem - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ); + if (width > 900) { + showDialog( + context: context, + builder: (context) => AddClientModal( + type: widget.type, + onConfirm: confirmAddItem, + dialog: true, + ), + ); + } + else { + showModalBottomSheet( + context: context, + builder: (context) => AddClientModal( + type: widget.type, + onConfirm: confirmAddItem, + dialog: false, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ); + } }, child: const Icon(Icons.add), ), diff --git a/lib/widgets/tab_content_list.dart b/lib/widgets/tab_content_list.dart index 5171081..9603c95 100644 --- a/lib/widgets/tab_content_list.dart +++ b/lib/widgets/tab_content_list.dart @@ -15,6 +15,7 @@ class CustomTabContentList extends StatelessWidget { final double? refreshIndicatorOffset; final Widget? fab; final bool? fabVisible; + final bool? noSliver; const CustomTabContentList({ Key? key, @@ -27,7 +28,8 @@ class CustomTabContentList extends StatelessWidget { required this.onRefresh, this.refreshIndicatorOffset, this.fab, - this.fabVisible + this.fabVisible, + this.noSliver }) : super(key: key); @override @@ -36,95 +38,156 @@ class CustomTabContentList extends StatelessWidget { 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() + if (noSliver == true) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: loadingGenerator() + ); + } + else { + 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 Stack( - children: [ - SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (BuildContext context) { - return RefreshIndicator( - onRefresh: onRefresh, - edgeOffset: refreshIndicatorOffset ?? 95, - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (itemsCount > 0) SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => contentWidget(index), - childCount: itemsCount + if (noSliver == true) { + if (itemsCount > 0) { + return Stack( + children: [ + ListView.builder( + itemCount: itemsCount, + itemBuilder: (context, index) => contentWidget(index), + ), + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], + ); + } + else { + return Stack( + children: [ + noData, + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], + ); + } + } + else { + return Stack( + children: [ + SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return RefreshIndicator( + onRefresh: onRefresh, + edgeOffset: refreshIndicatorOffset ?? 95, + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ), - ), - if (itemsCount == 0) SliverFillRemaining( - child: noData, - ) - ], - ), - ); - }, + if (itemsCount > 0) SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => contentWidget(index), + childCount: itemsCount + ), + ), + if (itemsCount == 0) SliverFillRemaining( + child: noData, + ) + ], + ), + ); + }, + ), ), - ), - if (fab != null) AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible != null && fabVisible == true ? - appConfigProvider.showingSnackbar - ? 70 : 20 - : -70, - right: 20, - child: fab! - ), - ], - ); + if (fab != null) AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible != null && fabVisible == true ? + appConfigProvider.showingSnackbar + ? 70 : 20 + : -70, + right: 20, + child: fab! + ), + ], + ); + } 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() - ), - ) - ], + if (noSliver == true) { + return Padding( + padding: const EdgeInsets.only( + top: 95, + left: 16, + right: 16 ), - ) - ); + child: errorGenerator() + ); + } + else { + 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();