2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 05:26:47 +00:00

Keyboard fix (#306)

* Remove focusNode in search widget

- Was causing some issues in iOS in particular

* Improve search UX by cancelling previous query

- Prevent successive search queries from being displayed

* Update release notes

* Add "type" for cancelable operation
This commit is contained in:
Oliver 2023-04-10 17:07:06 +10:00 committed by GitHub
parent 020cc4497c
commit efb7ff4170
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 50 deletions

View File

@ -1,6 +1,13 @@
## InvenTree App Release Notes
---
### 0.11.1 - April 2023
---
- Fixes keyboard bug in search widget
- Adds ability to create new purchase orders directly from the app
- Adds support for the "contact" field to purchase orders
### 0.11.0 - April 2023
---

View File

@ -1,5 +1,5 @@
import "dart:async";
import "package:async/async.dart";
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
@ -40,16 +40,10 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
final bool hasAppBar;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
}
CancelableOperation<void>? _search_query;
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@ -99,8 +93,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
int nSupplierResults = 0;
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}) {
@ -114,9 +106,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
} else {
debounceTimer = Timer(Duration(milliseconds: 250), () {
search(text);
if (!_focusNode.hasFocus) {
_focusNode.requestFocus();
}
});
}
}
@ -147,12 +136,36 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
return count;
}
// Actually perform the search query
Future<void> _perform_search(Map<String, dynamic> body) async {
InvenTreeAPI().post(
"search/",
body: body,
expectedStatusCode: 200).then((APIResponse response) {
decrementPendingSearches();
Map<String, dynamic> results = {};
if (response.data is Map<String, dynamic>) {
results = response.data as Map<String, dynamic>;
}
if (mounted) {
setState(() {
nPartResults = getSearchResultCount(results, "part");
nCategoryResults = getSearchResultCount(results, "partcategory");
nStockResults = getSearchResultCount(results, "stockitem");
nLocationResults = getSearchResultCount(results, "stocklocation");
nSupplierResults = 0; //getSearchResultCount(results, "")
nPurchaseOrderResults = getSearchResultCount(results, "purchaseorder");
});
}
});
}
/*
* 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
* Callback when the search input is changed
*/
Future<void> search(String term) async {
var api = InvenTreeAPI();
@ -173,6 +186,15 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
nPendingSearches = 0;
});
// Cancel the previous search query (if in progress)
if (_search_query != null) {
if (!_search_query!.isCanceled) {
_search_query!.cancel();
}
}
_search_query = null;
if (term.isEmpty) {
return;
}
@ -216,29 +238,9 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
if (body.isNotEmpty) {
nPendingSearches++;
api.post(
"search/",
body: body,
expectedStatusCode: 200).then((APIResponse response) {
decrementPendingSearches();
Map<String, dynamic> results = {};
if (response.data is Map<String, dynamic>) {
results = response.data as Map<String, dynamic>;
}
if (mounted) {
setState(() {
nPartResults = getSearchResultCount(results, "part");
nCategoryResults = getSearchResultCount(results, "partcategory");
nStockResults = getSearchResultCount(results, "stockitem");
nLocationResults = getSearchResultCount(results, "stocklocation");
nSupplierResults = 0; //getSearchResultCount(results, "")
nPurchaseOrderResults = getSearchResultCount(results, "purchaseorder");
});
}
});
_search_query = CancelableOperation.fromFuture(
_perform_search(body),
);
}
} else {
legacySearch(term);
@ -344,16 +346,13 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
),
key: _formKey,
readOnly: false,
autofocus: false,
autofocus: true,
autocorrect: false,
focusNode: _focusNode,
controller: searchController,
onChanged: (String text) {
onSearchTextChanged(text);
_focusNode.requestFocus();
},
onFieldSubmitted: (String text) {
_focusNode.requestFocus();
},
),
trailing: GestureDetector(
@ -363,7 +362,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
),
onTap: () {
searchController.clear();
_focusNode.requestFocus();
onSearchTextChanged("", immediate: true);
},
),
@ -541,10 +539,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
}
}
if (!_focusNode.hasFocus) {
_focusNode.requestFocus();
}
return tiles;
}