xNS5/7632cd

Feature: Implement a read status for messages

Closed this issue · 5 comments

xNS5 commented

THIS ISSUE WAS AUTOGENERATED BY HATCHWAYS
We want to track for each message if it has been read by the recipient, and to make several front end UI updates with this information, such as displaying how many unread messages there are in a given conversation.

You can reference the spec in this Figma file for the various updates to be made to reflect unread messages. (Note that the Figma file includes more than just the spec needed for this feature). Please include a screenshot in your PR description showing your updated UI.

In your PR description, please explain a couple different ways we could have stored the read status in the database for this feature. What are the benefits and drawbacks of each?

xNS5 commented

Assumptions

A conversation (at this point in time) can only exist between 2 users. When a user opens up a new conversation, the conversation will jump to the most recent message and show that they've read all of the new messages.

Possible Approaches

  1. In the Conversations table, create 2 extra columns: one for user1's unread count, the other for user2's unread count. When a user sends a message and the other user isn't reading them, the other user's unread count value will go up. When a conversation is read, the value is set to 0.
  2. In the Conversations table, create 1 column: number of unread messages. If the number is > 0, the client checks who sent the most recent message. For user1 and user2, if user2 sent 5 messages to user1, there would be a value 5 in the conversation row. On the client side, it would see this value and check who the last message is from. For user1, it would see that the last message isn't from them so the number 5 would appear in the conversation on the sidebar -- indicating the number of unread messages. When user1 clicks on the chat, that notifies the server to set the value to 0 and would update user2 with the index (0) that user1 has read.
  3. Add an extra column to the message table with a boolean value (or 1/0). When the messages are loaded, it will look for the most recent true or 1 value in the conversation where the sender is the current user. When a user opens a message containing unread messages, it will update the other user's most recent message to true or 1 and some sort of indicator will be activated on the message in the front end.

I think that approach No. 3 will be the easiest and will require less time and space to implement.

UI

If I have time I could easily implement an unread count, but for the time being a simple indicator such as a dot should suffice on the sidebar. In the conversation itself, either a checkmark type indicator like Signal or maybe the other user's profile picture like Facebook Messenger.

In order to move the read indicator, the previous indicator would have to be deactivated and the most recent messages indicator would have to be activated. A point in favor of approach 2 would be that instead of iterating to index n, I could just jump to in in constant time, then modify the message at index 0 (or -1 in this case).

xNS5 commented

I've added a new API route: /api/messages/markread. I decided to make a new API route instead of using a different request method in /api/messages to keep the services separate. This route can be accessed via POST request with the following parameters: conversationId and recipientId. With these parameters, we can get the last message that matches these parameters. From there we can return with the updated message

From here, I could do a couple of things: Once I have all of the message objects I could iterate from len-2...0 to find the previous "read" message. From there, the react application can emit something along the lines of "mark-read" and update the location of the read receipt. When /api/conversations is called, it'll just go for the most recent message marked with "True".

I've been thinking about how to address the frontend application features, and I think I'm going to do something like what's done for the online status indicator code-wise. When a user is online, they get marked with a green icon without a duplicate conversation in the sidebar.

xNS5 commented

The API is working as intended, but the current issue I'm facing is sending the read notification to the other user. It won't require updating the component. If I were to modify the code for adding new messages, I could alter the readStatus attribute for the required messages. The only issue with that is for some reason it isn't working. I added socket.on('mark-read', markConvoAsRead) and corresponding socket.off(...) with the rest of the socket events, but even though I've more or less copied the structure of adding a new message to the conversation it won't send to the other client. I'm stumped. I'm able to manually alter the HTML of each message to turn off/ on the read receipt marker for different messages and they appear/disappear as expected.

Most other examples of using socket.io to send/receive events from another application usually involve sending to/from a server, usually Node.js -- which isn't what's being used here.

xNS5 commented

The read receipt feature has been implemented. I was initially going to use a checkmark for simplicity, however after referencing the figma project I'm using the other person's profile picture to mark where they've read up to. This wasn't working initially because I mistakenly assumed that the socket.io server was the react application. I didn't think to check the backend application where the actual sockets were being handled. Once John pointed that out to me, I was able to implement the feature as described. What I decided to do was iterate from len-1...n from the most recent message to the oldest looking for the message marked as true. Once it finds the previous message marked true, it gets unmarked and the newest message is marked as read -- which in turn will move the read receipt. After it encounters the last unread, it marks it as false and breaks as it doesn't need to continue past that point.

I added the call to update the read receipt in 3 places:

  1. When the user clicks the chat to open it for the first time
  2. In the active chat, if the user has a new message they can click the conversation in the sidebar
  3. In the 'input' form field, I have it set to an onClick() event to call postRead().

I think I can reduce it to one call in the input.js file, however I'm going to need to do some testing first.

For the unread count, I think the easiest way to do this would be to have a field in the conversation database that counts the number of unread messages. Whenever markRead is called, that number gets set to zero. Whenever a user posts a new message, that number increases. The data (most likely will be an integer) will always transition from n -> 0 -> n+=1 -> n -> 0 as the users read, send, and read messages from the other user. The way I think I can differentiate between who the unread count is for is to just check who the sender of the last message in the conversation is. If it isn't user, then it's safe to assume that the unread count is for user.

xNS5 commented

That was not the easiest way. Instead I'm just counting the number of unread messages from the other person. This would have been an easier way had I kept the same logic in markRead but I had to readjust. See PR #12.