import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:sendtrain/services/functional/debouncer.dart'; import 'package:sendtrain/widgets/generic/elements/form_text_input.dart'; class Suggestion { T type; Suggestion(this.type); } class FormSearchInput extends StatefulWidget { const FormSearchInput( {super.key, required this.sessionController, required this.service, this.title, this.callback, this.optionalPayload}); final String? title; final TextEditingController sessionController; final dynamic service; final Function? callback; final dynamic optionalPayload; @override State createState() => _FormSearchInputState(); } class _FormSearchInputState extends State { String? _currentQuery; late final service = widget.service; late final callback = widget.callback; // The most recent suggestions received from the API. late Iterable _lastOptions = []; late final Debouncer debouncer; // @override // initState() { // service = widget.service; // } // Calls the "remote" API to search with the given query. Returns null when // the call has been made obsolete. Future?> _search(String query) async { _currentQuery = query; // In a real application, there should be some error handling here. // final Iterable options = await _FakeAPI.search(_currentQuery!); if (query.isNotEmpty) { final List? suggestions = await service.fetchSuggestions(_currentQuery!, 'en'); // If another search happened after this one, throw away these options. if (_currentQuery != query) { return null; } _currentQuery = null; return suggestions?.map((suggestion) => suggestion); } else { return null; } } @override void initState() { super.initState(); debouncer = Debouncer(Duration(milliseconds: 50), _search); } @override Widget build(BuildContext context) { return SearchAnchor( builder: (BuildContext context, SearchController controller) { return FormTextInput( controller: widget.sessionController, title: widget.title ?? "", icon: Icon(Icons.search_rounded), maxLines: 2, requiresValidation: false, onTap: () { controller.openView(); }); }, suggestionsBuilder: (BuildContext context, SearchController controller) async { final List? options = (await debouncer.process(controller.text))?.toList(); if (options == null) { return _lastOptions; } _lastOptions = List.generate(options.length, (int index) { final Suggestion item = options[index]; final dynamic content = item.type; return ListTile( title: Text(content.description), onTap: () async { if (callback != null) { callback!(content, service); } controller.closeView(null); }, ); }); return _lastOptions; }); } }