@@ -9,6 +9,7 @@ import { getProfiles, UserProfile, RawUserProfile, mapProfileData } from "../../
99import conversationData from "../data/conversations.json" ;
1010import firestore from "@react-native-firebase/firestore" ;
1111import auth from "@react-native-firebase/auth" ;
12+ import { SafeAreaView , useSafeAreaInsets } from "react-native-safe-area-context" ;
1213
1314// interface to define chat message structure
1415interface ChatMessage {
@@ -34,9 +35,12 @@ const imageMap: any = {
3435export default function ChatScreen ( ) {
3536
3637 const router = useRouter ( ) ;
37- // const profileMap = Object.fromEntries(profilesData.map(u => [u.id, u]));
3838 const [ profiles , setProfiles ] = useState < Record < string , UserProfile > > ( { } ) ;
3939 const [ messageReadStatus , setMessageReadStatus ] = useState ( conversationData ) ;
40+ const [ hasUnread , setHasUnread ] = useState < Record < string , boolean > > ( { } ) ; // track which conversations have unread messages
41+ const [ lastMessages , setLastMessages ] = useState < Record < string , string > > ( { } ) ; // track the last message for each conversation
42+ const [ lastMessageAt , setLastMessageAt ] = useState < Record < string , number > > ( { } ) ; // track the timestamp of the last message for sorting
43+ const insets = useSafeAreaInsets ( ) ;
4044
4145 // starts a conversation between the current user and the selected user or opens it if it already exists
4246 const startConversation = async ( otherUser : UserProfile ) => {
@@ -57,6 +61,9 @@ export default function ChatScreen() {
5761 lastMessageAt : firestore . FieldValue . serverTimestamp ( ) ,
5862 } ) ) . id ;
5963 // console.log("navigating with conversationId:", conversationId); // debug to verify we have the conversation ID before navigating
64+
65+ setHasUnread ( prev => ( { ...prev , [ otherUser . firebaseUid ] : false } ) ) ; // mark as read when opening the conversation
66+
6067 router . push ( {
6168 pathname : "/conversationScreen" ,
6269 params : { id : conversationId } ,
@@ -81,6 +88,40 @@ export default function ChatScreen() {
8188 } ;
8289 loadProfiles ( ) ;
8390} , [ ] ) ;
91+
92+ useEffect ( ( ) => {
93+ const currentUid = auth ( ) . currentUser ?. uid ;
94+ if ( ! currentUid ) return ;
95+
96+ const unsubscribe = firestore ( )
97+ . collection ( "conversations" )
98+ . where ( "participants" , "array-contains" , currentUid )
99+ . onSnapshot ( snapshot => {
100+ const unread : Record < string , boolean > = { } ;
101+ const lastMsg : Record < string , string > = { } ;
102+ const lastMsgAt : Record < string , number > = { } ;
103+
104+ snapshot . docs . forEach ( doc => {
105+ const data = doc . data ( ) ;
106+ const participants : string [ ] = data . participants ?? [ ] ;
107+ const otherUid = participants . find ( uid => uid !== currentUid ) ;
108+ if ( ! otherUid ) return ;
109+
110+ // true if the current user has any unread messages in this conversation
111+ unread [ otherUid ] = ! ! ( data . hasUnread ?. [ currentUid ] ) && data . lastMessageSenderId !== currentUid ;
112+ const sentByMe = data . lastMessageSenderId === currentUid ;
113+ lastMsg [ otherUid ] = data . lastMessage ? `${ sentByMe ? "You: " : "" } ${ data . lastMessage } ` : "Start a conversation" ;
114+ lastMsgAt [ otherUid ] = data . lastMessageAt ?. toMillis ( ) ?? 0 ; // use timestamp for sorting
115+
116+ } ) ;
117+ setHasUnread ( unread ) ;
118+ setLastMessages ( lastMsg ) ;
119+ setLastMessageAt ( lastMsgAt ) ;
120+ } ) ;
121+
122+ return ( ) => unsubscribe ( ) ;
123+ } , [ ] ) ;
124+
84125// Load message read status
85126useEffect ( ( ) => {
86127 const loadMessageReadStatus = async ( ) => {
@@ -134,40 +175,44 @@ useEffect(() => {
134175 } ;
135176
136177 return (
137- < View style = { styles . container } >
138- < FlatList < UserProfile >
139- data = { Object . values ( profiles ) }
140- keyExtractor = { ( item ) => item . id }
141- renderItem = { ( { item } ) => {
142- const avatarSource = item . avatar
143- ? / ^ h t t p s ? : \/ \/ / i. test ( item . avatar )
144- ? { uri : item . avatar }
145- : imageMap [ item . avatar ] ?? require ( "../../assets/images/chicken.png" )
146- : require ( "../../assets/images/chicken.png" ) ;
147-
148- return (
149- < TouchableOpacity
150- style = { styles . messageCard }
151- onPress = { ( ) => startConversation ( item ) }
152- >
153- < Image source = { avatarSource } style = { styles . profilePicture } />
154- < View style = { styles . messageContent } >
155- < Text style = { styles . username } > { item . name } </ Text >
156- < Text style = { styles . messageText } > Start a conversation</ Text >
157- </ View >
158- </ TouchableOpacity >
159- ) ;
160- } }
161- />
162- </ View >
178+ < View style = { [ styles . container , { paddingTop : insets . top } ] } >
179+ < FlatList < UserProfile >
180+ data = { Object . values ( profiles ) . sort ( ( a , b ) => {
181+ const aTime = lastMessageAt [ a . firebaseUid ] ?? 0 ;
182+ const bTime = lastMessageAt [ b . firebaseUid ] ?? 0 ;
183+ return bTime - aTime ; // sort by most recent message
184+ } ) }
185+ keyExtractor = { ( item ) => item . id }
186+ renderItem = { ( { item } ) => {
187+ const avatarSource = item . avatar
188+ ? / ^ h t t p s ? : \/ \/ / i. test ( item . avatar )
189+ ? { uri : item . avatar }
190+ : imageMap [ item . avatar ] ?? require ( "../../assets/images/chicken.png" )
191+ : require ( "../../assets/images/chicken.png" ) ;
192+ const isUnread = item . firebaseUid ? ( hasUnread [ item . firebaseUid ] ?? false ) : false ;
193+ return (
194+ < TouchableOpacity
195+ style = { styles . messageCard }
196+ onPress = { ( ) => startConversation ( item ) }
197+ >
198+ < Image source = { avatarSource } style = { styles . profilePicture } />
199+ < View style = { styles . messageContent } >
200+ < Text style = { styles . username } > { item . name } </ Text >
201+ < Text style = { styles . messageText } > { lastMessages [ item . firebaseUid ] ?? "Start a conversation" } </ Text >
202+ </ View >
203+ { isUnread && < Text style = { styles . notificationBadge } > </ Text > }
204+ </ TouchableOpacity >
205+ ) ;
206+ } }
207+ />
208+ </ View >
163209 ) ;
164210}
165211
166212const styles = StyleSheet . create ( {
167213 container : {
168214 flex : 1 ,
169215 backgroundColor : "#D9F7D7" ,
170- paddingTop : 20 ,
171216 } ,
172217 messageCard : {
173218 flexDirection : 'row' ,
@@ -195,8 +240,8 @@ const styles = StyleSheet.create({
195240 color : '#666' ,
196241 } ,
197242 notificationBadgeFormat : {
198- flexDirection : 'row ' ,
199- padding : 15 ,
243+ marginLeft : 'auto ' ,
244+ justifyContent : 'center' ,
200245 alignItems : 'center' ,
201246 } ,
202247 notificationBadge : {
@@ -206,6 +251,10 @@ const styles = StyleSheet.create({
206251 backgroundColor : "red" ,
207252 borderRadius : 20 ,
208253 color : "white" ,
209- }
254+ } ,
255+ safeArea : {
256+ flex : 1 ,
257+ backgroundColor : '#D9F7D7' ,
258+ } ,
210259} ) ;
211260
0 commit comments