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:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -60,7 +62,7 @@ class _AccessSettingsWidgetState extends State<AccessSettingsWidget> with Ticker
@override @override
void initState() { void initState() {
if (mounted) fetchClients(); fetchClients();
super.initState(); super.initState();
tabController = TabController( tabController = TabController(
initialIndex: 0, initialIndex: 0,
@ -72,48 +74,9 @@ class _AccessSettingsWidgetState extends State<AccessSettingsWidget> with Ticker
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context); final serversProvider = Provider.of<ServersProvider>(context);
return Scaffold(
body: DefaultTabController( Widget body() {
length: 3, return TabBarView(
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,
),
]
)
),
),
)
];
}),
body: TabBarView(
controller: tabController, controller: tabController,
children: [ children: [
ClientsList( ClientsList(
@ -141,9 +104,84 @@ class _AccessSettingsWidgetState extends State<AccessSettingsWidget> with Ticker
fetchClients: fetchClients 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: 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 { class AddClientModal extends StatefulWidget {
final String type; final String type;
final void Function(String, String) onConfirm; final void Function(String, String) onConfirm;
final bool dialog;
const AddClientModal({ const AddClientModal({
Key? key, Key? key,
required this.type, required this.type,
required this.onConfirm required this.onConfirm,
required this.dialog,
}) : super(key: key); }) : super(key: key);
@override @override
@ -65,32 +67,26 @@ class _AddClientModalState extends State<AddClientModal> {
} }
} }
Widget content() {
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
height: Platform.isIOS ? 321 : 305,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( child: Wrap(
color: Theme.of(context).dialogBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28)
)
),
child: Column(
children: [ children: [
Expanded( Row(
child: ListView( mainAxisAlignment: MainAxisAlignment.center,
physics: (Platform.isIOS ? 338 : 322) < MediaQuery.of(context).size.height
? const NeverScrollableScrollPhysics()
: null,
children: [ children: [
Icon( Icon(
icon(), icon(),
size: 24, size: 24,
color: Theme.of(context).listTileTheme.iconColor color: Theme.of(context).listTileTheme.iconColor
), ),
const SizedBox(height: 16), ],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text( Text(
title(), title(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -99,7 +95,9 @@ class _AddClientModalState extends State<AddClientModal> {
color: Theme.of(context).colorScheme.onSurface color: Theme.of(context).colorScheme.onSurface
), ),
), ),
const SizedBox(height: 16), ],
),
),
TextFormField( TextFormField(
controller: fieldController, controller: fieldController,
onChanged: (_) => checkValidValues(), onChanged: (_) => checkValidValues(),
@ -117,9 +115,6 @@ class _AddClientModalState extends State<AddClientModal> {
: AppLocalizations.of(context)!.domain, : AppLocalizations.of(context)!.domain,
), ),
), ),
],
),
),
Padding( Padding(
padding: const EdgeInsets.only(top: 24), padding: const EdgeInsets.only(top: 24),
child: Row( child: Row(
@ -129,7 +124,7 @@ class _AddClientModalState extends State<AddClientModal> {
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel) child: Text(AppLocalizations.of(context)!.cancel)
), ),
const SizedBox(width: 20), const SizedBox(width: 16),
TextButton( TextButton(
onPressed: validData == true onPressed: validData == true
? () { ? () {
@ -152,7 +147,36 @@ class _AddClientModalState extends State<AddClientModal> {
if (Platform.isIOS) const SizedBox(height: 16) 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 // ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -68,6 +70,8 @@ class _ClientsListState extends State<ClientsList> {
final serversProvider = Provider.of<ServersProvider>(context); final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
final width = MediaQuery.of(context).size.width;
void confirmRemoveItem(String client, String type) async { void confirmRemoveItem(String client, String type) async {
Map<String, List<String>> body = { Map<String, List<String>> body = {
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [], "allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
@ -209,6 +213,7 @@ class _ClientsListState extends State<ClientsList> {
} }
return CustomTabContentList( return CustomTabContentList(
noSliver: !(Platform.isAndroid || Platform.isIOS) ? true : false,
loadingGenerator: () => SizedBox( loadingGenerator: () => SizedBox(
width: double.maxFinite, width: double.maxFinite,
height: MediaQuery.of(context).size.height-171, height: MediaQuery.of(context).size.height-171,
@ -362,15 +367,28 @@ class _ClientsListState extends State<ClientsList> {
refreshIndicatorOffset: 0, refreshIndicatorOffset: 0,
fab: FloatingActionButton( fab: FloatingActionButton(
onPressed: () { onPressed: () {
if (width > 900) {
showDialog(
context: context,
builder: (context) => AddClientModal(
type: widget.type,
onConfirm: confirmAddItem,
dialog: true,
),
);
}
else {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => AddClientModal( builder: (context) => AddClientModal(
type: widget.type, type: widget.type,
onConfirm: confirmAddItem onConfirm: confirmAddItem,
dialog: false,
), ),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isScrollControlled: true isScrollControlled: true
); );
}
}, },
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),

View file

@ -15,6 +15,7 @@ class CustomTabContentList extends StatelessWidget {
final double? refreshIndicatorOffset; final double? refreshIndicatorOffset;
final Widget? fab; final Widget? fab;
final bool? fabVisible; final bool? fabVisible;
final bool? noSliver;
const CustomTabContentList({ const CustomTabContentList({
Key? key, Key? key,
@ -27,7 +28,8 @@ class CustomTabContentList extends StatelessWidget {
required this.onRefresh, required this.onRefresh,
this.refreshIndicatorOffset, this.refreshIndicatorOffset,
this.fab, this.fab,
this.fabVisible this.fabVisible,
this.noSliver
}) : super(key: key); }) : super(key: key);
@override @override
@ -36,6 +38,13 @@ class CustomTabContentList extends StatelessWidget {
switch (loadStatus) { switch (loadStatus) {
case LoadStatus.loading: case LoadStatus.loading:
if (noSliver == true) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: loadingGenerator()
);
}
else {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,
@ -55,9 +64,50 @@ class CustomTabContentList extends StatelessWidget {
), ),
) )
); );
}
case LoadStatus.loaded: case LoadStatus.loaded:
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( return Stack(
children: [ children: [
SafeArea( SafeArea(
@ -100,8 +150,20 @@ class CustomTabContentList extends StatelessWidget {
), ),
], ],
); );
}
case LoadStatus.error: case LoadStatus.error:
if (noSliver == true) {
return Padding(
padding: const EdgeInsets.only(
top: 95,
left: 16,
right: 16
),
child: errorGenerator()
);
}
else {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,
@ -125,6 +187,7 @@ class CustomTabContentList extends StatelessWidget {
), ),
) )
); );
}
default: default:
return const SizedBox(); return const SizedBox();