326 lines
11 KiB
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|