import 'package:comunic/helpers/search_helper.dart'; import 'package:comunic/lists/users_list.dart'; import 'package:comunic/models/user.dart'; import 'package:comunic/ui/tiles/simple_user_tile.dart'; import 'package:flutter/material.dart'; /// Pick user widget /// /// Allows to choose a user by starting to type his name and tapping on it /// when his name appear /// /// @author Pierre HUBERT typedef OnSelectUserCallback = void Function(User); class PickUserWidget extends StatefulWidget { final OnSelectUserCallback onSelectUser; final void Function(String)? onValueChange; final String label; final bool resetOnChoose; final bool keepFocusOnChoose; final bool enabled; const PickUserWidget({ Key? key, required this.onSelectUser, required this.label, this.resetOnChoose = false, this.keepFocusOnChoose = false, this.onValueChange, this.enabled = true, }) : super(key: key); @override State createState() => _PickUserWidgetState(); } class _PickUserWidgetState extends State { // Helper final SearchHelper _searchHelper = SearchHelper(); // Widget properties final FocusNode _focusNode = FocusNode(); final TextEditingController _controller = TextEditingController(); OverlayEntry? _overlayEntry; UsersList? _suggestions; @override void initState() { super.initState(); _focusNode.addListener(() { if (_focusNode.hasFocus) { //Check for focus //_showOverlay(); } else { //Remove overlay _removeOverlay(); } }); } @override Widget build(BuildContext context) { return TextField( focusNode: _focusNode, onChanged: (s) => _updateSuggestions(), controller: _controller, enabled: widget.enabled, decoration: InputDecoration( labelText: widget.label, alignLabelWithHint: true, ), ); } OverlayEntry _createOverlayEntry() { RenderBox renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; final offset = renderBox.localToGlobal(Offset.zero); return OverlayEntry(builder: (c) { return Positioned( left: offset.dx, top: offset.dy + size.height + 5.0, width: size.width, child: Material( elevation: 4.0, child: ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: _suggestions == null ? 0 : _suggestions!.length, itemBuilder: (c, i) => SimpleUserTile( user: _suggestions![i], onTap: _userTapped, ), ), ), ); }); } void _showOverlay() { _overlayEntry = _createOverlayEntry(); Overlay.of(context)!.insert(_overlayEntry!); } void _removeOverlay() { if (_overlayEntry != null) { _overlayEntry!.remove(); _overlayEntry = null; } } /// This method get called each time the input value is updated Future _updateSuggestions() async { if (widget.onValueChange != null) widget.onValueChange!(_controller.text); if (_controller.text.length == 0) return _removeOverlay(); final results = await _searchHelper.searchUser(_controller.text); if (results == null) return; _suggestions = results; if (_overlayEntry != null) _overlayEntry!.markNeedsBuild(); else _showOverlay(); } /// Method called each time a user is tapped (selected) void _userTapped(User user) { // Hide overlay _removeOverlay(); // Unfocus if required if (!widget.keepFocusOnChoose) { _focusNode.unfocus(); } //Check if name has to remain in input if (widget.resetOnChoose) { _controller.text = ""; } else _controller.text = user.fullName; //Callback widget.onSelectUser(user); } }