Optimized access settings

This commit is contained in:
Juan Gilsanz Polo 2023-04-30 23:36:02 +02:00
parent ef43f8b5dd
commit bc8e74be6d
4 changed files with 350 additions and 207 deletions

View file

@ -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<AccessSettingsWidget> with Ticker
@override
void initState() {
if (mounted) fetchClients();
fetchClients();
super.initState();
tabController = TabController(
initialIndex: 0,
@ -72,78 +74,114 @@ class _AccessSettingsWidgetState extends State<AccessSettingsWidget> with Ticker
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(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(),
);
}
}
}

View file

@ -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<AddClientModal> {
}
}
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<AddClientModal> {
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<AddClientModal> {
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<AddClientModal> {
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()
),
);
}
}
}

View file

@ -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<ClientsList> {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final width = MediaQuery.of(context).size.width;
void confirmRemoveItem(String client, String type) async {
Map<String, List<String>> body = {
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
@ -209,6 +213,7 @@ class _ClientsListState extends State<ClientsList> {
}
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<ClientsList> {
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),
),

View file

@ -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: <Widget>[
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: <Widget>[
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();