From 765ca8230018f6db91a57dbf3ebec1dacf4c58ef Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Sat, 27 Apr 2019 14:40:27 +0200 Subject: [PATCH] Created inline user picker --- lib/helpers/search_helper.dart | 31 +++++++ lib/ui/tiles/simple_user_tile.dart | 31 +++++++ lib/ui/widgets/pick_user_widget.dart | 117 +++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 lib/helpers/search_helper.dart create mode 100644 lib/ui/tiles/simple_user_tile.dart create mode 100644 lib/ui/widgets/pick_user_widget.dart diff --git a/lib/helpers/search_helper.dart b/lib/helpers/search_helper.dart new file mode 100644 index 0000000..6f5d12c --- /dev/null +++ b/lib/helpers/search_helper.dart @@ -0,0 +1,31 @@ +import 'package:comunic/helpers/users_helper.dart'; +import 'package:comunic/lists/users_list.dart'; +import 'package:comunic/models/api_request.dart'; +import 'package:comunic/utils/list_utils.dart'; + +/// Search helper +/// +/// @author Pierre HUBERT + +class SearchHelper { + + /// Search for user. This method returns information about the target users + /// + /// Returns information about the target users or null if an error occurred + Future searchUser(String query) async { + // Execute the query on the server + final response = await APIRequest( + uri: "user/search", + needLogin: true, + args: { + "query": query + } + ).exec(); + + if (response.code != 200) return null; + + return await UsersHelper().getUsersInfo( + listToIntList(response.getArray())); + } + +} \ No newline at end of file diff --git a/lib/ui/tiles/simple_user_tile.dart b/lib/ui/tiles/simple_user_tile.dart new file mode 100644 index 0000000..e9fd87d --- /dev/null +++ b/lib/ui/tiles/simple_user_tile.dart @@ -0,0 +1,31 @@ +import 'package:comunic/models/user.dart'; +import 'package:comunic/ui/widgets/account_image_widget.dart'; +import 'package:flutter/material.dart'; + +/// Simple user tile +/// +/// Basically this only shows the name of the user + its account image +/// +/// @author Pierre HUBERT + +typedef OnUserTap = void Function(User); + +class SimpleUserTile extends StatelessWidget { + final User user; + final OnUserTap onTap; + + const SimpleUserTile({Key key, this.user, this.onTap}) + : assert(user != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + onTap: onTap == null ? null : () => onTap(user), + leading: AccountImageWidget( + user: user, + ), + title: Text(user.fullName), + ); + } +} diff --git a/lib/ui/widgets/pick_user_widget.dart b/lib/ui/widgets/pick_user_widget.dart new file mode 100644 index 0000000..b633f13 --- /dev/null +++ b/lib/ui/widgets/pick_user_widget.dart @@ -0,0 +1,117 @@ +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:comunic/utils/intl_utils.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; + + const PickUserWidget({Key key, @required this.onSelectUser}) + : assert(onSelectUser != null), + 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 + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + } else { + //Remove overlay + _removeOverlay(); + } + }); + } + + @override + Widget build(BuildContext context) { + return TextField( + focusNode: _focusNode, + onChanged: (s) => _updateSuggestions(), + controller: _controller, + decoration: InputDecoration(labelText: tr("Select user")), + ); + } + + OverlayEntry _createOverlayEntry() { + RenderBox renderBox = context.findRenderObject(); + 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 _removeOverlay() { + if (_overlayEntry != null) { + _overlayEntry.remove(); + _overlayEntry = null; + } + } + + /// This method get called each time the input value is updated + Future _updateSuggestions() async { + if (_controller.value.text.length == 0) return; + + final results = await _searchHelper.searchUser(_controller.value.text); + + if (results == null) return; + + _suggestions = results; + if (_overlayEntry != null) _overlayEntry.markNeedsBuild(); + } + + /// Method called each time a user is tapped (selected) + void _userTapped(User user) { + _controller.text = user.fullName; + _removeOverlay(); + _focusNode.unfocus(); + + widget.onSelectUser(user); + } +}