mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +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:
parent
020cc4497c
commit
efb7ff4170
@ -1,6 +1,13 @@
|
|||||||
## InvenTree App Release Notes
|
## 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
|
### 0.11.0 - April 2023
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "dart:async";
|
import "dart:async";
|
||||||
|
import "package:async/async.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
@ -40,16 +40,10 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
|
|
||||||
final bool hasAppBar;
|
final bool hasAppBar;
|
||||||
|
|
||||||
@override
|
CancelableOperation<void>? _search_query;
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_focusNode = FocusNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_focusNode.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,8 +93,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
int nSupplierResults = 0;
|
int nSupplierResults = 0;
|
||||||
int nPurchaseOrderResults = 0;
|
int nPurchaseOrderResults = 0;
|
||||||
|
|
||||||
late FocusNode _focusNode;
|
|
||||||
|
|
||||||
// Callback when the text is being edited
|
// Callback when the text is being edited
|
||||||
// Incorporates a debounce timer to restrict search frequency
|
// Incorporates a debounce timer to restrict search frequency
|
||||||
void onSearchTextChanged(String text, {bool immediate = false}) {
|
void onSearchTextChanged(String text, {bool immediate = false}) {
|
||||||
@ -114,9 +106,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
} else {
|
} else {
|
||||||
debounceTimer = Timer(Duration(milliseconds: 250), () {
|
debounceTimer = Timer(Duration(milliseconds: 250), () {
|
||||||
search(text);
|
search(text);
|
||||||
if (!_focusNode.hasFocus) {
|
|
||||||
_focusNode.requestFocus();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,12 +136,36 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
return count;
|
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.
|
* Callback when the search input is changed
|
||||||
* 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<void> search(String term) async {
|
Future<void> search(String term) async {
|
||||||
var api = InvenTreeAPI();
|
var api = InvenTreeAPI();
|
||||||
@ -173,6 +186,15 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
nPendingSearches = 0;
|
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) {
|
if (term.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -216,29 +238,9 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
if (body.isNotEmpty) {
|
if (body.isNotEmpty) {
|
||||||
nPendingSearches++;
|
nPendingSearches++;
|
||||||
|
|
||||||
api.post(
|
_search_query = CancelableOperation.fromFuture(
|
||||||
"search/",
|
_perform_search(body),
|
||||||
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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
legacySearch(term);
|
legacySearch(term);
|
||||||
@ -344,16 +346,13 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
),
|
),
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
autofocus: false,
|
autofocus: true,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
focusNode: _focusNode,
|
|
||||||
controller: searchController,
|
controller: searchController,
|
||||||
onChanged: (String text) {
|
onChanged: (String text) {
|
||||||
onSearchTextChanged(text);
|
onSearchTextChanged(text);
|
||||||
_focusNode.requestFocus();
|
|
||||||
},
|
},
|
||||||
onFieldSubmitted: (String text) {
|
onFieldSubmitted: (String text) {
|
||||||
_focusNode.requestFocus();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
trailing: GestureDetector(
|
trailing: GestureDetector(
|
||||||
@ -363,7 +362,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
searchController.clear();
|
searchController.clear();
|
||||||
_focusNode.requestFocus();
|
|
||||||
onSearchTextChanged("", immediate: true);
|
onSearchTextChanged("", immediate: true);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -541,10 +539,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_focusNode.hasFocus) {
|
|
||||||
_focusNode.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiles;
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user