mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-30 21:05:42 +00:00 
			
		
		
		
	Refactoring of paginated views
This commit is contained in:
		| @@ -13,6 +13,7 @@ import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/part_detail.dart'; | ||||
| import 'package:InvenTree/widget/drawer.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:InvenTree/widget/paginator.dart'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| @@ -155,7 +156,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   Widget getCategoryDescriptionCard() { | ||||
|   Widget getCategoryDescriptionCard({bool extra = true}) { | ||||
|     if (category == null) { | ||||
|       return Card( | ||||
|         child: ListTile( | ||||
| @@ -163,16 +164,18 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | ||||
|         ) | ||||
|       ); | ||||
|     } else { | ||||
|       return Card( | ||||
|         child: Column( | ||||
|           children: <Widget>[ | ||||
|             ListTile( | ||||
|               title: Text("${category.name}", | ||||
|                   style: TextStyle(fontWeight: FontWeight.bold) | ||||
|               ), | ||||
|               subtitle: Text("${category.description}"), | ||||
|             ), | ||||
|             Divider(), | ||||
|  | ||||
|       List<Widget> children = [ | ||||
|         ListTile( | ||||
|           title: Text("${category.name}", | ||||
|               style: TextStyle(fontWeight: FontWeight.bold) | ||||
|           ), | ||||
|           subtitle: Text("${category.description}"), | ||||
|         ), | ||||
|       ]; | ||||
|  | ||||
|       if (extra) { | ||||
|         children.add( | ||||
|             ListTile( | ||||
|               title: Text(I18N.of(context).parentCategory), | ||||
|               subtitle: Text("${category.parentpathstring}"), | ||||
| @@ -190,7 +193,12 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | ||||
|                 } | ||||
|               }, | ||||
|             ) | ||||
|           ] | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       return Card( | ||||
|         child: Column( | ||||
|           children: children | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| @@ -250,7 +258,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | ||||
|   List<Widget> actionTiles() { | ||||
|  | ||||
|     List<Widget> tiles = [ | ||||
|       getCategoryDescriptionCard(), | ||||
|       getCategoryDescriptionCard(extra: false), | ||||
|       ListTile( | ||||
|         title: Text(I18N.of(context).actions, | ||||
|           style: TextStyle(fontWeight: FontWeight.bold) | ||||
| @@ -263,6 +271,8 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   int partCount = 0; | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|  | ||||
| @@ -272,7 +282,33 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | ||||
|           children: detailTiles() | ||||
|         ); | ||||
|       case 1: | ||||
|         return PaginatedPartList({"category": "${category?.pk ?? null}"}); | ||||
|         return Column( | ||||
|           mainAxisAlignment: MainAxisAlignment.start, | ||||
|           children: [ | ||||
|             getCategoryDescriptionCard(extra: false), | ||||
|             ListTile( | ||||
|               title: Text( | ||||
|                 I18N.of(context).parts, | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               trailing: Text( | ||||
|                 "${partCount}", | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|             ), | ||||
|             Divider(height: 3), | ||||
|             Expanded( | ||||
|               child: PaginatedPartList( | ||||
|                 {"category": "${category?.pk ?? null}"}, | ||||
|                 onTotalChanged: (int total) { | ||||
|                   setState(() { | ||||
|                     partCount = total; | ||||
|                   }); | ||||
|                 }, | ||||
|               ) | ||||
|             ) | ||||
|           ], | ||||
|         ); | ||||
|       case 2: | ||||
|         return ListView( | ||||
|           children: actionTiles() | ||||
| @@ -335,10 +371,12 @@ class PaginatedPartList extends StatefulWidget { | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   PaginatedPartList(this.filters); | ||||
|   Function onTotalChanged; | ||||
|  | ||||
|   PaginatedPartList(this.filters, {this.onTotalChanged}); | ||||
|  | ||||
|   @override | ||||
|   _PaginatedPartListState createState() => _PaginatedPartListState(filters); | ||||
|   _PaginatedPartListState createState() => _PaginatedPartListState(filters, onTotalChanged); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -348,9 +386,11 @@ class _PaginatedPartListState extends State<PaginatedPartList> { | ||||
|  | ||||
|   String _searchTerm; | ||||
|  | ||||
|   Function onTotalChanged; | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   _PaginatedPartListState(this.filters); | ||||
|   _PaginatedPartListState(this.filters, this.onTotalChanged); | ||||
|  | ||||
|   final PagingController<int, InvenTreePart> _pagingController = PagingController(firstPageKey: 0); | ||||
|  | ||||
| @@ -397,6 +437,10 @@ class _PaginatedPartListState extends State<PaginatedPartList> { | ||||
|         _pagingController.appendPage(parts, nextPageKey); | ||||
|       } | ||||
|  | ||||
|       if (onTotalChanged != null) { | ||||
|         onTotalChanged(page.count); | ||||
|       } | ||||
|  | ||||
|     } catch (error) { | ||||
|       print("Error! - ${error.toString()}"); | ||||
|       _pagingController.error = error; | ||||
| @@ -437,34 +481,25 @@ class _PaginatedPartListState extends State<PaginatedPartList> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return CustomScrollView( | ||||
|       shrinkWrap: true, | ||||
|       physics: ClampingScrollPhysics(), | ||||
|       scrollDirection: Axis.vertical, | ||||
|       slivers: <Widget>[ | ||||
|         // TODO: Introduce searching within the list | ||||
|         /* | ||||
|         SliverToBoxAdapter(child: TextField( | ||||
|             onChanged: updateSearchTerm, | ||||
|           ) | ||||
|         ), | ||||
|          */ | ||||
|         //PaginatedSearch(callback: updateSearchTerm), | ||||
|         PagedSliverList.separated( | ||||
|             pagingController: _pagingController, | ||||
|             builderDelegate: PagedChildBuilderDelegate<InvenTreePart>( | ||||
|                 itemBuilder: (context, item, index) { | ||||
|                   return _buildPart(context, item); | ||||
|                 } | ||||
|                 }, | ||||
|               noItemsFoundIndicatorBuilder: (context) { | ||||
|                   return NoResultsWidget("No parts found"); | ||||
|               } | ||||
|             ), | ||||
|             separatorBuilder: (context, index) => const Divider(height: 1), | ||||
|         ), | ||||
|       ] | ||||
|     ); | ||||
|  | ||||
|     return PagedListView<int, InvenTreePart>.separated( | ||||
|         pagingController: _pagingController, | ||||
|         builderDelegate: PagedChildBuilderDelegate<InvenTreePart>( | ||||
|           itemBuilder: (context, item, index) { | ||||
|             return _buildPart(context, item); | ||||
|           } | ||||
|         ), | ||||
|         separatorBuilder: (context, index) => const Divider(height: 1), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/search.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/stock_detail.dart'; | ||||
| import 'package:InvenTree/widget/paginator.dart'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -131,8 +132,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   List<InvenTreeStockItem> _items = List<InvenTreeStockItem>(); | ||||
|  | ||||
|   @override | ||||
|   Future<void> onBuild(BuildContext context) async { | ||||
|     refresh(); | ||||
| @@ -162,7 +161,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|     setState(() {}); | ||||
|   } | ||||
|  | ||||
|   Widget locationDescriptionCard() { | ||||
|   Widget locationDescriptionCard({bool includeActions = true}) { | ||||
|     if (location == null) { | ||||
|       return Card( | ||||
|         child: ListTile( | ||||
| @@ -170,13 +169,16 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|         ) | ||||
|       ); | ||||
|     } else { | ||||
|       return Card( | ||||
|         child: Column( | ||||
|           children: <Widget> [ | ||||
|             ListTile( | ||||
|               title: Text("${location.name}"), | ||||
|               subtitle: Text("${location.description}"), | ||||
|             ), | ||||
|  | ||||
|       List<Widget> children = [ | ||||
|         ListTile( | ||||
|           title: Text("${location.name}"), | ||||
|           subtitle: Text("${location.description}"), | ||||
|         ), | ||||
|       ]; | ||||
|  | ||||
|       if (includeActions) { | ||||
|         children.add( | ||||
|             ListTile( | ||||
|               title: Text("Parent Category"), | ||||
|               subtitle: Text("${location.parentpathstring}"), | ||||
| @@ -193,7 +195,12 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|                 } | ||||
|               }, | ||||
|             ) | ||||
|           ] | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       return Card( | ||||
|         child: Column( | ||||
|           children: children, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
| @@ -222,6 +229,8 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   int stockItemCount = 0; | ||||
|  | ||||
|   Widget getSelectedWidget(int index) { | ||||
|     switch (index) { | ||||
|       case 0: | ||||
| @@ -229,7 +238,30 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> { | ||||
|           children: detailTiles(), | ||||
|         ); | ||||
|       case 1: | ||||
|         return PaginatedStockList({"location": "${location?.pk ?? -1}"}); | ||||
|         return Column( | ||||
|           mainAxisAlignment: MainAxisAlignment.start, | ||||
|           children: [ | ||||
|             locationDescriptionCard(includeActions: false), | ||||
|             ListTile( | ||||
|               title: Text( | ||||
|                 I18N.of(context).stockItems, | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               trailing: Text("${stockItemCount}") | ||||
|             ), | ||||
|             Divider(height: 3), | ||||
|             Expanded( | ||||
|               child: PaginatedStockList( | ||||
|                 {"location": "${location?.pk ?? -1}"}, | ||||
|                 onTotalChanged: (int total) { | ||||
|                   setState(() { | ||||
|                     stockItemCount = total; | ||||
|                   }); | ||||
|                 } | ||||
|               ), | ||||
|             ) | ||||
|           ] | ||||
|         ); | ||||
|       case 2: | ||||
|         return ListView( | ||||
|           children: ListTile.divideTiles( | ||||
| @@ -275,40 +307,10 @@ List<Widget> detailTiles() { | ||||
|   } | ||||
|  | ||||
|  | ||||
|   List<Widget> stockTiles() { | ||||
|     List<Widget> tiles = [ | ||||
|       locationDescriptionCard(), | ||||
|       ListTile( | ||||
|         title: Text( | ||||
|             I18N.of(context).stockItems, | ||||
|             style: TextStyle(fontWeight: FontWeight.bold) | ||||
|         ), | ||||
|         trailing: _items.length > 0 ? Text("${_items.length}") : null, | ||||
|       ) | ||||
|     ]; | ||||
|  | ||||
|     /* | ||||
|  | ||||
|     if (loading) { | ||||
|       tiles.add(progressIndicator()); | ||||
|     } else if (_items.length > 0) { | ||||
|       tiles.add(StockList(_items)); | ||||
|     } else { | ||||
|       tiles.add(ListTile( | ||||
|         title: Text("No Stock Items"), | ||||
|         subtitle: Text("No stock items available in this location") | ||||
|       )); | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   List<Widget> actionTiles() { | ||||
|     List<Widget> tiles = []; | ||||
|  | ||||
|     tiles.add(locationDescriptionCard()); | ||||
|     tiles.add(locationDescriptionCard(includeActions: false)); | ||||
|  | ||||
|     // Stock adjustment actions | ||||
|     if (InvenTreeAPI().checkPermission('stock', 'change')) { | ||||
| @@ -403,10 +405,12 @@ class PaginatedStockList extends StatefulWidget { | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   PaginatedStockList(this.filters); | ||||
|   Function onTotalChanged; | ||||
|  | ||||
|   PaginatedStockList(this.filters, {this.onTotalChanged}); | ||||
|  | ||||
|   @override | ||||
|   _PaginatedStockListState createState() => _PaginatedStockListState(filters); | ||||
|   _PaginatedStockListState createState() => _PaginatedStockListState(filters, onTotalChanged); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -418,7 +422,9 @@ class _PaginatedStockListState extends State<PaginatedStockList> { | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   _PaginatedStockListState(this.filters); | ||||
|   Function onTotalChanged; | ||||
|  | ||||
|   _PaginatedStockListState(this.filters, this.onTotalChanged); | ||||
|  | ||||
|   final PagingController<int, InvenTreeStockItem> _pagingController = PagingController(firstPageKey: 0); | ||||
|  | ||||
| @@ -464,6 +470,11 @@ class _PaginatedStockListState extends State<PaginatedStockList> { | ||||
|         final int nextPageKey = pageKey + page.length; | ||||
|         _pagingController.appendPage(items, nextPageKey); | ||||
|       } | ||||
|  | ||||
|       if (onTotalChanged != null) { | ||||
|         onTotalChanged(page.count); | ||||
|       } | ||||
|  | ||||
|     } catch (error) { | ||||
|       _pagingController.error = error; | ||||
|     } | ||||
| @@ -498,6 +509,9 @@ class _PaginatedStockListState extends State<PaginatedStockList> { | ||||
|   @override | ||||
|   Widget build (BuildContext context) { | ||||
|     return CustomScrollView( | ||||
|       shrinkWrap: true, | ||||
|       physics: ClampingScrollPhysics(), | ||||
|       scrollDirection: Axis.vertical, | ||||
|       slivers: <Widget>[ | ||||
|         // TODO - Search input | ||||
|         PagedSliverList.separated( | ||||
| @@ -505,6 +519,9 @@ class _PaginatedStockListState extends State<PaginatedStockList> { | ||||
|             builderDelegate: PagedChildBuilderDelegate<InvenTreeStockItem>( | ||||
|               itemBuilder: (context, item, index) { | ||||
|                 return _buildItem(context, item); | ||||
|               }, | ||||
|               noItemsFoundIndicatorBuilder: (context) { | ||||
|                 return NoResultsWidget("No stock items found"); | ||||
|               } | ||||
|             ), | ||||
|             separatorBuilder: (context, item) => const Divider(height: 1), | ||||
|   | ||||
							
								
								
									
										44
									
								
								lib/widget/paginator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								lib/widget/paginator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| // Pagination related widgets | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class PaginatedSearch extends StatelessWidget { | ||||
|  | ||||
|   Function callback; | ||||
|  | ||||
|   PaginatedSearch({this.callback}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SliverToBoxAdapter( | ||||
|       child: TextField( | ||||
|           onChanged: callback, | ||||
|           decoration: InputDecoration( | ||||
|             hintText: "Search", | ||||
|           ), | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class NoResultsWidget extends StatelessWidget { | ||||
|  | ||||
|   final String description; | ||||
|  | ||||
|   NoResultsWidget(this.description); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|  | ||||
|     return ListTile( | ||||
|       title: Text(I18N.of(context).noResults), | ||||
|       subtitle: Text(description), | ||||
|       leading: FaIcon(FontAwesomeIcons.exclamationCircle), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -427,6 +427,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> { | ||||
|     return tiles; | ||||
|   } | ||||
|  | ||||
|   int stockItemCount = 0; | ||||
|  | ||||
|   Widget getSelectedWidget(int index) { | ||||
|     switch (index) { | ||||
| @@ -440,17 +441,30 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> { | ||||
|         ), | ||||
|       ); | ||||
|       case 1: | ||||
|         return PaginatedStockList({"part": "${part.pk}"}); | ||||
|         /* | ||||
|         return Center( | ||||
|           child: ListView( | ||||
|             children: ListTile.divideTiles( | ||||
|               context: context, | ||||
|               tiles: stockTiles() | ||||
|             ).toList() | ||||
|           ) | ||||
|         return Column( | ||||
|           mainAxisAlignment: MainAxisAlignment.start, | ||||
|           children: [ | ||||
|             headerTile(), | ||||
|             ListTile( | ||||
|               title: Text( | ||||
|                 I18N.of(context).stockItems, | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               trailing: Text("${stockItemCount}") | ||||
|             ), | ||||
|             Divider(height: 3), | ||||
|             Expanded( | ||||
|               child: PaginatedStockList( | ||||
|                 {"part": "${part.pk}"}, | ||||
|                 onTotalChanged: (int total) { | ||||
|                   setState(() { | ||||
|                     stockItemCount = total; | ||||
|                   }); | ||||
|                 }, | ||||
|               ) | ||||
|             ) | ||||
|           ], | ||||
|         ); | ||||
|          */ | ||||
|       case 2: | ||||
|         return Center( | ||||
|           child: ListView( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user