import 'package:flutter/material.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/views/app_style/sizing.dart'; class AutoCompleteGenericField extends StatefulWidget { final String? label; final String initialValue; final bool clearAfterPick; final Future> Function(String query) onSearch; final String Function(T item) displayString; final Function(T item) onPick; const AutoCompleteGenericField({ Key? key, this.label, required this.initialValue, required this.onSearch, required this.displayString, required this.onPick, this.clearAfterPick = true, }) : super(key: key); @override State> createState() => _AutoCompleteGenericFieldState(); } class _AutoCompleteGenericFieldState extends State> { late TextEditingController _controller; List _options = []; bool _isLoading = false; @override void initState() { _controller = TextEditingController(text: widget.initialValue); super.initState(); } @override void didUpdateWidget(covariant AutoCompleteGenericField oldWidget) { if (widget.initialValue != oldWidget.initialValue) { _controller.text = widget.initialValue; } super.didUpdateWidget(oldWidget); } Future _search(String query) async { if (query.isEmpty) { setState(() => _options = []); return; } setState(() => _isLoading = true); try { final results = await widget.onSearch(query); setState(() => _options = results); } finally { setState(() => _isLoading = false); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); return Container( decoration: BoxDecoration( color: AppColor.background(context), borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), ), child: RawAutocomplete( // textEditingController: _controller, optionsBuilder: (TextEditingValue textEditingValue) => _options, displayStringForOption: widget.displayString, fieldViewBuilder: (context, fieldTextEditingController, fieldFocusNode, onFieldSubmitted) { return TextField( controller: _controller, focusNode: fieldFocusNode, style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), textAlign: TextAlign.start, decoration: InputDecoration( border: border, disabledBorder: border, focusedBorder: border, enabledBorder: border, errorBorder: border, contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), // suffixIcon: _isLoading // ? const Padding( // padding: EdgeInsets.all(8.0), // child: SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)), // ) // : const Icon(Icons.search, size: 18), filled: true, fillColor: AppColor.fieldBgColor(context), labelText: widget.label, labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), ), textInputAction: TextInputAction.search, onChanged: (text) => _search(text), onSubmitted: (_) => onFieldSubmitted(), ); }, onSelected: (T selection) { if (widget.clearAfterPick) { _controller.clear(); } else { _controller.text = widget.displayString(selection); } widget.onPick(selection); }, optionsViewBuilder: (context, onSelected, options) { return Align( alignment: Alignment.topLeft, child: Material( elevation: 4, borderRadius: BorderRadius.circular(10), child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 200, minWidth: 200), child: ListView.builder( padding: EdgeInsets.zero, itemCount: options.length, itemBuilder: (context, index) { final option = options.elementAt(index); return ListTile( title: Text(widget.displayString(option)), onTap: () => onSelected(option), ); }, ), ), ), ); }, ), ); } }