1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-10-23 15:03:22 +00:00
comunicmobile/lib/ui/widgets/pick_user_widget.dart
2022-03-11 16:36:42 +01:00

152 lines
3.8 KiB
Dart

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<StatefulWidget> createState() => _PickUserWidgetState();
}
class _PickUserWidgetState extends State<PickUserWidget> {
// 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<void> _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);
}
}