From d08a94ac2cad0fb707c81494566986625e82b772 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 08:08:07 +1100 Subject: [PATCH] Refactor paginated search widgets - Implement a base class - Override specific members --- lib/widget/company_list.dart | 126 +++--------------------- lib/widget/paginator.dart | 139 ++++++++++++++++++++++++++ lib/widget/part_list.dart | 145 +++++----------------------- lib/widget/purchase_order_list.dart | 140 +++------------------------ lib/widget/stock_list.dart | 129 ++++--------------------- 5 files changed, 206 insertions(+), 473 deletions(-) diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index 948cf25a..6d2a3f4e 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -5,6 +5,7 @@ import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; import "package:inventree/api.dart"; import "package:inventree/inventree/company.dart"; +import 'package:inventree/inventree/model.dart'; import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; @@ -56,96 +57,25 @@ class PaginatedCompanyList extends StatefulWidget { final Function(int)? onTotalChanged; @override - _CompanyListState createState() => _CompanyListState(filters, onTotalChanged); + _CompanyListState createState() => _CompanyListState(filters); } -class _CompanyListState extends State { +class _CompanyListState extends PaginatedSearchState { - _CompanyListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; + _CompanyListState(Map filters) : super(filters); - String _searchTerm = ""; - - Function(int)? onTotalChanged; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - final TextEditingController searchController = TextEditingController(); - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); - - super.initState(); + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreeCompany().listPaginated(limit, offset, filters: params); + + return page; } - + @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } - - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - Map params = filters; + Widget buildItem(BuildContext context, InvenTreeModel model) { - params["search"] = _searchTerm; - - final page = await InvenTreeCompany().listPaginated( - _pageSize, pageKey, filters: params); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - List companies = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreeCompany) { - companies.add(result); - } else { - print(result.jsondata); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(companies); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(companies, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - Widget _buildCompany(BuildContext context, InvenTreeCompany company) { + InvenTreeCompany company = model as InvenTreeCompany; return ListTile( title: Text(company.name), @@ -160,36 +90,4 @@ class _CompanyListState extends State { }, ); } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildCompany(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().companyNoResults); - } - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } - } \ No newline at end of file diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 6d5eba0c..d46ec19d 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -1,8 +1,147 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:inventree/inventree/model.dart'; +import 'package:inventree/inventree/sentry.dart'; import "package:inventree/l10.dart"; +class PaginatedSearchState extends State { + + PaginatedSearchState(this.filters); + + final Map filters; + + static const _pageSize = 25; + + // Search query term + String searchTerm = ""; + + int resultCount = 0; + + // Text controller + final TextEditingController searchController = TextEditingController(); + + // Pagination controller + final PagingController _pagingController = PagingController(firstPageKey: 0); + + @override + void initState() { + _pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + Future requestPage(int limit, int offset, Map params) async { + + print("Blank request page"); + // Default implementation returns null - must be overridden + return null; + } + + Future _fetchPage(int pageKey) async { + try { + Map params = filters; + + params["search"] = "${searchTerm}"; + + final page = await requestPage( + _pageSize, + pageKey, + params + ); + + int pageLength = page?.length ?? 0; + int pageCount = page?.count ?? 0; + + final isLastPage = pageLength < _pageSize; + + List items = []; + + if (page != null) { + for (var result in page.results) { + if (result is InvenTreeModel) { + items.add(result); + } + } + } + + if (isLastPage) { + _pagingController.appendLastPage(items); + } else { + final int nextPageKey = pageKey + pageLength; + _pagingController.appendPage(items, nextPageKey); + } + + setState(() { + resultCount = pageCount; + }); + } catch (error, stackTrace) { + _pagingController.error = error; + + sentryReportError(error, stackTrace); + } + } + + void updateSearchTerm() { + searchTerm = searchController.text; + _pagingController.refresh(); + } + + Widget buildItem(BuildContext context, InvenTreeModel item) { + + // This method must be overridden by the child class + return ListTile( + title: Text("*** UNIMPLEMENTED ***"), + subtitle: Text("*** buildItem() is unimplemented for this widget!"), + ); + } + + String get noResultsText => L10().noResults; + + @override + Widget build (BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + // TODO - Search input + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return buildItem(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget(noResultsText); + } + ), + separatorBuilder: (context, item) => const Divider(height: 1), + ) + ] + ) + ) + ] + ); + } + +} + + class PaginatedSearchWidget extends StatelessWidget { const PaginatedSearchWidget(this.controller, this.onChanged, this.results); diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart index 88725877..fc4635f9 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part_list.dart @@ -1,14 +1,15 @@ -import 'package:flutter/material.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/refreshable_state.dart'; +import "package:flutter/material.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import 'package:inventree/inventree/model.dart'; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; -import '../api.dart'; -import '../app_settings.dart'; -import '../l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_settings.dart"; +import "package:inventree/l10.dart"; class PartList extends StatefulWidget { @@ -52,86 +53,21 @@ class PaginatedPartList extends StatefulWidget { } -class _PaginatedPartListState extends State { +class _PaginatedPartListState extends PaginatedSearchState { - _PaginatedPartListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; - - String _searchTerm = ""; + _PaginatedPartListState(Map filters, this.onTotalChanged) : super(filters); Function(int)? onTotalChanged; - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); + Future requestPage(int limit, int offset, Map params) async { + final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); - super.initState(); - } + params["cascade"] = "${cascade}"; - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } + final page = await InvenTreePart().listPaginated(limit, offset, filters: params); - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = _searchTerm; - - final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreePart().listPaginated(_pageSize, pageKey, filters: params); - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of part objects - List parts = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreePart) { - parts.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(parts); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(parts, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } + return page; } void _openPart(BuildContext context, int pk) { @@ -144,7 +80,11 @@ class _PaginatedPartListState extends State { }); } - Widget _buildPart(BuildContext context, InvenTreePart part) { + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreePart part = model as InvenTreePart; + return ListTile( title: Text(part.fullname), subtitle: Text("${part.description}"), @@ -159,43 +99,4 @@ class _PaginatedPartListState extends State { }, ); } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildPart(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().partNoResults); - }, - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } } \ No newline at end of file diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index 67633221..0c284cfc 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -3,6 +3,7 @@ import "package:flutter/material.dart"; import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; import "package:inventree/inventree/company.dart"; +import 'package:inventree/inventree/model.dart'; import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/purchase_order_detail.dart"; @@ -43,117 +44,35 @@ class _PurchaseOrderListWidgetState extends RefreshableState filters; - final Function(int)? onTotalChanged; - @override - _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(filters, onTotalChanged); + _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(filters); } -class _PaginatedPurchaseOrderListState extends State { +class _PaginatedPurchaseOrderListState extends PaginatedSearchState { - _PaginatedPurchaseOrderListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; - - String _searchTerm = ""; - - Function(int)? onTotalChanged; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - final TextEditingController searchController = TextEditingController(); + _PaginatedPurchaseOrderListState(Map filters) : super(filters); @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); + Future requestPage(int limit, int offset, Map params) async { + + params["outstanding"] = "true"; + + final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params); + + return page; - super.initState(); } @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } + Widget buildItem(BuildContext context, InvenTreeModel model) { - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - Map params = {}; - - params["search"] = _searchTerm; - - // Only return results for open purchase orders - params["outstanding"] = "true"; - - // Copy across provided filters - for (String key in filters.keys) { - params[key] = filters[key] ?? ""; - } - - final page = await InvenTreePurchaseOrder().listPaginated( - _pageSize, - pageKey, - filters: params - ); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - List orders = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreePurchaseOrder) { - orders.add(result); - } else { - print("Result is not valid PurchaseOrder:"); - print(result.jsondata); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(orders); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(orders, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - Widget _buildOrder(BuildContext context, InvenTreePurchaseOrder order) { + InvenTreePurchaseOrder order = model as InvenTreePurchaseOrder; InvenTreeCompany? supplier = order.supplier; @@ -176,35 +95,4 @@ class _PaginatedPurchaseOrderListState extends State }, ); } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildOrder(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().companyNoResults); - } - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } } \ No newline at end of file diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart index f3affc50..96040860 100644 --- a/lib/widget/stock_list.dart +++ b/lib/widget/stock_list.dart @@ -2,6 +2,7 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import 'package:inventree/inventree/model.dart'; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/widget/paginator.dart"; @@ -51,84 +52,25 @@ class PaginatedStockItemList extends StatefulWidget { } -class _PaginatedStockItemListState extends State { +class _PaginatedStockItemListState extends PaginatedSearchState { - _PaginatedStockItemListState(this.filters); - - static const _pageSize = 25; - - String _searchTerm = ""; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); + _PaginatedStockItemListState(Map filters) : super(filters); @override - String getAppbarTitle(BuildContext context) => L10().stockItems; + Future requestPage(int limit, int offset, Map params) async { - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); + // Do we include stock items from sub-locations? + final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); - super.initState(); - } + params["cascade"] = "${cascade}"; - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } + final page = await InvenTreeStockItem().listPaginated( + limit, + offset, + filters: params + ); - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = "${_searchTerm}"; - - // Do we include stock items from sub-locations? - final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of stock item objects - List items = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreeStockItem) { - items.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(items); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(items, nextPageKey); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } + return page; } void _openItem(BuildContext context, int pk) { @@ -139,7 +81,11 @@ class _PaginatedStockItemListState extends State { }); } - Widget _buildItem(BuildContext context, InvenTreeStockItem item) { + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreeStockItem item = model as InvenTreeStockItem; + return ListTile( title: Text("${item.partName}"), subtitle: Text("${item.locationPathString}"), @@ -159,43 +105,4 @@ class _PaginatedStockItemListState extends State { }, ); } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build (BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - // TODO - Search input - PagedSliverList.separated( - pagingController: _pagingController, - 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), - ) - ] - ) - ) - ] - ); - } } \ No newline at end of file