import 'package:comunic/helpers/groups_helper.dart'; import 'package:comunic/helpers/search_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/lists/groups_list.dart'; import 'package:comunic/lists/search_results_list.dart'; import 'package:comunic/lists/users_list.dart'; import 'package:comunic/models/search_result.dart'; import 'package:comunic/ui/routes/main_route/main_route.dart'; import 'package:comunic/ui/widgets/account_image_widget.dart'; import 'package:comunic/ui/widgets/group_icon_widget.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/material.dart'; /// Global search field /// /// @author Pierre Hubert const _MainSearchColor = Color(0xFF999990); class _SearchResults { final SearchResultsList list; final UsersList users; final GroupsList groups; _SearchResults(this.list, this.users, this.groups); } class GlobalSearchField extends StatefulWidget { @override _GlobalSearchFieldState createState() => _GlobalSearchFieldState(); } class _GlobalSearchFieldState extends State { final _focusNode = FocusNode(); final _controller = TextEditingController(); _SearchResults? _searchResultsList; OverlayEntry? _overlayEntry; @override void initState() { super.initState(); _focusNode.addListener(() { if (!_focusNode.hasFocus) _removeOverlay(); }); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Material( color: Color(0xFF374850), borderRadius: BorderRadius.all(Radius.circular(5.0)), child: Padding( padding: const EdgeInsets.only(left: 8.0), child: TextField( autofocus: false, controller: _controller, focusNode: _focusNode, textAlignVertical: TextAlignVertical.center, onChanged: (s) => _refreshSearch(), decoration: InputDecoration( hintText: tr("Search..."), hintStyle: TextStyle(color: _MainSearchColor), suffixIcon: Icon( Icons.search, color: _MainSearchColor, ), focusedBorder: InputBorder.none, border: InputBorder.none, alignLabelWithHint: false, ), ), ), ), ); } void _refreshSearch() async { if (_controller.text.length < 1) { _removeOverlay(); return; } _showOverlay(); try { final results = await SearchHelper().globalSearch(_controller.text); final users = await UsersHelper().getListWithThrow(results.usersId); final groups = await GroupsHelper().getListOrThrow(results.groupsId); _searchResultsList = _SearchResults(results, users, groups); if (_overlayEntry != null) _overlayEntry!.markNeedsBuild(); } catch (e, s) { print("Could not perform search! $e\n$s"); showSimpleSnack(context, tr("Could not perform search!")!); } } void _showOverlay() { if (_overlayEntry == null) { _overlayEntry = _createOverlayEntry(); Overlay.of(context)!.insert(_overlayEntry!); } } void _removeOverlay() { if (_overlayEntry != null) { _overlayEntry!.remove(); _overlayEntry = null; } } OverlayEntry _createOverlayEntry() { RenderBox renderBox = context.findRenderObject() as RenderBox; var size = renderBox.size; var offset = renderBox.localToGlobal(Offset.zero); return OverlayEntry( builder: (context) => Positioned( left: offset.dx, top: offset.dy + size.height + 5.0, width: size.width, height: 300, child: Material( elevation: 4.0, child: _SearchResultsWidget( results: _searchResultsList, onTap: _removeOverlay, ), ), )); } } class _SearchResultsWidget extends StatelessWidget { final _SearchResults? results; final Function() onTap; const _SearchResultsWidget({ Key? key, required this.results, required this.onTap, }) : assert(onTap != null), super(key: key); @override Widget build(BuildContext context) { if (results == null) return Container(); return ListView.builder( itemBuilder: _builder, itemCount: results!.list.length, ); } Widget _builder(BuildContext context, int index) { final SearchResult res = results!.list[index]; switch (res.kind) { case SearchResultKind.USER: final user = results!.users.getUser(res.id); return ListTile( leading: AccountImageWidget(user: user), title: Text(user.displayName), onTap: () { MainController.of(context)!.openUserPage(user.id!); onTap(); }, ); case SearchResultKind.GROUP: final group = results!.groups.getGroup(res.id)!; return ListTile( leading: GroupIcon(group: group), title: Text(group.displayName), onTap: () { MainController.of(context)!.openGroup(group.id); onTap(); }, ); } throw Exception("Unreachable statement!"); } }