diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 64ba7417..0914f8ec 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -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 { }); } - Widget getCategoryDescriptionCard() { + Widget getCategoryDescriptionCard({bool extra = true}) { if (category == null) { return Card( child: ListTile( @@ -163,16 +164,18 @@ class _CategoryDisplayState extends RefreshableState { ) ); } else { - return Card( - child: Column( - children: [ - ListTile( - title: Text("${category.name}", - style: TextStyle(fontWeight: FontWeight.bold) - ), - subtitle: Text("${category.description}"), - ), - Divider(), + + List 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 { } }, ) - ] + ); + } + + return Card( + child: Column( + children: children ), ); } @@ -250,7 +258,7 @@ class _CategoryDisplayState extends RefreshableState { List actionTiles() { List 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 { return tiles; } + int partCount = 0; + @override Widget getBody(BuildContext context) { @@ -272,7 +282,33 @@ class _CategoryDisplayState extends RefreshableState { 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 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 { String _searchTerm; + Function onTotalChanged; + final Map filters; - _PaginatedPartListState(this.filters); + _PaginatedPartListState(this.filters, this.onTotalChanged); final PagingController _pagingController = PagingController(firstPageKey: 0); @@ -397,6 +437,10 @@ class _PaginatedPartListState extends State { _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 { @override Widget build(BuildContext context) { return CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, slivers: [ // TODO: Introduce searching within the list - /* - SliverToBoxAdapter(child: TextField( - onChanged: updateSearchTerm, - ) - ), - */ + //PaginatedSearch(callback: updateSearchTerm), PagedSliverList.separated( pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { return _buildPart(context, item); - } + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget("No parts found"); + } ), separatorBuilder: (context, index) => const Divider(height: 1), ), ] ); - - return PagedListView.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildPart(context, item); - } - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ); } } diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 59570b1e..401d9733 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -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 { } } - List _items = List(); - @override Future onBuild(BuildContext context) async { refresh(); @@ -162,7 +161,7 @@ class _LocationDisplayState extends RefreshableState { setState(() {}); } - Widget locationDescriptionCard() { + Widget locationDescriptionCard({bool includeActions = true}) { if (location == null) { return Card( child: ListTile( @@ -170,13 +169,16 @@ class _LocationDisplayState extends RefreshableState { ) ); } else { - return Card( - child: Column( - children: [ - ListTile( - title: Text("${location.name}"), - subtitle: Text("${location.description}"), - ), + + List 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 { } }, ) - ] + ); + } + + return Card( + child: Column( + children: children, ) ); } @@ -222,6 +229,8 @@ class _LocationDisplayState extends RefreshableState { ); } + int stockItemCount = 0; + Widget getSelectedWidget(int index) { switch (index) { case 0: @@ -229,7 +238,30 @@ class _LocationDisplayState extends RefreshableState { 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 detailTiles() { } - List stockTiles() { - List 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 actionTiles() { List 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 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 { final Map filters; - _PaginatedStockListState(this.filters); + Function onTotalChanged; + + _PaginatedStockListState(this.filters, this.onTotalChanged); final PagingController _pagingController = PagingController(firstPageKey: 0); @@ -464,6 +470,11 @@ class _PaginatedStockListState extends State { 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 { @override Widget build (BuildContext context) { return CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, slivers: [ // TODO - Search input PagedSliverList.separated( @@ -505,6 +519,9 @@ class _PaginatedStockListState extends State { builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { return _buildItem(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget("No stock items found"); } ), separatorBuilder: (context, item) => const Divider(height: 1), diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart new file mode 100644 index 00000000..6eea72b4 --- /dev/null +++ b/lib/widget/paginator.dart @@ -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), + ); + } + +} + diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 8da38ffc..72f8a24a 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -427,6 +427,7 @@ class _PartDisplayState extends RefreshableState { return tiles; } + int stockItemCount = 0; Widget getSelectedWidget(int index) { switch (index) { @@ -440,17 +441,30 @@ class _PartDisplayState extends RefreshableState { ), ); 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(