@@ -8,6 +8,7 @@ import 'package:flutter_chat_demo/constants/app_constants.dart';
8
8
import 'package:flutter_chat_demo/constants/color_constants.dart' ;
9
9
import 'package:flutter_chat_demo/constants/constants.dart' ;
10
10
import 'package:flutter_chat_demo/providers/providers.dart' ;
11
+ import 'package:flutter_chat_demo/utils/utils.dart' ;
11
12
import 'package:flutter_local_notifications/flutter_local_notifications.dart' ;
12
13
import 'package:fluttertoast/fluttertoast.dart' ;
13
14
import 'package:google_sign_in/google_sign_in.dart' ;
@@ -34,20 +35,27 @@ class HomePageState extends State<HomePage> {
34
35
35
36
int _limit = 20 ;
36
37
int _limitIncrement = 20 ;
38
+ String _textSearch = "" ;
37
39
bool isLoading = false ;
38
- List <Choice > choices = const < Choice > [
39
- const Choice (title: 'Settings' , icon: Icons .settings),
40
- const Choice (title: 'Log out' , icon: Icons .exit_to_app),
41
- ];
40
+
42
41
late AuthProvider authProvider;
43
42
late String currentUserId;
44
43
late HomeProvider homeProvider;
44
+ Debouncer searchDebouncer = Debouncer (milliseconds: 300 );
45
+ StreamController <bool > btnClearController = StreamController <bool >();
46
+ TextEditingController searchBarTec = TextEditingController ();
47
+
48
+ List <PopupChoices > choices = < PopupChoices > [
49
+ PopupChoices (title: 'Settings' , icon: Icons .settings),
50
+ PopupChoices (title: 'Log out' , icon: Icons .exit_to_app),
51
+ ];
45
52
46
53
@override
47
54
void initState () {
48
55
super .initState ();
49
56
authProvider = context.read <AuthProvider >();
50
57
homeProvider = context.read <HomeProvider >();
58
+
51
59
if (authProvider.getUserFirebaseId ()? .isNotEmpty == true ) {
52
60
currentUserId = authProvider.getUserFirebaseId ()! ;
53
61
} else {
@@ -61,6 +69,12 @@ class HomePageState extends State<HomePage> {
61
69
listScrollController.addListener (scrollListener);
62
70
}
63
71
72
+ @override
73
+ void dispose () {
74
+ super .dispose ();
75
+ btnClearController.close ();
76
+ }
77
+
64
78
void registerNotification () {
65
79
firebaseMessaging.requestPermission ();
66
80
@@ -99,7 +113,7 @@ class HomePageState extends State<HomePage> {
99
113
}
100
114
}
101
115
102
- void onItemMenuPress (Choice choice) {
116
+ void onItemMenuPress (PopupChoices choice) {
103
117
if (choice.title == 'Log out' ) {
104
118
handleSignOut ();
105
119
} else {
@@ -235,7 +249,7 @@ class HomePageState extends State<HomePage> {
235
249
appBar: AppBar (
236
250
title: Text (
237
251
AppConstants .homeTitle,
238
- style: TextStyle (color: ColorConstants .primaryColor, fontWeight : FontWeight .bold ),
252
+ style: TextStyle (color: ColorConstants .primaryColor),
239
253
),
240
254
centerTitle: true ,
241
255
actions: < Widget > [buildPopupMenu ()],
@@ -244,26 +258,37 @@ class HomePageState extends State<HomePage> {
244
258
child: Stack (
245
259
children: < Widget > [
246
260
// List
247
- Container (
248
- child: StreamBuilder <QuerySnapshot >(
249
- stream: homeProvider.getStreamFireStore (FirestoreConstants .pathUserCollection, _limit),
250
- builder: (BuildContext context, AsyncSnapshot <QuerySnapshot > snapshot) {
251
- if (snapshot.hasData) {
252
- return ListView .builder (
253
- padding: EdgeInsets .all (10 ),
254
- itemBuilder: (context, index) => buildItem (context, snapshot.data? .docs[index]),
255
- itemCount: snapshot.data? .docs.length,
256
- controller: listScrollController,
257
- );
258
- } else {
259
- return Center (
260
- child: CircularProgressIndicator (
261
- color: ColorConstants .themeColor,
262
- ),
263
- );
264
- }
265
- },
266
- ),
261
+ Column (
262
+ children: [
263
+ buildSearchBar (),
264
+ Expanded (
265
+ child: StreamBuilder <QuerySnapshot >(
266
+ stream: homeProvider.getStreamFireStore (FirestoreConstants .pathUserCollection, _limit, _textSearch),
267
+ builder: (BuildContext context, AsyncSnapshot <QuerySnapshot > snapshot) {
268
+ if (snapshot.hasData) {
269
+ if ((snapshot.data? .docs.length ?? 0 ) > 0 ) {
270
+ return ListView .builder (
271
+ padding: EdgeInsets .all (10 ),
272
+ itemBuilder: (context, index) => buildItem (context, snapshot.data? .docs[index]),
273
+ itemCount: snapshot.data? .docs.length,
274
+ controller: listScrollController,
275
+ );
276
+ } else {
277
+ return Center (
278
+ child: Text ("No users" ),
279
+ );
280
+ }
281
+ } else {
282
+ return Center (
283
+ child: CircularProgressIndicator (
284
+ color: ColorConstants .themeColor,
285
+ ),
286
+ );
287
+ }
288
+ },
289
+ ),
290
+ ),
291
+ ],
267
292
),
268
293
269
294
// Loading
@@ -277,12 +302,72 @@ class HomePageState extends State<HomePage> {
277
302
);
278
303
}
279
304
305
+ Widget buildSearchBar () {
306
+ return Container (
307
+ height: 40 ,
308
+ child: Row (
309
+ crossAxisAlignment: CrossAxisAlignment .center,
310
+ children: [
311
+ Icon (Icons .search, color: ColorConstants .greyColor, size: 20 ),
312
+ SizedBox (width: 5 ),
313
+ Expanded (
314
+ child: TextFormField (
315
+ textInputAction: TextInputAction .search,
316
+ controller: searchBarTec,
317
+ onChanged: (value) {
318
+ searchDebouncer.run (() {
319
+ if (value.isNotEmpty) {
320
+ btnClearController.add (true );
321
+ setState (() {
322
+ _textSearch = value;
323
+ });
324
+ } else {
325
+ btnClearController.add (false );
326
+ setState (() {
327
+ _textSearch = "" ;
328
+ });
329
+ }
330
+ });
331
+ },
332
+ decoration: InputDecoration .collapsed (
333
+ hintText: 'Search nickname (you have to type exactly string)' ,
334
+ hintStyle: TextStyle (fontSize: 13 , color: ColorConstants .greyColor),
335
+ ),
336
+ style: TextStyle (fontSize: 13 ),
337
+ ),
338
+ ),
339
+ StreamBuilder <bool >(
340
+ stream: btnClearController.stream,
341
+ builder: (context, snapshot) {
342
+ return snapshot.data == true
343
+ ? GestureDetector (
344
+ onTap: () {
345
+ searchBarTec.clear ();
346
+ btnClearController.add (false );
347
+ setState (() {
348
+ _textSearch = "" ;
349
+ });
350
+ },
351
+ child: Icon (Icons .clear_rounded, color: ColorConstants .greyColor, size: 20 ))
352
+ : SizedBox .shrink ();
353
+ }),
354
+ ],
355
+ ),
356
+ decoration: BoxDecoration (
357
+ borderRadius: BorderRadius .circular (16 ),
358
+ color: ColorConstants .greyColor2,
359
+ ),
360
+ padding: EdgeInsets .fromLTRB (16 , 8 , 16 , 8 ),
361
+ margin: EdgeInsets .fromLTRB (16 , 8 , 16 , 8 ),
362
+ );
363
+ }
364
+
280
365
Widget buildPopupMenu () {
281
- return PopupMenuButton <Choice >(
366
+ return PopupMenuButton <PopupChoices >(
282
367
onSelected: onItemMenuPress,
283
368
itemBuilder: (BuildContext context) {
284
- return choices.map ((Choice choice) {
285
- return PopupMenuItem <Choice >(
369
+ return choices.map ((PopupChoices choice) {
370
+ return PopupMenuItem <PopupChoices >(
286
371
value: choice,
287
372
child: Row (
288
373
children: < Widget > [
@@ -383,12 +468,16 @@ class HomePageState extends State<HomePage> {
383
468
],
384
469
),
385
470
onPressed: () {
471
+ if (Utilities .isKeyboardShowing ()) {
472
+ Utilities .closeKeyboard (context);
473
+ }
386
474
Navigator .push (
387
475
context,
388
476
MaterialPageRoute (
389
477
builder: (context) => ChatPage (
390
478
peerId: userChat.id,
391
479
peerAvatar: userChat.photoUrl,
480
+ peerNickname: userChat.nickname,
392
481
),
393
482
),
394
483
);
@@ -410,10 +499,3 @@ class HomePageState extends State<HomePage> {
410
499
}
411
500
}
412
501
}
413
-
414
- class Choice {
415
- const Choice ({required this .title, required this .icon});
416
-
417
- final String title;
418
- final IconData icon;
419
- }
0 commit comments