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 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
|
@ -619,8 +620,20 @@ class _EditVarietySheetState extends State<_EditVarietySheet> {
|
||||||
late int? _seedHarvestMonths = widget.detail.seedHarvestMonths;
|
late int? _seedHarvestMonths = widget.detail.seedHarvestMonths;
|
||||||
late bool _calendarOpen = widget.detail.hasCropCalendar;
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_speciesDebounce?.cancel();
|
||||||
|
_nameDebounce?.cancel();
|
||||||
_name.dispose();
|
_name.dispose();
|
||||||
_category.dispose();
|
_category.dispose();
|
||||||
_notes.dispose();
|
_notes.dispose();
|
||||||
|
|
@ -628,21 +641,38 @@ class _EditVarietySheetState extends State<_EditVarietySheet> {
|
||||||
super.dispose();
|
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 lang = Localizations.localeOf(context).languageCode;
|
||||||
final results = await widget.species.search(query, languageCode: lang);
|
final results = await widget.species.search(query, languageCode: lang);
|
||||||
// A manual species search takes over from the name-based suggestion.
|
// 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
|
/// 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
|
/// variety has no species yet and the grower hasn't picked one, so an
|
||||||
/// existing link is never second-guessed.
|
/// 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;
|
if (widget.detail.speciesId != null || _pickedSpeciesId != null) return;
|
||||||
|
final queryId = ++_nameQueryId;
|
||||||
final lang = Localizations.localeOf(context).languageCode;
|
final lang = Localizations.localeOf(context).languageCode;
|
||||||
final match = await widget.species.classifyLabel(name, languageCode: lang);
|
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) {
|
void _pick(SpeciesMatch match) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue