Added ConversationsPage

master
Rafael 2024-06-02 16:32:19 +02:00
parent 97863bff2d
commit cad532da0b
6 changed files with 194 additions and 44 deletions

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../pages/conversations_page.dart';
import '../pages/home_page.dart'; import '../pages/home_page.dart';
import '../pages/user_data_page.dart'; import '../pages/user_data_page.dart';
import '../pages/settings_page.dart'; import '../pages/settings_page.dart';
@ -38,7 +39,7 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25), padding: const EdgeInsets.only(left: 25),
child: ListTile( child: ListTile(
title: const Text("Home"), title: const Text('Home'),
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
onTap: () { onTap: () {
// pop the drawer // pop the drawer
@ -58,7 +59,7 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25), padding: const EdgeInsets.only(left: 25),
child: ListTile( child: ListTile(
title: const Text("Find Matches"), title: const Text('Find Matches'),
leading: const Icon(Icons.person_search), leading: const Icon(Icons.person_search),
onTap: () { onTap: () {
// pop the drawer // pop the drawer
@ -79,9 +80,17 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25), padding: const EdgeInsets.only(left: 25),
child: ListTile( child: ListTile(
title: const Text("Conversations"), title: const Text('Chats'),
leading: const Icon(Icons.chat), leading: const Icon(Icons.chat),
onTap: () {}, // TODO onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationsPage(),
),
);
},
), ),
), ),
@ -107,7 +116,7 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25), padding: const EdgeInsets.only(left: 25),
child: ListTile( child: ListTile(
title: const Text("My Profile Settings"), title: const Text('My Profile Settings'),
leading: const Icon(Icons.edit_note), leading: const Icon(Icons.edit_note),
onTap: () { onTap: () {
// pop the drawer first, then navigate to destination // pop the drawer first, then navigate to destination
@ -156,7 +165,7 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25), padding: const EdgeInsets.only(left: 25),
child: ListTile( child: ListTile(
title: const Text("About the app"), title: const Text('About the app'),
leading: const Icon(Icons.perm_device_info), leading: const Icon(Icons.perm_device_info),
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
@ -171,7 +180,7 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25), padding: const EdgeInsets.only(left: 25),
child: ListTile( child: ListTile(
title: const Text("Send feedback"), title: const Text('Send feedback'),
leading: const Icon(Icons.sentiment_satisfied_alt), leading: const Icon(Icons.sentiment_satisfied_alt),
onTap: () { onTap: () {
showDialog( showDialog(
@ -187,7 +196,7 @@ class MyDrawer extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 25, bottom: 25), padding: const EdgeInsets.only(left: 25, bottom: 25),
child: ListTile( child: ListTile(
title: const Text("L O G O U T"), title: const Text('L O G O U T'),
leading: const Icon(Icons.logout), leading: const Icon(Icons.logout),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import '../components/user_tile.dart';
import '../constants.dart';
import '../services/auth/auth_service.dart';
import '../services/user_service.dart';
import 'chat_page.dart';
class ConversationsPage extends StatelessWidget {
ConversationsPage({super.key});
// auth service
final AuthService _authService = AuthService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Conversations'),
backgroundColor: Colors.transparent,
foregroundColor: Colors.grey.shade800,
elevation: 0,
),
body: _buildUserMatchesList(),
);
}
// build the list of matches for the current user
Widget _buildUserMatchesList() {
final currentUser = _authService.getCurrentUser();
if (currentUser == null) {
return const Center(child: Text('Error: User not logged in'));
}
return StreamBuilder<List<Map<String, dynamic>>>(
stream: UserService.getMatchedUsersStream(currentUser.uid),
builder: (context, snapshot) {
// error
if (snapshot.hasError) {
return Text('Error: ${snapshot.error.toString()}');
}
// loading
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
// return list view
if (snapshot.hasData) {
final matchedUsers = snapshot.data!;
if (matchedUsers.isEmpty) {
return const Center(child: Text('No matches yet'));
}
return ListView(
children: matchedUsers
.map<Widget>(
(userData) => _buildUserListItem(userData, context))
.toList(),
);
} else {
return const Center(child: Text('No matches found'));
}
},
);
}
// build individual user list item
Widget _buildUserListItem(
Map<String, dynamic> userData, BuildContext context) {
return UserTile(
text: userData[Constants.dbFieldUsersEmail],
onTap: () {
// tapped on a user -> go to chat page
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
receiverEmail: userData[Constants.dbFieldUsersEmail],
receiverID: userData[Constants.dbFieldUsersID],
),
),
);
},
);
}
}

View File

@ -1,23 +1,22 @@
import 'package:cofounderella/components/my_drawer.dart';
import 'package:cofounderella/components/user_tile.dart';
import 'package:cofounderella/services/auth/auth_service.dart';
import 'package:cofounderella/services/chat/chat_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../components/my_drawer.dart';
import '../components/user_tile.dart';
import '../constants.dart';
import '../services/auth/auth_service.dart';
import '../services/user_service.dart';
import 'chat_page.dart'; import 'chat_page.dart';
class HomePage extends StatelessWidget { class HomePage extends StatelessWidget {
HomePage({super.key}); HomePage({super.key});
// chat and auth service
final ChatService _chatService = ChatService();
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Home"), title: const Text('Home'),
// TODO appbar style: remove background and set elevation to 0 ? // TODO appbar style: remove background and set elevation to 0 ?
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.grey.shade800, foregroundColor: Colors.grey.shade800,
@ -31,16 +30,16 @@ class HomePage extends StatelessWidget {
// build a list of users except for the current logged in user // build a list of users except for the current logged in user
Widget _buildUserList() { Widget _buildUserList() {
return StreamBuilder( return StreamBuilder(
stream: _chatService.getUsersStream(), stream: UserService.getUsersStream(),
builder: (context, snapshot) { builder: (context, snapshot) {
// error // error
if (snapshot.hasError) { if (snapshot.hasError) {
return const Text("Error"); return Text('Error: ${snapshot.error.toString()}');
} }
//loading //loading
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading.."); return const Text('Loading..');
} }
// return list view // return list view
@ -57,17 +56,18 @@ class HomePage extends StatelessWidget {
Widget _buildUserListItem( Widget _buildUserListItem(
Map<String, dynamic> userData, BuildContext context) { Map<String, dynamic> userData, BuildContext context) {
// display all users except current user // display all users except current user
if (userData["email"] != _authService.getCurrentUser()!.email) { if (userData[Constants.dbFieldUsersEmail] !=
_authService.getCurrentUser()!.email) {
return UserTile( return UserTile(
text: userData["email"], text: userData[Constants.dbFieldUsersEmail],
onTap: () { onTap: () {
// tapped on a user -> go to chat page // TODO // tapped on a user -> go to chat page
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChatPage( builder: (context) => ChatPage(
receiverEmail: userData["email"], receiverEmail: userData[Constants.dbFieldUsersEmail],
receiverID: userData["uid"], receiverID: userData[Constants.dbFieldUsersID],
), ),
), ),
); );

View File

@ -31,8 +31,11 @@ class RegisterPage extends StatelessWidget {
.signUpWithEmailPassword( .signUpWithEmailPassword(
_emailController.text, _passwordController.text) _emailController.text, _passwordController.text)
.then((userCredential) { .then((userCredential) {
UserService.saveUserData(userCredential, _emailController.text, UserService.saveUserRegistrationData(
_nameController.text, _lastnameController.text); userCredential,
_emailController.text,
_nameController.text,
_lastnameController.text);
}); });
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
if (context.mounted) { if (context.mounted) {

View File

@ -10,22 +10,6 @@ class ChatService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance; final FirebaseAuth _auth = FirebaseAuth.instance;
// get user stream
Stream<List<Map<String, dynamic>>> getUsersStream() {
return _firestore
.collection(Constants.dbCollectionUsers)
.snapshots()
.map((snapshot) {
return snapshot.docs.map((doc) {
// iterate each user
final user = doc.data();
//return user
return user;
}).toList();
});
}
// send message // send message
Future<void> sendMessage(String receiverID, message) async { Future<void> sendMessage(String receiverID, message) async {
// get current user info // get current user info
@ -61,7 +45,7 @@ class ChatService {
.collection(Constants.dbCollectionChatRooms) .collection(Constants.dbCollectionChatRooms)
.doc(chatRoomID) .doc(chatRoomID)
.collection(Constants.dbCollectionMessages) .collection(Constants.dbCollectionMessages)
.orderBy("timestamp", descending: false) .orderBy('timestamp', descending: false)
.snapshots(); .snapshots();
} }
} }

View File

@ -9,8 +9,8 @@ import '../models/user_profile.dart';
class UserService { class UserService {
UserService._(); // Private constructor to prevent instantiation UserService._(); // Private constructor to prevent instantiation
static Future<void> saveUserData(UserCredential userCredential, String email, static Future<void> saveUserRegistrationData(UserCredential userCredential,
String firstname, String lastname) async { String email, String firstname, String lastname) async {
// create full name // create full name
String fullName = (firstname.isNotEmpty && lastname.isNotEmpty) String fullName = (firstname.isNotEmpty && lastname.isNotEmpty)
? '$firstname $lastname' ? '$firstname $lastname'
@ -151,4 +151,71 @@ class UserService {
return userProfile; return userProfile;
} }
// get users stream
static Stream<List<Map<String, dynamic>>> getUsersStream() {
return FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.snapshots()
.map((snapshot) {
return snapshot.docs.map((doc) {
// iterate each user
final user = doc.data();
//return user
return user;
}).toList();
});
}
// Get list of matched user ids for a given user
static Future<List<String>> getMatchedUserIds(String userId) async {
List<String> matchedUserIds = [];
final snapshot = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(userId)
.collection(Constants.dbCollectionMatches)
.get();
for (var doc in snapshot.docs) {
final data = doc.data();
final otherUserId = data['otherUserId'] as String?;
if (otherUserId != null && otherUserId.isNotEmpty) {
matchedUserIds.add(otherUserId);
}
// assuming the match document ID is the matched user's ID
// it would just be an one liner:
// matchedUserIds.add(doc.id);
}
return matchedUserIds;
}
// Get matched users data for a given user as stream
static Stream<List<Map<String, dynamic>>> getMatchedUsersStream(
String userId) {
return FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(userId)
.collection(Constants.dbCollectionMatches)
.snapshots()
.asyncMap((snapshot) async {
final matchedUserIds = snapshot.docs
.map((doc) => doc.data()['otherUserId'] as String?)
.where((otherUserId) => otherUserId != null && otherUserId.isNotEmpty)
.toList();
if (matchedUserIds.isEmpty) {
return [];
}
final matchedUsersSnapshot = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.where(FieldPath.documentId, whereIn: matchedUserIds)
.get();
return matchedUsersSnapshot.docs.map((doc) => doc.data()).toList();
});
}
} }