flutter-freaccess-hub/lib/shared/widgets/list_view.dart

326 lines
11 KiB
Dart

part of '../widgets.dart';
typedef SearchKey = GlobalKey<EnhancedRemoteListViewState>;
typedef Query<X extends Archive> = X?;
/// -----------------------------------------------
/// [EnhancedListView]
/// -----------------------------------------------
abstract interface class EnhancedListView<T> extends StatefulWidget {
const EnhancedListView({super.key});
}
abstract interface class EnhancedListViewState<T>
extends State<EnhancedListView> {}
/// -----------------------------------------------
/// [EnhancedLocalListView]
/// -----------------------------------------------
class EnhancedLocalListView<T> extends EnhancedListView<T> {
final List<T> list;
final Widget Function(T) itemBuilder;
final bool Function(T, String) filter;
final Widget header;
final List<T> Function(String)? onSearch;
EnhancedLocalListView({
Key? key,
required this.list,
required this.itemBuilder,
required this.filter,
List<T> Function(String)? onSearch,
Widget? header,
}) : header = header ?? const SizedBox.shrink(),
onSearch = onSearch ??
((String query) =>
list.where((documents) => filter(documents, query)).toList()),
super(key: key);
// return documents.where((documents) => filter(documents, query)).toList();
@override
EnhancedLocalListViewState<T> createState() =>
EnhancedLocalListViewState<T>();
}
class EnhancedLocalListViewState<T> extends State<EnhancedLocalListView<T>> {
TextEditingController editingController = TextEditingController();
late List<T> filteredItems;
@override
void initState() {
filteredItems = widget.list;
super.initState();
}
@override
Widget build(BuildContext context) {
void filter(value) {
safeSetState(() {
filteredItems = widget.onSearch!(value);
});
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: filteredItems.length + 1,
itemBuilder: (context, index) {
if (index == 0) return widget.header;
return widget.itemBuilder(filteredItems[index - 1]);
},
),
),
Padding(
padding: const EdgeInsets.all(30.0),
child: TextFormField(
controller: editingController,
onChanged: filter,
cursorColor: Colors.black,
cursorWidth: 2.0,
cursorRadius: Radius.circular(2.0),
style: TextStyle(
color: Colors.black,
fontSize: 16.0,
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
autocorrect: true,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Colors.black),
labelText: 'Pesquisar',
labelStyle: TextStyle(
color: Colors.black,
fontSize: 16.0,
),
hintText: 'Digite sua pesquisa',
hintStyle: TextStyle(
color: Colors.grey,
fontSize: 14.0,
),
filled: true,
fillColor: Colors.white,
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: Colors.blue),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
),
)),
],
);
}
}
/// -----------------------------------------------
/// [EnhancedRemoteListView]
/// -----------------------------------------------
// ignore: must_be_immutable
class EnhancedRemoteListView<T, Y> extends EnhancedListView<T> {
final Widget Function(BuildContext, T, int) bodyBuilder;
final Future<List<Y?>> Function() headerItems;
Widget Function<T>(Future<List<T?>> Function() gen) headerBuilder;
final PagingController<int, T> pagingController;
final Future<(bool, List<T?>?)> Function(int pageKey, Query query)
dataProvider;
final void Function(Object, StackTrace) onFetchError;
EnhancedRemoteListView({
Key? key,
// required this.fetchItems,
required this.bodyBuilder,
required this.headerItems,
required this.headerBuilder,
required this.pagingController,
required this.dataProvider,
required this.onFetchError,
}) : super(key: key);
@override
EnhancedRemoteListViewState<T, Y> createState() =>
EnhancedRemoteListViewState<T, Y>();
}
class EnhancedRemoteListViewState<T, Y>
extends State<EnhancedRemoteListView<T, Y>> with Pageable {
TextEditingController editingController = TextEditingController();
bool isLoading = false;
Query query = Document.fromDesc('');
@override
void initState() {
widget.pagingController.addPageRequestListener(
(page) => fetchPage(
dataProvider: () async => await widget.dataProvider(page, query),
onDataUnavailable: () => showNoMoreDataSnackBar(context),
onDataAvailable: (data) =>
widget.pagingController.appendLastPage(data),
onFetchError: (e, s) => widget.onFetchError),
);
widget.pagingController.addStatusListener(_showError);
super.initState();
}
Future<void> _showError(PagingStatus status) async {
if (status == PagingStatus.subsequentPageError) {
final message = FFLocalizations.of(context).getVariableText(
enText: 'Something went wrong while fetching a new page.',
ptText: 'Algo deu errado ao buscar uma nova página.',
);
final retry = FFLocalizations.of(context).getVariableText(
enText: 'Retry',
ptText: 'Recarregar',
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
action: SnackBarAction(
label: retry,
onPressed: () => widget.pagingController.retryLastFailedRequest(),
),
),
);
}
}
void filter(Query data) async {
if (data is Category) {
safeSetState(() => query = Category(
id: data.id,
color: data.color,
title: data.title,
));
widget.pagingController.refresh();
} else if (data is Document) {
log('filter: ${data.description}');
safeSetState(() => query = data);
widget.pagingController.refresh();
} else {
safeSetState(() {
query = Document.fromDesc('');
});
widget.pagingController.refresh();
}
}
@override
Widget build(BuildContext context) {
final noDataFound = FFLocalizations.of(context).getVariableText(
ptText: "Nenhum item encontrado!",
enText: "No item found",
);
final theme = FlutterFlowTheme.of(context);
final locale = FFLocalizations.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
buildPaginatedListView<int, T, Y>(
noDataFound,
widget.pagingController,
widget.headerItems,
widget.headerBuilder,
widget.bodyBuilder,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: editingController,
onChanged: (value) => EasyDebounce.debounce(
'_model.keyTextFieldTextController',
const Duration(milliseconds: 500),
() => filter(Document.fromDesc(value)),
),
cursorColor: theme.primaryText,
showCursor: false,
cursorWidth: 2.0,
cursorRadius: Radius.circular(100),
style: TextStyle(
color: theme.primaryText,
fontSize: 16.0,
decorationColor: Colors.amber,
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
autocorrect: true,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: theme.primary),
labelText: locale.getVariableText(
ptText: 'Pesquisar',
enText: 'Search',
),
labelStyle: TextStyle(
color: theme.primaryText,
fontSize: 16.0,
),
hintText: locale.getVariableText(
ptText: 'Digite sua pesquisa',
enText: 'Enter your search',
),
hintStyle: TextStyle(
color: theme.accent2,
fontSize: 14.0,
),
filled: true,
fillColor: Colors.transparent,
helperStyle: TextStyle(
color: theme.primaryText,
decorationColor: theme.primaryText,
),
focusColor: theme.primaryText,
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
enabledBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
focusedBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
errorBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
focusedErrorBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText, width: 2.0),
),
),
),
),
],
);
}
}