Improve user search logic

This commit is contained in:
Nutcake 2023-05-04 19:38:35 +02:00
parent 40c4d82b81
commit 5b4f509840
2 changed files with 52 additions and 46 deletions

View file

@ -16,7 +16,7 @@ import 'package:flutter/material.dart';
class MenuItemDefinition { class MenuItemDefinition {
final String name; final String name;
final IconData icon; final IconData icon;
final void Function() onTap; final Function() onTap;
const MenuItemDefinition({required this.name, required this.icon, required this.onTap}); const MenuItemDefinition({required this.name, required this.icon, required this.onTap});
} }
@ -100,15 +100,20 @@ class _FriendsListState extends State<FriendsList> {
padding: const EdgeInsets.only(right: 4), padding: const EdgeInsets.only(right: 4),
child: PopupMenuButton<MenuItemDefinition>( child: PopupMenuButton<MenuItemDefinition>(
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
onSelected: (MenuItemDefinition itemDef) { onSelected: (MenuItemDefinition itemDef) async {
itemDef.onTap(); await itemDef.onTap();
}, },
itemBuilder: (BuildContext context) => [ itemBuilder: (BuildContext context) => [
MenuItemDefinition(name: "Settings", icon: Icons.settings, onTap: () { MenuItemDefinition(name: "Settings", icon: Icons.settings, onTap: () async {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const SettingsPage())); _autoRefresh?.cancel();
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const SettingsPage()));
_autoRefresh = Timer(_autoRefreshDuration, () => setState(() => _refreshFriendsList()));
}), }),
MenuItemDefinition(name: "Find Users", icon: Icons.person_add, onTap: () { MenuItemDefinition(name: "Find Users", icon: Icons.person_add, onTap: () async {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const UserSearch())); _autoRefresh?.cancel();
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const UserSearch()));
_autoRefresh = Timer(_autoRefreshDuration, () => setState(() => _refreshFriendsList()));
}) })
].map((item) => ].map((item) =>
PopupMenuItem<MenuItemDefinition>( PopupMenuItem<MenuItemDefinition>(

View file

@ -25,10 +25,11 @@ class UserSearch extends StatefulWidget {
class _UserSearchState extends State<UserSearch> { class _UserSearchState extends State<UserSearch> {
final TextEditingController _searchInputController = TextEditingController(); final TextEditingController _searchInputController = TextEditingController();
Timer? _searchDebouncer; Timer? _searchDebouncer;
late Future<List<User>>? _usersFuture = _emptySearch; late Future<List<User>?>? _usersFuture = _emptySearch;
bool _isLoading = false;
Future<List<User>> get _emptySearch => Future(() => throw const SearchError( Future<List<User>> get _emptySearch =>
Future(() =>
throw const SearchError(
message: "Start typing to search for users", icon: Icons.search) message: "Start typing to search for users", icon: Icons.search)
); );
@ -38,37 +39,34 @@ class _UserSearchState extends State<UserSearch> {
return; return;
} }
_usersFuture = UserApi.searchUsers(ClientHolder _usersFuture = UserApi.searchUsers(ClientHolder
.of(context).apiClient, needle: needle).then((value) { .of(context)
.apiClient, needle: needle).then((value) {
final res = value.toList(); final res = value.toList();
if (res.isEmpty) throw SearchError(message: "No user found with username '$needle'", icon: Icons.search_off); if (res.isEmpty) throw SearchError(message: "No user found with username '$needle'", icon: Icons.search_off);
res.sort( res.sort(
(a, b) => a.username.length.compareTo(b.username.length) (a, b) => a.username.length.compareTo(b.username.length)
); );
return res; return res;
}).whenComplete(() => setState(() => _isLoading = false)); });
}
@override
void initState() {
super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mClient = ClientHolder
.of(context)
.messagingClient;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Find Users"), title: const Text("Find Users"),
), ),
body: Column( body: Column(
children: [ children: [
if(_isLoading) const LinearProgressIndicator(),
Expanded( Expanded(
child: FutureBuilder( child: FutureBuilder(
future: _usersFuture, future: _usersFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
final users = (snapshot.data as List<User>); final users = snapshot.data as List<User>;
final mClient = ClientHolder.of(context).messagingClient;
return ListView.builder( return ListView.builder(
itemCount: users.length, itemCount: users.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -89,9 +87,13 @@ class _UserSearchState extends State<UserSearch> {
return DefaultErrorWidget(title: "${snapshot.error}",); return DefaultErrorWidget(title: "${snapshot.error}",);
} }
} else { } else {
return const SizedBox.shrink(); return Column(
} children: const [
LinearProgressIndicator(),
],
);
} }
},
), ),
), ),
Padding( Padding(
@ -111,13 +113,12 @@ class _UserSearchState extends State<UserSearch> {
_searchDebouncer?.cancel(); _searchDebouncer?.cancel();
if (value.isEmpty) { if (value.isEmpty) {
setState(() { setState(() {
_isLoading = false;
_querySearch(context, value); _querySearch(context, value);
}); });
return; return;
} }
setState(() { setState(() {
_isLoading = true; _usersFuture = Future(() => null);
}); });
_searchDebouncer = Timer(const Duration(milliseconds: 300), () { _searchDebouncer = Timer(const Duration(milliseconds: 300), () {
setState(() { setState(() {