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/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/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..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"; @@ -36,6 +35,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 +64,17 @@ class _SearchDisplayState extends RefreshableState { Timer? debounceTimer; + bool isSearching() { + + if (searchController.text.isEmpty) { + return false; + } + + return nSearchResults < 5; + } + + int nSearchResults = 0; + int nPartResults = 0; int nCategoryResults = 0; @@ -64,6 +87,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}) { @@ -79,72 +104,88 @@ 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 { - if (term.isEmpty) { - setState(() { - // Do not search on an empty string - nPartResults = 0; - nCategoryResults = 0; - nStockResults = 0; - nLocationResults = 0; - nSupplierResults = 0; - nPurchaseOrderResults = 0; - }); + 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) { return; } // Search parts - InvenTreePart().count( - searchQuery: term - ).then((int n) { - setState(() { - nPartResults = n; - }); + 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; - }); + 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; - }); + 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++; + }); + } }); + // 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", }, ).then((int n) { setState(() { nSupplierResults = n; + nSearchResults++; }); }); + */ // Search purchase orders InvenTreePurchaseOrder().count( @@ -153,9 +194,12 @@ class _SearchDisplayState extends RefreshableState { "outstanding": "true" } ).then((int n) { - setState(() { - nPurchaseOrderResults = n; - }); + if (term == searchController.text) { + setState(() { + nPurchaseOrderResults = n; + nSearchResults++; + }); + } }); } @@ -166,29 +210,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 +381,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), @@ -348,8 +404,11 @@ class _SearchDisplayState extends RefreshableState { } } - return tiles; + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + return tiles; } @override