From 138cae2da077c31945a1a78ed7ceba5bf3431e63 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 30 Jun 2023 22:42:59 +1000 Subject: [PATCH] Search improvements (#388) * Refactor search widget - visual improvements - Simplifications - Add refresh button - Improve search button * Track original search * fix BOM widget * Update release notes --- assets/release_notes.md | 2 +- lib/inventree/stock.dart | 11 +++- lib/widget/bom_list.dart | 6 +- lib/widget/category_display.dart | 43 ++---------- lib/widget/category_list.dart | 22 ++----- lib/widget/company_list.dart | 9 ++- lib/widget/location_display.dart | 37 +---------- lib/widget/location_list.dart | 22 ++----- lib/widget/paginator.dart | 94 ++++++++++++++++++++++----- lib/widget/part_detail.dart | 4 +- lib/widget/part_list.dart | 20 ++---- lib/widget/part_parameter_widget.dart | 10 +-- lib/widget/po_line_list.dart | 5 +- lib/widget/purchase_order_detail.dart | 4 +- lib/widget/purchase_order_list.dart | 21 ++---- lib/widget/refreshable_state.dart | 3 +- lib/widget/stock_item_history.dart | 10 +-- lib/widget/stock_list.dart | 22 ++----- lib/widget/supplier_part_list.dart | 24 ++----- 19 files changed, 158 insertions(+), 211 deletions(-) diff --git a/assets/release_notes.md b/assets/release_notes.md index b1155f07..e709bd9c 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -2,7 +2,7 @@ --- - Pre-fill stock location when transferring stock amount - +- UX improvements for searching data ### - 0.12.3 - June 2023 --- diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index f6cca996..22113d4e 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -117,7 +117,16 @@ class InvenTreeStockItemHistory extends InvenTreeModel { } } - String get userString => getString("username", subKey: "user_detail"); + int? get user => getValue("user") as int?; + + String get userString { + + if (user != null) { + return getString("username", subKey: "user_detail"); + } else { + return ""; + } + } } diff --git a/lib/widget/bom_list.dart b/lib/widget/bom_list.dart index f1d867dd..a39b2773 100644 --- a/lib/widget/bom_list.dart +++ b/lib/widget/bom_list.dart @@ -80,7 +80,6 @@ class _BillOfMaterialsState extends RefreshableState { Expanded( child: PaginatedBomList( filters, - showSearch: showFilterOptions, isParentPart: widget.isParentComponent, ), ), @@ -95,10 +94,13 @@ class _BillOfMaterialsState extends RefreshableState { */ class PaginatedBomList extends PaginatedSearchWidget { - const PaginatedBomList(Map filters, {bool showSearch = false, this.isParentPart = true}) : super(filters: filters, showSearch: showSearch); + const PaginatedBomList(Map filters, {this.isParentPart = true}) : super(filters: filters); final bool isParentPart; + @override + String get searchTitle => L10().parts; + @override _PaginatedBomListState createState() => _PaginatedBomListState(); } diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 31ead036..261840af 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -30,8 +30,6 @@ class _CategoryDisplayState extends RefreshableState { _CategoryDisplayState(); - bool showFilterOptions = false; - @override String getAppBarTitle() => L10().partCategory; @@ -204,26 +202,12 @@ class _CategoryDisplayState extends RefreshableState { List tiles = [ getCategoryDescriptionCard(), - ListTile( - title: Text( - L10().subcategories, - style: TextStyle(fontWeight: FontWeight.bold) - ), - trailing: GestureDetector( - child: FaIcon(FontAwesomeIcons.filter), - onTap: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ), Expanded( child: PaginatedPartCategoryList( - { - "parent": widget.category?.pk.toString() ?? "null" - }, - showFilterOptions, + { + "parent": widget.category?.pk.toString() ?? "null" + }, + title: L10().subcategories, ), flex: 10, ) @@ -240,25 +224,8 @@ class _CategoryDisplayState extends RefreshableState { }; return [ - ListTile( - title: Text( - L10().parts, - style: TextStyle(fontWeight: FontWeight.bold), - ), - trailing: GestureDetector( - child: FaIcon(FontAwesomeIcons.filter), - onTap: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ), - ), Expanded( - child: PaginatedPartList( - filters, - showFilterOptions, - ), + child: PaginatedPartList(filters), flex: 10, ) ]; diff --git a/lib/widget/category_list.dart b/lib/widget/category_list.dart index 0686faf9..c1c95514 100644 --- a/lib/widget/category_list.dart +++ b/lib/widget/category_list.dart @@ -1,5 +1,4 @@ import "package:flutter/material.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/part.dart"; @@ -26,32 +25,21 @@ class _PartCategoryListState extends RefreshableState { _PartCategoryListState(); - bool showFilterOptions = false; - - @override - List appBarActions(BuildContext context) => [ - IconButton( - icon: FaIcon(FontAwesomeIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; - @override String getAppBarTitle() => L10().partCategories; @override Widget getBody(BuildContext context) { - return PaginatedPartCategoryList(widget.filters, showFilterOptions); + return PaginatedPartCategoryList(widget.filters); } } class PaginatedPartCategoryList extends PaginatedSearchWidget { - const PaginatedPartCategoryList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedPartCategoryList(Map filters, {String title = ""}) : super(filters: filters, title: title); + + @override + String get searchTitle => title.isNotEmpty ? title : L10().partCategories; @override _PaginatedPartCategoryListState createState() => _PaginatedPartCategoryListState(); diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index fe6ecca7..a19762c2 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -36,14 +36,19 @@ class _CompanyListWidgetState extends RefreshableState { @override Widget getBody(BuildContext context) { - return PaginatedCompanyList(widget.filters, true); + return PaginatedCompanyList(widget.title, widget.filters); } } class PaginatedCompanyList extends PaginatedSearchWidget { - const PaginatedCompanyList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedCompanyList(this.companyTitle, Map filters) : super(filters: filters); + + final String companyTitle; + + @override + String get searchTitle => companyTitle; @override _CompanyListState createState() => _CompanyListState(); diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index b76586d6..f1da729e 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -38,8 +38,6 @@ class _LocationDisplayState extends RefreshableState { final InvenTreeStockLocation? location; - bool showFilterOptions = false; - @override String getAppBarTitle() { return L10().stockLocation; @@ -345,26 +343,12 @@ class _LocationDisplayState extends RefreshableState { List detailTiles() { List tiles = [ locationDescriptionCard(), - ListTile( - title: Text( - L10().sublocations, - style: TextStyle(fontWeight: FontWeight.bold), - ), - trailing: GestureDetector( - child: FaIcon(FontAwesomeIcons.filter), - onTap: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ), Expanded( child: PaginatedStockLocationList( { "parent": location?.pk.toString() ?? "null", }, - showFilterOptions, + title: L10().sublocations, ), flex: 10, ) @@ -380,25 +364,8 @@ class _LocationDisplayState extends RefreshableState { }; return [ - ListTile( - title: Text( - L10().stock, - style: TextStyle(fontWeight: FontWeight.bold), - ), - trailing: GestureDetector( - child: FaIcon(FontAwesomeIcons.filter), - onTap: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ), - ), Expanded( - child: PaginatedStockItemList( - filters, - showFilterOptions, - ), + child: PaginatedStockItemList(filters), flex: 10, ) ]; diff --git a/lib/widget/location_list.dart b/lib/widget/location_list.dart index 73c992e6..fe396593 100644 --- a/lib/widget/location_list.dart +++ b/lib/widget/location_list.dart @@ -1,5 +1,4 @@ import "package:flutter/material.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/stock.dart"; @@ -27,33 +26,22 @@ class _StockLocationListState extends RefreshableState { final Map filters; - bool showFilterOptions = false; - - @override - List appBarActions(BuildContext context) => [ - IconButton( - icon: FaIcon(FontAwesomeIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; - @override String getAppBarTitle() => L10().stockLocations; @override Widget getBody(BuildContext context) { - return PaginatedStockLocationList(filters, showFilterOptions); + return PaginatedStockLocationList(filters); } } class PaginatedStockLocationList extends PaginatedSearchWidget { - const PaginatedStockLocationList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedStockLocationList(Map filters, {String title = ""}) : super(filters: filters, title: title); + + @override + String get searchTitle => title.isNotEmpty ? title : L10().stockLocations; @override _PaginatedStockLocationListState createState() => _PaginatedStockLocationListState(); diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 70ef6cb8..a08b749e 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -19,11 +19,13 @@ import "package:inventree/widget/refreshable_state.dart"; */ abstract class PaginatedSearchWidget extends StatefulWidget { - const PaginatedSearchWidget({this.filters = const {}, this.showSearch = false}); + const PaginatedSearchWidget({this.filters = const {}, this.title = ""}); + + final String title; + + String get searchTitle => title; final Map filters; - - final bool showSearch; } @@ -34,6 +36,8 @@ abstract class PaginatedSearchState extends Sta static const _pageSize = 25; + bool showSearchWidget = false; + // Prefix for storing and loading pagination options // Override in implementing class String get prefix => "prefix_"; @@ -116,7 +120,7 @@ abstract class PaginatedSearchState extends Sta } // Update the (configurable) filters for this paginated list - Future _saveOrderingOptions(BuildContext context) async { + Future _setOrderingOptions(BuildContext context) async { // Retrieve stored setting dynamic _field = await orderingField(); dynamic _order = await orderingOrder(); @@ -281,7 +285,17 @@ abstract class PaginatedSearchState extends Sta // Include user search term if (searchTerm.isNotEmpty) { - params["search"] = "${searchTerm}"; + + String _search = searchTerm; + + // Include original search in search test + String original = params["original_search"] ?? ""; + + if (original.isNotEmpty) { + _search = "${original} ${_search}"; + } + + params["search"] = "${_search}"; } // Use custom query ordering if available @@ -369,9 +383,12 @@ abstract class PaginatedSearchState extends Sta @override Widget build (BuildContext context) { - List children = []; + List children = [ + buildTitleWidget(context), + Divider(), + ]; - if (widget.showSearch) { + if (showSearchWidget) { children.add(buildSearchInput(context)); } @@ -392,7 +409,7 @@ abstract class PaginatedSearchState extends Sta return NoResultsWidget(noResultsText); } ), - separatorBuilder: (context, item) => const Divider(height: 1), + separatorBuilder: (context, item) => const Divider(height: .1), ) ] ) @@ -405,17 +422,65 @@ abstract class PaginatedSearchState extends Sta ); } + /* + * Build the title widget for this list + */ + Widget buildTitleWidget(BuildContext context) { + + const double icon_size = 32; + + List _icons = []; + + if (filterOptions.isNotEmpty || orderingOptions.isNotEmpty) { + _icons.add(IconButton( + onPressed: () async { + _setOrderingOptions(context); + }, + icon: Icon(Icons.filter_alt, size: icon_size) + )); + } + + _icons.add(IconButton( + onPressed: () { + setState(() { + showSearchWidget = !showSearchWidget; + }); + }, + icon: Icon(showSearchWidget ? Icons.zoom_out : Icons.search, size: icon_size) + )); + + _icons.add(IconButton( + onPressed: () async { + updateSearchTerm(); + }, + icon: Icon(Icons.refresh, size: icon_size), + )); + + return ListTile( + title: Text( + widget.searchTitle, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + "${L10().results}: ${resultCount}", + style: TextStyle( + fontStyle: FontStyle.italic + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: _icons, + ), + ); + } + /* * Construct a search input text field for the user to enter a search term */ Widget buildSearchInput(BuildContext context) { return ListTile( - leading: orderingOptions.isEmpty ? null : GestureDetector( - child: Icon(Icons.filter_list, color: COLOR_ACTION, size: 32), - onTap: () async { - _saveOrderingOptions(context); - }, - ), trailing: GestureDetector( child: FaIcon( searchController.text.isEmpty ? FontAwesomeIcons.magnifyingGlass : FontAwesomeIcons.deleteLeft, @@ -435,7 +500,6 @@ abstract class PaginatedSearchState extends Sta }, decoration: InputDecoration( hintText: L10().search, - helperText: resultsString(), ), ) ); diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 9376837f..4943410c 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -681,11 +681,11 @@ class _PartDisplayState extends RefreshableState { ).toList() ) ), - PaginatedStockItemList({"part": part.pk.toString()}, true) + PaginatedStockItemList({"part": part.pk.toString()}) ]; if (showParameters) { - tabs.add(PaginatedParameterList({"part": part.pk.toString()}, true)); + tabs.add(PaginatedParameterList({"part": part.pk.toString()})); } return tabs; diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart index ba7c8391..6def9b1b 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part_list.dart @@ -1,5 +1,4 @@ import "package:flutter/material.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; import "package:inventree/l10.dart"; @@ -38,21 +37,9 @@ class _PartListState extends RefreshableState { @override String getAppBarTitle() => title.isNotEmpty ? title : L10().parts; - @override - List appBarActions(BuildContext context) => [ - IconButton( - icon: FaIcon(FontAwesomeIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; - @override Widget getBody(BuildContext context) { - return PaginatedPartList(filters, showFilterOptions); + return PaginatedPartList(filters); } } @@ -60,7 +47,10 @@ class _PartListState extends RefreshableState { class PaginatedPartList extends PaginatedSearchWidget { - const PaginatedPartList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedPartList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().parts; @override _PaginatedPartListState createState() => _PaginatedPartListState(); diff --git a/lib/widget/part_parameter_widget.dart b/lib/widget/part_parameter_widget.dart index d889be81..1196a4c6 100644 --- a/lib/widget/part_parameter_widget.dart +++ b/lib/widget/part_parameter_widget.dart @@ -44,10 +44,7 @@ class _ParameterWidgetState extends RefreshableState { return Column( children: [ Expanded( - child: PaginatedParameterList( - filters, - false, - ) + child: PaginatedParameterList(filters) ) ], ); @@ -60,7 +57,10 @@ class _ParameterWidgetState extends RefreshableState { */ class PaginatedParameterList extends PaginatedSearchWidget { - const PaginatedParameterList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedParameterList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().parts; @override _PaginatedParameterState createState() => _PaginatedParameterState(); diff --git a/lib/widget/po_line_list.dart b/lib/widget/po_line_list.dart index 13946a14..27827f81 100644 --- a/lib/widget/po_line_list.dart +++ b/lib/widget/po_line_list.dart @@ -17,7 +17,10 @@ import "package:inventree/widget/progress.dart"; */ class PaginatedPOLineList extends PaginatedSearchWidget { - const PaginatedPOLineList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedPOLineList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().lineItems; @override _PaginatedPOLineListState createState() => _PaginatedPOLineListState(); diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 8b2b6501..ba870a49 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -331,9 +331,9 @@ class _PurchaseOrderDetailState extends RefreshableState getTabs(BuildContext context) { return [ ListView(children: orderTiles(context)), - PaginatedPOLineList({"order": order.pk.toString()}, true), + PaginatedPOLineList({"order": order.pk.toString()}), // ListView(children: lineTiles(context)), - PaginatedStockItemList({"purchase_order": order.pk.toString()}, true), + PaginatedStockItemList({"purchase_order": order.pk.toString()}), ]; } diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index 913a7e2a..cdb8b8be 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -31,23 +31,9 @@ class _PurchaseOrderListWidgetState extends RefreshableState filters; - bool showFilterOptions = false; - @override String getAppBarTitle() => L10().purchaseOrders; - @override - List appBarActions(BuildContext context) => [ - IconButton( - icon: FaIcon(FontAwesomeIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; - @override List actionButtons(BuildContext context) { List actions = []; @@ -95,14 +81,17 @@ class _PurchaseOrderListWidgetState extends RefreshableState filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedPurchaseOrderList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().purchaseOrders; @override _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(); diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index e484265d..2bfc5734 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -270,8 +270,7 @@ abstract class RefreshableState extends State with appBar: buildAppBar(context, refreshableKey), drawer: getDrawer(context), floatingActionButton: buildSpeedDial(context), - floatingActionButtonLocation: FloatingActionButtonLocation - .miniEndDocked, + floatingActionButtonLocation: FloatingActionButtonLocation.miniEndDocked, body: RefreshIndicator( onRefresh: () async { refresh(context); diff --git a/lib/widget/stock_item_history.dart b/lib/widget/stock_item_history.dart index 3c3771e4..b2829fd2 100644 --- a/lib/widget/stock_item_history.dart +++ b/lib/widget/stock_item_history.dart @@ -22,8 +22,6 @@ class _StockItemHistoryDisplayState extends RefreshableState L10().stockItemHistory; @@ -36,7 +34,7 @@ class _StockItemHistoryDisplayState extends RefreshableState filters, bool showSearch) - : super(filters: filters, showSearch: showSearch); + const PaginatedStockHistoryList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().stockItemHistory; @override _PaginatedStockHistoryState createState() => _PaginatedStockHistoryState(); diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart index dd657a71..b260b248 100644 --- a/lib/widget/stock_list.dart +++ b/lib/widget/stock_list.dart @@ -1,5 +1,4 @@ import "package:flutter/material.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/stock.dart"; @@ -27,32 +26,21 @@ class _StockListState extends RefreshableState { final Map filters; - bool showFilterOptions = false; - @override String getAppBarTitle() => L10().stockItems; - @override - List appBarActions(BuildContext context) => [ - IconButton( - icon: FaIcon(FontAwesomeIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; - @override Widget getBody(BuildContext context) { - return PaginatedStockItemList(filters, showFilterOptions); + return PaginatedStockItemList(filters); } } class PaginatedStockItemList extends PaginatedSearchWidget { - const PaginatedStockItemList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedStockItemList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().stockItems; @override _PaginatedStockItemListState createState() => _PaginatedStockItemListState(); diff --git a/lib/widget/supplier_part_list.dart b/lib/widget/supplier_part_list.dart index 4b9c18f3..3674f66c 100644 --- a/lib/widget/supplier_part_list.dart +++ b/lib/widget/supplier_part_list.dart @@ -1,5 +1,4 @@ import "package:flutter/material.dart"; -import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; import "package:inventree/l10.dart"; @@ -31,23 +30,9 @@ class _SupplierPartListState extends RefreshableState { @override String getAppBarTitle() => L10().supplierParts; - bool showFilterOptions = false; - @override - List appBarActions(BuildContext context) => [ - IconButton( - icon: FaIcon(FontAwesomeIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; - - @override - Widget getBody(BuildContext context) { - return PaginatedSupplierPartList(widget.filters, showFilterOptions); + Widget getBody(BuildContext context) { + return PaginatedSupplierPartList(widget.filters); } } @@ -55,7 +40,10 @@ class _SupplierPartListState extends RefreshableState { class PaginatedSupplierPartList extends PaginatedSearchWidget { - const PaginatedSupplierPartList(Map filters, bool showSearch) : super(filters: filters, showSearch: showSearch); + const PaginatedSupplierPartList(Map filters) : super(filters: filters); + + @override + String get searchTitle => L10().supplierParts; @override _PaginatedSupplierPartListState createState() => _PaginatedSupplierPartListState();