part of '../widgets.dart'; typedef SearchKey = GlobalKey; typedef Query = X?; /// ----------------------------------------------- /// [EnhancedListView] /// ----------------------------------------------- abstract interface class EnhancedListView extends StatefulWidget { const EnhancedListView({super.key}); } abstract interface class EnhancedListViewState extends State {} /// ----------------------------------------------- /// [EnhancedLocalListView] /// ----------------------------------------------- class EnhancedLocalListView extends EnhancedListView { final List list; final Widget Function(T) itemBuilder; final bool Function(T, String) filter; final Widget header; final List Function(String)? onSearch; EnhancedLocalListView({ Key? key, required this.list, required this.itemBuilder, required this.filter, List 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 createState() => EnhancedLocalListViewState(); } class EnhancedLocalListViewState extends State> { TextEditingController editingController = TextEditingController(); late List 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: [ 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 extends EnhancedListView { final Widget Function(BuildContext, T, int) bodyBuilder; final Future> Function() headerItems; Widget Function(Future> Function() gen) headerBuilder; final PagingController pagingController; final Future<(bool, List?)> 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 createState() => EnhancedRemoteListViewState(); } class EnhancedRemoteListViewState extends State> 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 _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: [ buildPaginatedListView( 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), ), ), ), ), ], ); } }