From acf89426cec238ef3837ad7dc409a317a22e6197 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 19 May 2022 20:38:28 +1000 Subject: [PATCH 1/3] Fix for search screen - Change input and controller - Add focus node - Add "searching" indicator --- assets/release_notes.md | 5 +++ lib/l10n/app_en.arb | 3 ++ lib/widget/search.dart | 88 +++++++++++++++++++++++++++++++++-------- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/assets/release_notes.md b/assets/release_notes.md index ca14a6c8..e240c26d 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,6 +1,11 @@ ## InvenTree App Release Notes --- +### 0.7.1 - May 2022 +--- + +- Fixes issue which prevented text input in search window + ### 0.7.0 - May 2022 --- diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3b13d9cf..f6e60649 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -797,6 +797,9 @@ "description": "search" }, + "searching": "Searching", + "@searching": {}, + "searchLocation": "Search for location", "@searchLocation": {}, diff --git a/lib/widget/search.dart b/lib/widget/search.dart index 2d21fc66..96cf42f9 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -36,6 +36,19 @@ class _SearchDisplayState extends RefreshableState { final bool hasAppBar; + @override + void initState() { + super.initState(); + + _focusNode = FocusNode(); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + @override String getAppBarTitle(BuildContext context) => L10().search; @@ -52,6 +65,17 @@ class _SearchDisplayState extends RefreshableState { Timer? debounceTimer; + bool isSearching() { + + if (searchController.text.isEmpty) { + return false; + } + + return nSearchResults < 6; + } + + int nSearchResults = 0; + int nPartResults = 0; int nCategoryResults = 0; @@ -64,6 +88,8 @@ class _SearchDisplayState extends RefreshableState { int nPurchaseOrderResults = 0; + late FocusNode _focusNode; + // Callback when the text is being edited // Incorporates a debounce timer to restrict search frequency void onSearchTextChanged(String text, {bool immediate = false}) { @@ -84,6 +110,10 @@ class _SearchDisplayState extends RefreshableState { Future search(String term) async { + setState(() { + nSearchResults = 0; + }); + if (term.isEmpty) { setState(() { // Do not search on an empty string @@ -93,6 +123,8 @@ class _SearchDisplayState extends RefreshableState { nLocationResults = 0; nSupplierResults = 0; nPurchaseOrderResults = 0; + + nSearchResults = 0; }); return; @@ -104,6 +136,8 @@ class _SearchDisplayState extends RefreshableState { ).then((int n) { setState(() { nPartResults = n; + + nSearchResults++; }); }); @@ -113,6 +147,8 @@ class _SearchDisplayState extends RefreshableState { ).then((int n) { setState(() { nCategoryResults = n; + + nSearchResults++; }); }); @@ -122,6 +158,8 @@ class _SearchDisplayState extends RefreshableState { ).then((int n) { setState(() { nStockResults = n; + + nSearchResults++; }); }); @@ -131,6 +169,8 @@ class _SearchDisplayState extends RefreshableState { ).then((int n) { setState(() { nLocationResults = n; + + nSearchResults++; }); }); @@ -143,6 +183,7 @@ class _SearchDisplayState extends RefreshableState { ).then((int n) { setState(() { nSupplierResults = n; + nSearchResults++; }); }); @@ -155,6 +196,7 @@ class _SearchDisplayState extends RefreshableState { ).then((int n) { setState(() { nPurchaseOrderResults = n; + nSearchResults++; }); }); @@ -166,29 +208,31 @@ class _SearchDisplayState extends RefreshableState { // Search input tiles.add( - InputDecorator( + TextFormField( decoration: InputDecoration( - ), - child: ListTile( - title: TextField( - readOnly: false, - decoration: InputDecoration( - helperText: L10().queryEmpty, - ), - controller: searchController, - onChanged: (String text) { - onSearchTextChanged(text); - }, + hintText: L10().queryEmpty, + prefixIcon: IconButton( + icon: FaIcon(FontAwesomeIcons.search), + onPressed: null, ), - trailing: IconButton( + suffixIcon: IconButton( icon: FaIcon(FontAwesomeIcons.backspace, color: Colors.red), onPressed: () { searchController.clear(); onSearchTextChanged("", immediate: true); - }, + _focusNode.requestFocus(); + } ), - ) - ) + ), + readOnly: false, + autofocus: true, + autocorrect: false, + focusNode: _focusNode, + controller: searchController, + onChanged: (String text) { + onSearchTextChanged(text); + }, + ), ); String query = searchController.text; @@ -335,7 +379,17 @@ class _SearchDisplayState extends RefreshableState { ); } - if (results.isEmpty && searchController.text.isNotEmpty) { + if (isSearching()) { + tiles.add( + ListTile( + title: Text(L10().searching), + leading: FaIcon(FontAwesomeIcons.search), + trailing: CircularProgressIndicator(), + ) + ); + } + + if (!isSearching() && results.isEmpty && searchController.text.isNotEmpty) { tiles.add( ListTile( title: Text(L10().queryNoResults), From 941ee5e1725975b6fbba9f4a44c2fb0c70e14286 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 19 May 2022 21:06:20 +1000 Subject: [PATCH 2/3] Prevent "old" search results from mucking up the displayed data - Only accept results if the search term has not changed --- lib/inventree/model.dart | 2 - lib/widget/search.dart | 103 ++++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index decb7aaa..550bc2f5 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -216,8 +216,6 @@ class InvenTreeModel { if (response.isValid()) { int n = int.tryParse(response.data["count"].toString()) ?? 0; - - print("${URL} -> ${n} results"); return n; } else { return 0; diff --git a/lib/widget/search.dart b/lib/widget/search.dart index 96cf42f9..1a59237e 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -105,78 +105,78 @@ class _SearchDisplayState extends RefreshableState { search(text); }); } - } + /* + * Initiate multiple search requests to the server. + * Each request returns at *some point* in the future, + * by which time the search input may have changed, giving unexpected results. + * + * So, each request only causes an update *if* the search term is still the same when it completes + */ Future search(String term) async { setState(() { + // Do not search on an empty string + nPartResults = 0; + nCategoryResults = 0; + nStockResults = 0; + nLocationResults = 0; + nSupplierResults = 0; + nPurchaseOrderResults = 0; + nSearchResults = 0; }); if (term.isEmpty) { - setState(() { - // Do not search on an empty string - nPartResults = 0; - nCategoryResults = 0; - nStockResults = 0; - nLocationResults = 0; - nSupplierResults = 0; - nPurchaseOrderResults = 0; - - nSearchResults = 0; - }); - return; } // Search parts - InvenTreePart().count( - searchQuery: term - ).then((int n) { - setState(() { - nPartResults = n; - - nSearchResults++; - }); + InvenTreePart().count(searchQuery: term).then((int n) { + if (term == searchController.text) { + setState(() { + nPartResults = n; + nSearchResults++; + }); + } }); // Search part categories - InvenTreePartCategory().count( - searchQuery: term, - ).then((int n) { - setState(() { - nCategoryResults = n; - - nSearchResults++; - }); + InvenTreePartCategory().count(searchQuery: term,).then((int n) { + if (term == searchController.text) { + setState(() { + nCategoryResults = n; + nSearchResults++; + }); + } }); // Search stock items - InvenTreeStockItem().count( - searchQuery: term - ).then((int n) { - setState(() { - nStockResults = n; - - nSearchResults++; - }); + InvenTreeStockItem().count(searchQuery: term).then((int n) { + if (term == searchController.text) { + setState(() { + nStockResults = n; + nSearchResults++; + }); + } }); // Search stock locations - InvenTreeStockLocation().count( - searchQuery: term - ).then((int n) { - setState(() { - nLocationResults = n; + InvenTreeStockLocation().count(searchQuery: term).then((int n) { + if (term == searchController.text) { + setState(() { + nLocationResults = n; - nSearchResults++; - }); + nSearchResults++; + }); + } }); + // TDOO: Re-implement this once display for companies has been fixed + /* // Search suppliers - InvenTreeCompany().count( - searchQuery: term, + InvenTreeCompany().count(searchQuery: term, filters: { "is_supplier": "true", }, @@ -186,6 +186,7 @@ class _SearchDisplayState extends RefreshableState { nSearchResults++; }); }); + */ // Search purchase orders InvenTreePurchaseOrder().count( @@ -194,10 +195,12 @@ class _SearchDisplayState extends RefreshableState { "outstanding": "true" } ).then((int n) { - setState(() { - nPurchaseOrderResults = n; - nSearchResults++; - }); + if (term == searchController.text) { + setState(() { + nPurchaseOrderResults = n; + nSearchResults++; + }); + } }); } From 11157b7c77da15ab6578ec48957b8540694579e9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 19 May 2022 21:11:46 +1000 Subject: [PATCH 3/3] Fix search counter --- lib/widget/search.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/widget/search.dart b/lib/widget/search.dart index 1a59237e..c2bc63f5 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -4,7 +4,6 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/widget/part_list.dart"; import "package:inventree/widget/purchase_order_list.dart"; @@ -71,7 +70,7 @@ class _SearchDisplayState extends RefreshableState { return false; } - return nSearchResults < 6; + return nSearchResults < 5; } int nSearchResults = 0; @@ -405,8 +404,11 @@ class _SearchDisplayState extends RefreshableState { } } - return tiles; + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + return tiles; } @override