2020-05-08 19:02:39 +00:00
|
|
|
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';
|
2020-05-11 16:39:07 +00:00
|
|
|
import 'package:comunic/ui/routes/main_route/main_route.dart';
|
2020-05-08 19:02:39 +00:00
|
|
|
import 'package:comunic/ui/widgets/account_image_widget.dart';
|
|
|
|
import 'package:comunic/ui/widgets/group_icon_widget.dart';
|
2020-05-08 14:06:10 +00:00
|
|
|
import 'package:comunic/utils/intl_utils.dart';
|
2020-05-08 19:02:39 +00:00
|
|
|
import 'package:comunic/utils/ui_utils.dart';
|
2020-05-08 14:06:10 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
|
|
/// Global search field
|
|
|
|
///
|
|
|
|
/// @author Pierre Hubert
|
|
|
|
|
|
|
|
const _MainSearchColor = Color(0xFF999990);
|
|
|
|
|
2020-05-08 19:02:39 +00:00
|
|
|
class _SearchResults {
|
|
|
|
final SearchResultsList list;
|
|
|
|
final UsersList users;
|
|
|
|
final GroupsList groups;
|
|
|
|
|
|
|
|
_SearchResults(this.list, this.users, this.groups);
|
|
|
|
}
|
|
|
|
|
2020-05-08 14:06:10 +00:00
|
|
|
class GlobalSearchField extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_GlobalSearchFieldState createState() => _GlobalSearchFieldState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _GlobalSearchFieldState extends State<GlobalSearchField> {
|
2020-05-08 19:02:39 +00:00
|
|
|
final _focusNode = FocusNode();
|
|
|
|
|
|
|
|
final _controller = TextEditingController();
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
_SearchResults? _searchResultsList;
|
2020-05-08 19:02:39 +00:00
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
OverlayEntry? _overlayEntry;
|
2020-05-08 19:02:39 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
|
|
|
|
_focusNode.addListener(() {
|
|
|
|
if (!_focusNode.hasFocus) _removeOverlay();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-05-08 14:06:10 +00:00
|
|
|
@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(
|
2020-05-08 19:07:51 +00:00
|
|
|
autofocus: false,
|
2020-05-08 19:02:39 +00:00
|
|
|
controller: _controller,
|
|
|
|
focusNode: _focusNode,
|
2020-05-08 14:06:10 +00:00
|
|
|
textAlignVertical: TextAlignVertical.center,
|
2020-05-08 19:02:39 +00:00
|
|
|
onChanged: (s) => _refreshSearch(),
|
2020-05-08 14:06:10 +00:00
|
|
|
decoration: InputDecoration(
|
|
|
|
hintText: tr("Search..."),
|
|
|
|
hintStyle: TextStyle(color: _MainSearchColor),
|
|
|
|
suffixIcon: Icon(
|
|
|
|
Icons.search,
|
|
|
|
color: _MainSearchColor,
|
|
|
|
),
|
|
|
|
focusedBorder: InputBorder.none,
|
|
|
|
border: InputBorder.none,
|
|
|
|
alignLabelWithHint: false,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2020-05-08 19:02:39 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
if (_overlayEntry != null) _overlayEntry!.markNeedsBuild();
|
2020-05-08 19:02:39 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
print("Could not perform search! $e\n$s");
|
2022-03-10 18:39:57 +00:00
|
|
|
showSimpleSnack(context, tr("Could not perform search!")!);
|
2020-05-08 19:02:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _showOverlay() {
|
|
|
|
if (_overlayEntry == null) {
|
|
|
|
_overlayEntry = _createOverlayEntry();
|
2022-03-10 18:39:57 +00:00
|
|
|
Overlay.of(context)!.insert(_overlayEntry!);
|
2020-05-08 19:02:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _removeOverlay() {
|
|
|
|
if (_overlayEntry != null) {
|
2022-03-10 18:39:57 +00:00
|
|
|
_overlayEntry!.remove();
|
2020-05-08 19:02:39 +00:00
|
|
|
_overlayEntry = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
OverlayEntry _createOverlayEntry() {
|
2022-03-10 18:39:57 +00:00
|
|
|
RenderBox renderBox = context.findRenderObject() as RenderBox;
|
2020-05-08 19:02:39 +00:00
|
|
|
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,
|
2020-05-11 16:39:07 +00:00
|
|
|
child: _SearchResultsWidget(
|
|
|
|
results: _searchResultsList,
|
|
|
|
onTap: _removeOverlay,
|
|
|
|
),
|
2020-05-08 19:02:39 +00:00
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SearchResultsWidget extends StatelessWidget {
|
2022-03-10 18:39:57 +00:00
|
|
|
final _SearchResults? results;
|
2020-05-11 16:39:07 +00:00
|
|
|
final Function() onTap;
|
2020-05-08 19:02:39 +00:00
|
|
|
|
2020-05-11 16:39:07 +00:00
|
|
|
const _SearchResultsWidget({
|
2022-03-10 18:39:57 +00:00
|
|
|
Key? key,
|
|
|
|
required this.results,
|
|
|
|
required this.onTap,
|
2022-03-11 15:36:42 +00:00
|
|
|
}) : super(key: key);
|
2020-05-08 19:02:39 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
if (results == null) return Container();
|
|
|
|
return ListView.builder(
|
|
|
|
itemBuilder: _builder,
|
2022-03-10 18:39:57 +00:00
|
|
|
itemCount: results!.list.length,
|
2020-05-08 19:02:39 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _builder(BuildContext context, int index) {
|
2022-03-10 18:39:57 +00:00
|
|
|
final SearchResult res = results!.list[index];
|
2020-05-08 19:02:39 +00:00
|
|
|
|
|
|
|
switch (res.kind) {
|
|
|
|
case SearchResultKind.USER:
|
2022-03-10 18:39:57 +00:00
|
|
|
final user = results!.users.getUser(res.id);
|
2020-05-08 19:02:39 +00:00
|
|
|
return ListTile(
|
|
|
|
leading: AccountImageWidget(user: user),
|
|
|
|
title: Text(user.displayName),
|
2020-05-11 16:39:07 +00:00
|
|
|
onTap: () {
|
2022-03-11 15:21:35 +00:00
|
|
|
MainController.of(context)!.openUserPage(user.id);
|
2020-05-11 16:39:07 +00:00
|
|
|
onTap();
|
|
|
|
},
|
2020-05-08 19:02:39 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
case SearchResultKind.GROUP:
|
2022-03-10 18:39:57 +00:00
|
|
|
final group = results!.groups.getGroup(res.id)!;
|
2020-05-08 19:02:39 +00:00
|
|
|
return ListTile(
|
|
|
|
leading: GroupIcon(group: group),
|
|
|
|
title: Text(group.displayName),
|
2020-05-11 16:39:07 +00:00
|
|
|
onTap: () {
|
2022-03-10 18:39:57 +00:00
|
|
|
MainController.of(context)!.openGroup(group.id);
|
2020-05-11 16:39:07 +00:00
|
|
|
onTap();
|
|
|
|
},
|
2020-05-08 19:02:39 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-05-08 14:06:10 +00:00
|
|
|
}
|