2019-04-27 12:40:27 +00:00
|
|
|
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;
|
2022-03-10 18:39:57 +00:00
|
|
|
final void Function(String)? onValueChange;
|
2019-04-27 14:23:08 +00:00
|
|
|
final String label;
|
|
|
|
final bool resetOnChoose;
|
|
|
|
final bool keepFocusOnChoose;
|
2019-05-01 07:24:50 +00:00
|
|
|
final bool enabled;
|
2019-04-27 14:23:08 +00:00
|
|
|
|
2020-05-02 13:30:19 +00:00
|
|
|
const PickUserWidget({
|
2022-03-10 18:39:57 +00:00
|
|
|
Key? key,
|
|
|
|
required this.onSelectUser,
|
|
|
|
required this.label,
|
2020-05-02 13:30:19 +00:00
|
|
|
this.resetOnChoose = false,
|
|
|
|
this.keepFocusOnChoose = false,
|
|
|
|
this.onValueChange,
|
|
|
|
this.enabled = true,
|
2022-03-11 15:36:42 +00:00
|
|
|
}) : super(key: key);
|
2019-04-27 12:40:27 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => _PickUserWidgetState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PickUserWidgetState extends State<PickUserWidget> {
|
|
|
|
// Helper
|
|
|
|
final SearchHelper _searchHelper = SearchHelper();
|
|
|
|
|
|
|
|
// Widget properties
|
|
|
|
final FocusNode _focusNode = FocusNode();
|
|
|
|
final TextEditingController _controller = TextEditingController();
|
2022-03-10 18:39:57 +00:00
|
|
|
OverlayEntry? _overlayEntry;
|
|
|
|
UsersList? _suggestions;
|
2019-04-27 12:40:27 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
|
|
|
|
_focusNode.addListener(() {
|
|
|
|
if (_focusNode.hasFocus) {
|
|
|
|
//Check for focus
|
2019-04-27 14:23:08 +00:00
|
|
|
//_showOverlay();
|
2019-04-27 12:40:27 +00:00
|
|
|
} else {
|
|
|
|
//Remove overlay
|
|
|
|
_removeOverlay();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return TextField(
|
|
|
|
focusNode: _focusNode,
|
|
|
|
onChanged: (s) => _updateSuggestions(),
|
|
|
|
controller: _controller,
|
2019-05-01 07:24:50 +00:00
|
|
|
enabled: widget.enabled,
|
2019-04-27 14:23:08 +00:00
|
|
|
decoration: InputDecoration(
|
|
|
|
labelText: widget.label,
|
|
|
|
alignLabelWithHint: true,
|
|
|
|
),
|
2019-04-27 12:40:27 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
OverlayEntry _createOverlayEntry() {
|
2022-03-10 18:39:57 +00:00
|
|
|
RenderBox renderBox = context.findRenderObject() as RenderBox;
|
2019-04-27 12:40:27 +00:00
|
|
|
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,
|
2022-03-10 18:39:57 +00:00
|
|
|
itemCount: _suggestions == null ? 0 : _suggestions!.length,
|
2019-04-27 12:40:27 +00:00
|
|
|
itemBuilder: (c, i) => SimpleUserTile(
|
2022-03-10 18:39:57 +00:00
|
|
|
user: _suggestions![i],
|
2020-05-02 13:30:19 +00:00
|
|
|
onTap: _userTapped,
|
|
|
|
),
|
2019-04-27 12:40:27 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-27 14:23:08 +00:00
|
|
|
void _showOverlay() {
|
|
|
|
_overlayEntry = _createOverlayEntry();
|
2022-03-10 18:39:57 +00:00
|
|
|
Overlay.of(context)!.insert(_overlayEntry!);
|
2019-04-27 14:23:08 +00:00
|
|
|
}
|
|
|
|
|
2019-04-27 12:40:27 +00:00
|
|
|
void _removeOverlay() {
|
|
|
|
if (_overlayEntry != null) {
|
2022-03-10 18:39:57 +00:00
|
|
|
_overlayEntry!.remove();
|
2019-04-27 12:40:27 +00:00
|
|
|
_overlayEntry = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This method get called each time the input value is updated
|
|
|
|
Future<void> _updateSuggestions() async {
|
2022-03-10 18:39:57 +00:00
|
|
|
if (widget.onValueChange != null) widget.onValueChange!(_controller.text);
|
2019-04-27 12:40:27 +00:00
|
|
|
|
2020-05-02 13:30:19 +00:00
|
|
|
if (_controller.text.length == 0) return _removeOverlay();
|
|
|
|
|
|
|
|
final results = await _searchHelper.searchUser(_controller.text);
|
2019-04-27 12:40:27 +00:00
|
|
|
|
|
|
|
if (results == null) return;
|
|
|
|
|
|
|
|
_suggestions = results;
|
2019-04-27 14:23:08 +00:00
|
|
|
if (_overlayEntry != null)
|
2022-03-10 18:39:57 +00:00
|
|
|
_overlayEntry!.markNeedsBuild();
|
2019-04-27 14:23:08 +00:00
|
|
|
else
|
|
|
|
_showOverlay();
|
2019-04-27 12:40:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Method called each time a user is tapped (selected)
|
|
|
|
void _userTapped(User user) {
|
2019-04-27 14:23:08 +00:00
|
|
|
// Hide overlay
|
2019-04-27 12:40:27 +00:00
|
|
|
_removeOverlay();
|
|
|
|
|
2019-04-27 14:23:08 +00:00
|
|
|
// 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
|
2019-04-27 12:40:27 +00:00
|
|
|
widget.onSelectUser(user);
|
|
|
|
}
|
|
|
|
}
|