Skip to content

Commit f458901

Browse files
committed
add search user to support you guys when testing between two users
1 parent 5584e3e commit f458901

File tree

13 files changed

+189
-50
lines changed

13 files changed

+189
-50
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@
2020
* Run `flutter run` (remember open simulator or connect physical device, iOS auto run additional command `pod install`)
2121

2222
## Big update
23-
* Jun, 4th, 2021 Migrating to Flutter 2 and using Dart sound null safety
23+
* Jun, 4th, 2021 - Migrating to Flutter 2 and using Dart sound null safety
2424
* Oct, 2th, 2021 - Apply provider & restructure

lib/constants/app_constants.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ class AppConstants {
22
static const appTitle = "Flutter Chat Demo";
33
static const loginTitle = "Login";
44
static const homeTitle = "Home";
5-
static const chatTitle = "Chat";
65
static const settingsTitle = "Settings";
76
static const fullPhotoTitle = "Full Photo";
87
}

lib/models/models.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export 'message_chat.dart';
2+
export 'popup_choices.dart';
23
export 'user_chat.dart';

lib/models/popup_choices.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'package:flutter/material.dart';
2+
3+
class PopupChoices {
4+
String title;
5+
IconData icon;
6+
7+
PopupChoices({required this.title, required this.icon});
8+
}

lib/pages/chat_page.dart

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import 'dart:io';
44
import 'package:cloud_firestore/cloud_firestore.dart';
55
import 'package:firebase_storage/firebase_storage.dart';
66
import 'package:flutter/material.dart';
7-
import 'package:flutter_chat_demo/constants/app_constants.dart';
87
import 'package:flutter_chat_demo/constants/color_constants.dart';
98
import 'package:flutter_chat_demo/constants/constants.dart';
109
import 'package:flutter_chat_demo/models/models.dart';
@@ -20,18 +19,24 @@ import 'pages.dart';
2019
class ChatPage extends StatefulWidget {
2120
final String peerId;
2221
final String peerAvatar;
22+
final String peerNickname;
2323

24-
ChatPage({Key? key, required this.peerId, required this.peerAvatar}) : super(key: key);
24+
ChatPage({Key? key, required this.peerId, required this.peerAvatar, required this.peerNickname}) : super(key: key);
2525

2626
@override
27-
State createState() => ChatPageState(peerId: peerId, peerAvatar: peerAvatar);
27+
State createState() => ChatPageState(
28+
peerId: this.peerId,
29+
peerAvatar: this.peerAvatar,
30+
peerNickname: this.peerNickname,
31+
);
2832
}
2933

3034
class ChatPageState extends State<ChatPage> {
31-
ChatPageState({Key? key, required this.peerId, required this.peerAvatar});
35+
ChatPageState({Key? key, required this.peerId, required this.peerAvatar, required this.peerNickname});
3236

3337
String peerId;
3438
String peerAvatar;
39+
String peerNickname;
3540
late String currentUserId;
3641

3742
List<QueryDocumentSnapshot> listMessage = new List.from([]);
@@ -150,7 +155,7 @@ class ChatPageState extends State<ChatPage> {
150155
chatProvider.sendMessage(content, type, groupChatId, currentUserId, peerId);
151156
listScrollController.animateTo(0, duration: Duration(milliseconds: 300), curve: Curves.easeOut);
152157
} else {
153-
Fluttertoast.showToast(msg: 'Nothing to send', backgroundColor: Colors.black, textColor: Colors.red);
158+
Fluttertoast.showToast(msg: 'Nothing to send', backgroundColor: ColorConstants.greyColor);
154159
}
155160
}
156161

@@ -435,8 +440,8 @@ class ChatPageState extends State<ChatPage> {
435440
return Scaffold(
436441
appBar: AppBar(
437442
title: Text(
438-
AppConstants.chatTitle,
439-
style: TextStyle(color: ColorConstants.primaryColor, fontWeight: FontWeight.bold),
443+
this.peerNickname,
444+
style: TextStyle(color: ColorConstants.primaryColor),
440445
),
441446
centerTitle: true,
442447
),

lib/pages/full_photo_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class FullPhotoPage extends StatelessWidget {
1414
appBar: AppBar(
1515
title: Text(
1616
AppConstants.fullPhotoTitle,
17-
style: TextStyle(color: ColorConstants.primaryColor, fontWeight: FontWeight.bold),
17+
style: TextStyle(color: ColorConstants.primaryColor),
1818
),
1919
centerTitle: true,
2020
),

lib/pages/home_page.dart

Lines changed: 118 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter_chat_demo/constants/app_constants.dart';
88
import 'package:flutter_chat_demo/constants/color_constants.dart';
99
import 'package:flutter_chat_demo/constants/constants.dart';
1010
import 'package:flutter_chat_demo/providers/providers.dart';
11+
import 'package:flutter_chat_demo/utils/utils.dart';
1112
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
1213
import 'package:fluttertoast/fluttertoast.dart';
1314
import 'package:google_sign_in/google_sign_in.dart';
@@ -34,20 +35,27 @@ class HomePageState extends State<HomePage> {
3435

3536
int _limit = 20;
3637
int _limitIncrement = 20;
38+
String _textSearch = "";
3739
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+
4241
late AuthProvider authProvider;
4342
late String currentUserId;
4443
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+
];
4552

4653
@override
4754
void initState() {
4855
super.initState();
4956
authProvider = context.read<AuthProvider>();
5057
homeProvider = context.read<HomeProvider>();
58+
5159
if (authProvider.getUserFirebaseId()?.isNotEmpty == true) {
5260
currentUserId = authProvider.getUserFirebaseId()!;
5361
} else {
@@ -61,6 +69,12 @@ class HomePageState extends State<HomePage> {
6169
listScrollController.addListener(scrollListener);
6270
}
6371

72+
@override
73+
void dispose() {
74+
super.dispose();
75+
btnClearController.close();
76+
}
77+
6478
void registerNotification() {
6579
firebaseMessaging.requestPermission();
6680

@@ -99,7 +113,7 @@ class HomePageState extends State<HomePage> {
99113
}
100114
}
101115

102-
void onItemMenuPress(Choice choice) {
116+
void onItemMenuPress(PopupChoices choice) {
103117
if (choice.title == 'Log out') {
104118
handleSignOut();
105119
} else {
@@ -235,7 +249,7 @@ class HomePageState extends State<HomePage> {
235249
appBar: AppBar(
236250
title: Text(
237251
AppConstants.homeTitle,
238-
style: TextStyle(color: ColorConstants.primaryColor, fontWeight: FontWeight.bold),
252+
style: TextStyle(color: ColorConstants.primaryColor),
239253
),
240254
centerTitle: true,
241255
actions: <Widget>[buildPopupMenu()],
@@ -244,26 +258,37 @@ class HomePageState extends State<HomePage> {
244258
child: Stack(
245259
children: <Widget>[
246260
// 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+
],
267292
),
268293

269294
// Loading
@@ -277,12 +302,72 @@ class HomePageState extends State<HomePage> {
277302
);
278303
}
279304

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+
280365
Widget buildPopupMenu() {
281-
return PopupMenuButton<Choice>(
366+
return PopupMenuButton<PopupChoices>(
282367
onSelected: onItemMenuPress,
283368
itemBuilder: (BuildContext context) {
284-
return choices.map((Choice choice) {
285-
return PopupMenuItem<Choice>(
369+
return choices.map((PopupChoices choice) {
370+
return PopupMenuItem<PopupChoices>(
286371
value: choice,
287372
child: Row(
288373
children: <Widget>[
@@ -383,12 +468,16 @@ class HomePageState extends State<HomePage> {
383468
],
384469
),
385470
onPressed: () {
471+
if (Utilities.isKeyboardShowing()) {
472+
Utilities.closeKeyboard(context);
473+
}
386474
Navigator.push(
387475
context,
388476
MaterialPageRoute(
389477
builder: (context) => ChatPage(
390478
peerId: userChat.id,
391479
peerAvatar: userChat.photoUrl,
480+
peerNickname: userChat.nickname,
392481
),
393482
),
394483
);
@@ -410,10 +499,3 @@ class HomePageState extends State<HomePage> {
410499
}
411500
}
412501
}
413-
414-
class Choice {
415-
const Choice({required this.title, required this.icon});
416-
417-
final String title;
418-
final IconData icon;
419-
}

lib/pages/login_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class LoginPageState extends State<LoginPage> {
3636
appBar: AppBar(
3737
title: Text(
3838
AppConstants.loginTitle,
39-
style: TextStyle(color: ColorConstants.primaryColor, fontWeight: FontWeight.bold),
39+
style: TextStyle(color: ColorConstants.primaryColor),
4040
),
4141
centerTitle: true,
4242
),

lib/pages/settings_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class SettingsPage extends StatelessWidget {
2222
appBar: AppBar(
2323
title: Text(
2424
AppConstants.settingsTitle,
25-
style: TextStyle(color: ColorConstants.primaryColor, fontWeight: FontWeight.bold),
25+
style: TextStyle(color: ColorConstants.primaryColor),
2626
),
2727
centerTitle: true,
2828
),

lib/providers/home_provider.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:cloud_firestore/cloud_firestore.dart';
2+
import 'package:flutter_chat_demo/constants/firestore_constants.dart';
23

34
class HomeProvider {
45
final FirebaseFirestore firebaseFirestore;
@@ -9,7 +10,15 @@ class HomeProvider {
910
return firebaseFirestore.collection(collectionPath).doc(path).update(dataNeedUpdate);
1011
}
1112

12-
Stream<QuerySnapshot> getStreamFireStore(String pathCollection, int limit) {
13-
return firebaseFirestore.collection(pathCollection).limit(limit).snapshots();
13+
Stream<QuerySnapshot> getStreamFireStore(String pathCollection, int limit, String? textSearch) {
14+
if (textSearch?.isNotEmpty == true) {
15+
return firebaseFirestore
16+
.collection(pathCollection)
17+
.limit(limit)
18+
.where(FirestoreConstants.nickname, isEqualTo: textSearch)
19+
.snapshots();
20+
} else {
21+
return firebaseFirestore.collection(pathCollection).limit(limit).snapshots();
22+
}
1423
}
1524
}

lib/utils/debouncer.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'dart:async';
2+
import 'dart:ui';
3+
4+
class Debouncer {
5+
final int milliseconds;
6+
Timer? _timer;
7+
8+
Debouncer({required this.milliseconds});
9+
10+
run(VoidCallback action) {
11+
_timer?.cancel();
12+
13+
_timer = Timer(Duration(milliseconds: milliseconds), action);
14+
}
15+
}

0 commit comments

Comments
 (0)