perf(ui): debounce species/name lookups in the edit sheet
Typing in the edit sheet ran heavy DB work on every keystroke: a SQL LIKE search on the species field, and a full species-catalog load plus O(n^2) label matching on the name field. That made typing laggy. Debounce both lookups (250ms) so they only run once the grower pauses, and drop stale results with a per-field query counter so an earlier response can't flicker an outdated suggestion back in.
This commit is contained in:
parent
b840b83c42
commit
913e058122
1 changed files with 34 additions and 4 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
|
|
@ -619,8 +620,20 @@ class _EditVarietySheetState extends State<_EditVarietySheet> {
|
|||
late int? _seedHarvestMonths = widget.detail.seedHarvestMonths;
|
||||
late bool _calendarOpen = widget.detail.hasCropCalendar;
|
||||
|
||||
// Typing stays responsive by not touching the DB on every keystroke: each
|
||||
// field waits until the grower pauses before running its (heavy) lookup, and
|
||||
// a per-field query counter drops any result that a later keystroke has
|
||||
// already superseded, so stale suggestions never flicker back in.
|
||||
static const _debounce = Duration(milliseconds: 250);
|
||||
Timer? _speciesDebounce;
|
||||
Timer? _nameDebounce;
|
||||
int _speciesQueryId = 0;
|
||||
int _nameQueryId = 0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_speciesDebounce?.cancel();
|
||||
_nameDebounce?.cancel();
|
||||
_name.dispose();
|
||||
_category.dispose();
|
||||
_notes.dispose();
|
||||
|
|
@ -628,21 +641,38 @@ class _EditVarietySheetState extends State<_EditVarietySheet> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onSpeciesQuery(String query) async {
|
||||
void _onSpeciesQuery(String query) {
|
||||
_speciesDebounce?.cancel();
|
||||
_speciesDebounce = Timer(_debounce, () => _runSpeciesQuery(query));
|
||||
}
|
||||
|
||||
Future<void> _runSpeciesQuery(String query) async {
|
||||
final queryId = ++_speciesQueryId;
|
||||
final lang = Localizations.localeOf(context).languageCode;
|
||||
final results = await widget.species.search(query, languageCode: lang);
|
||||
// A manual species search takes over from the name-based suggestion.
|
||||
if (mounted) setState(() => _suggestions = results);
|
||||
if (mounted && queryId == _speciesQueryId) {
|
||||
setState(() => _suggestions = results);
|
||||
}
|
||||
}
|
||||
|
||||
void _onNameChanged(String name) {
|
||||
if (widget.detail.speciesId != null || _pickedSpeciesId != null) return;
|
||||
_nameDebounce?.cancel();
|
||||
_nameDebounce = Timer(_debounce, () => _runNameClassify(name));
|
||||
}
|
||||
|
||||
/// As the name is typed, offer the species it names — but only when the
|
||||
/// variety has no species yet and the grower hasn't picked one, so an
|
||||
/// existing link is never second-guessed.
|
||||
Future<void> _onNameChanged(String name) async {
|
||||
Future<void> _runNameClassify(String name) async {
|
||||
if (widget.detail.speciesId != null || _pickedSpeciesId != null) return;
|
||||
final queryId = ++_nameQueryId;
|
||||
final lang = Localizations.localeOf(context).languageCode;
|
||||
final match = await widget.species.classifyLabel(name, languageCode: lang);
|
||||
if (mounted) setState(() => _suggested = match);
|
||||
if (mounted && queryId == _nameQueryId) {
|
||||
setState(() => _suggested = match);
|
||||
}
|
||||
}
|
||||
|
||||
void _pick(SpeciesMatch match) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue