Make chat notification display messengers name and disable notifying if already in chat view

This commit is contained in:
Alex
2026-03-26 21:31:36 -06:00
parent dbb24085b2
commit 2c61e6e664
3 changed files with 118 additions and 22 deletions

View File

@@ -25,6 +25,7 @@ import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SendMessageRequest; import com.example.petstoremobile.dtos.SendMessageRequest;
import com.example.petstoremobile.models.Chat; import com.example.petstoremobile.models.Chat;
import com.example.petstoremobile.models.Message; import com.example.petstoremobile.models.Message;
import com.example.petstoremobile.services.ChatNotificationService;
import com.example.petstoremobile.websocket.StompChatManager; import com.example.petstoremobile.websocket.StompChatManager;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -40,6 +41,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
private RecyclerView rvChatList, rvMessages; private RecyclerView rvChatList, rvMessages;
private EditText etMessage; private EditText etMessage;
private Button btnSend; private Button btnSend;
private TextView tvChatTitle;
// Adapters // Adapters
private ChatAdapter chatAdapter; private ChatAdapter chatAdapter;
@@ -75,6 +77,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
rvMessages = view.findViewById(R.id.rvMessages); rvMessages = view.findViewById(R.id.rvMessages);
etMessage = view.findViewById(R.id.etMessage); etMessage = view.findViewById(R.id.etMessage);
btnSend = view.findViewById(R.id.btnSend); btnSend = view.findViewById(R.id.btnSend);
tvChatTitle = view.findViewById(R.id.tvChatTitle);
ImageButton hamburger = view.findViewById(R.id.btnHamburger); ImageButton hamburger = view.findViewById(R.id.btnHamburger);
hamburger.setOnClickListener(v -> drawerLayout.openDrawer(GravityCompat.START)); hamburger.setOnClickListener(v -> drawerLayout.openDrawer(GravityCompat.START));
@@ -172,6 +175,13 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
if (activeConversationId != null) { if (activeConversationId != null) {
setConversationActive(true); setConversationActive(true);
// Update title to customer name of active conversation
for (Chat chat : chatList) {
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
tvChatTitle.setText(chat.getCustomerName());
break;
}
}
if (stompChatManager != null) { if (stompChatManager != null) {
stompChatManager.subscribeToConversation(activeConversationId); stompChatManager.subscribeToConversation(activeConversationId);
} }
@@ -197,6 +207,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
public void onChatClick(Chat chat) { public void onChatClick(Chat chat) {
activeConversationId = Long.parseLong(chat.getChatId()); activeConversationId = Long.parseLong(chat.getChatId());
setConversationActive(true); setConversationActive(true);
tvChatTitle.setText(chat.getCustomerName());
drawerLayout.closeDrawer(GravityCompat.START); drawerLayout.closeDrawer(GravityCompat.START);
if (stompChatManager != null) { if (stompChatManager != null) {
@@ -316,6 +327,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
if (activeConversationId != null && activeConversationId.equals(dto.getId())) { if (activeConversationId != null && activeConversationId.equals(dto.getId())) {
setConversationActive(true); setConversationActive(true);
tvChatTitle.setText(name);
} }
} }
@@ -397,6 +409,8 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
etMessage.setEnabled(active); etMessage.setEnabled(active);
if (!active) { if (!active) {
activeConversationId = null; activeConversationId = null;
ChatNotificationService.activeConversationIdInUi = null;
if (tvChatTitle != null) tvChatTitle.setText("Customer Chat");
if (stompChatManager != null) { if (stompChatManager != null) {
stompChatManager.clearConversationSubscription(); stompChatManager.clearConversationSubscription();
} }
@@ -406,6 +420,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
etMessage.setHint("Select a chat to start messaging"); etMessage.setHint("Select a chat to start messaging");
} else { } else {
etMessage.setHint("Type a message..."); etMessage.setHint("Type a message...");
ChatNotificationService.activeConversationIdInUi = activeConversationId;
} }
} }
@@ -413,6 +428,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
ChatNotificationService.activeConversationIdInUi = null;
if (stompChatManager != null) stompChatManager.disconnect(); if (stompChatManager != null) stompChatManager.disconnect();
} }
} }

View File

@@ -4,16 +4,22 @@ import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.example.petstoremobile.api.ChatApi; import com.example.petstoremobile.api.ChatApi;
import com.example.petstoremobile.api.CustomerApi;
import com.example.petstoremobile.api.RetrofitClient; import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.api.auth.TokenManager;
import com.example.petstoremobile.dtos.ConversationDTO; import com.example.petstoremobile.dtos.ConversationDTO;
import com.example.petstoremobile.dtos.CustomerDTO;
import com.example.petstoremobile.dtos.MessageDTO; import com.example.petstoremobile.dtos.MessageDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.utils.NotificationHelper; import com.example.petstoremobile.utils.NotificationHelper;
import com.example.petstoremobile.websocket.StompChatManager; import com.example.petstoremobile.websocket.StompChatManager;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@@ -22,8 +28,13 @@ import retrofit2.Response;
// Service to receive notifications when a new conversation is created // Service to receive notifications when a new conversation is created
public class ChatNotificationService extends Service { public class ChatNotificationService extends Service {
private static final String TAG = "ChatNotificationService"; private static final String TAG = "ChatNotificationService";
public static Long activeConversationIdInUi = null;
private StompChatManager stompChatManager; private StompChatManager stompChatManager;
private final Set<Long> knownConversationIds = new HashSet<>(); private final Set<Long> knownConversationIds = new HashSet<>();
private final Map<Long, Long> conversationToCustomerId = new HashMap<>();
private final Map<Long, String> customerIdToName = new HashMap<>();
private Long currentUserId; private Long currentUserId;
//When the service starts, connect to the websocket //When the service starts, connect to the websocket
@@ -43,36 +54,59 @@ public class ChatNotificationService extends Service {
currentUserId = tm.getUserId(); currentUserId = tm.getUserId();
if (token != null && stompChatManager == null) { if (token != null && stompChatManager == null) {
// Fetch existing conversations //load customers to have names associated with customer ids
ChatApi chatApi = RetrofitClient.getChatApi(this); CustomerApi customerApi = RetrofitClient.getCustomerApi(this);
chatApi.getAllConversations().enqueue(new Callback<List<ConversationDTO>>() { customerApi.getAllCustomers(0, 1000).enqueue(new Callback<PageResponse<CustomerDTO>>() {
@Override @Override
public void onResponse(Call<List<ConversationDTO>> call, Response<List<ConversationDTO>> response) { public void onResponse(@NonNull Call<PageResponse<CustomerDTO>> call, @NonNull Response<PageResponse<CustomerDTO>> response) {
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
for (ConversationDTO conversation : response.body()) { for (CustomerDTO customer : response.body().getContent()) {
if (conversation.getId() != null) { customerIdToName.put(customer.getCustomerId(), customer.getFullName());
knownConversationIds.add(conversation.getId());
// subscribe to existing conversations to get message notifications
if (stompChatManager != null) {
stompChatManager.subscribeToConversation(conversation.getId());
}
}
} }
Log.d(TAG, "Loaded " + knownConversationIds.size() + " existing conversations");
} }
startStomp(token, role); loadConversationsAndStartStomp(token, role);
} }
@Override @Override
public void onFailure(Call<List<ConversationDTO>> call, Throwable t) { public void onFailure(@NonNull Call<PageResponse<CustomerDTO>> call, @NonNull Throwable t) {
Log.e(TAG, "Failed to load existing conversations", t); Log.e(TAG, "Failed to load customers", t);
//tries to connect if loading fails loadConversationsAndStartStomp(token, role);
startStomp(token, role);
} }
}); });
} }
} }
private void loadConversationsAndStartStomp(String token, String role) {
// Fetch existing conversations
ChatApi chatApi = RetrofitClient.getChatApi(this);
chatApi.getAllConversations().enqueue(new Callback<List<ConversationDTO>>() {
@Override
public void onResponse(@NonNull Call<List<ConversationDTO>> call, @NonNull Response<List<ConversationDTO>> response) {
if (response.isSuccessful() && response.body() != null) {
for (ConversationDTO conversation : response.body()) {
if (conversation.getId() != null) {
knownConversationIds.add(conversation.getId());
conversationToCustomerId.put(conversation.getId(), conversation.getCustomerId());
// subscribe to existing conversations to get message notifications
if (stompChatManager != null) {
stompChatManager.subscribeToConversation(conversation.getId());
}
}
}
Log.d(TAG, "Loaded " + knownConversationIds.size() + " existing conversations");
}
startStomp(token, role);
}
@Override
public void onFailure(@NonNull Call<List<ConversationDTO>> call, @NonNull Throwable t) {
Log.e(TAG, "Failed to load existing conversations", t);
//tries to connect if loading fails
startStomp(token, role);
}
});
}
private void startStomp(String token, String role) { private void startStomp(String token, String role) {
if (stompChatManager != null) return; if (stompChatManager != null) return;
@@ -81,9 +115,23 @@ public class ChatNotificationService extends Service {
// Listen for messages in existing conversations // Listen for messages in existing conversations
stompChatManager.setMessageListener(message -> { stompChatManager.setMessageListener(message -> {
if (message != null && !message.getSenderId().equals(currentUserId)) { if (message != null && !message.getSenderId().equals(currentUserId)) {
// Check if this conversation is already active in the view
//if it is then don't make a notification for this chat
if (activeConversationIdInUi != null && activeConversationIdInUi.equals(message.getConversationId())) {
Log.d(TAG, "Disable notification for active conversation: " + message.getConversationId());
return;
}
String title = "New Message";
Long customerId = conversationToCustomerId.get(message.getConversationId());
if (customerId != null && customerIdToName.containsKey(customerId)) {
//append the customer name to the title of the notification
title = "New message from " + customerIdToName.get(customerId);
}
NotificationHelper.showNotification( NotificationHelper.showNotification(
getApplicationContext(), getApplicationContext(),
"New Message", title,
message.getContent(), message.getContent(),
message.getConversationId() message.getConversationId()
); );
@@ -98,13 +146,24 @@ public class ChatNotificationService extends Service {
if (!knownConversationIds.contains(conversation.getId())) { if (!knownConversationIds.contains(conversation.getId())) {
//add the conversation to the set of known conversations //add the conversation to the set of known conversations
knownConversationIds.add(conversation.getId()); knownConversationIds.add(conversation.getId());
conversationToCustomerId.put(conversation.getId(), conversation.getCustomerId());
// Subscribe to the new conversation's messages // Subscribe to the new conversation's messages
stompChatManager.subscribeToConversation(conversation.getId()); stompChatManager.subscribeToConversation(conversation.getId());
String title = "New Support Request";
if (customerIdToName.containsKey(conversation.getCustomerId())) {
//append the customer name to the title of the notification
title = "New Support Request from " + customerIdToName.get(conversation.getCustomerId());
} else {
// Try to fetch customer name for the new request
fetchCustomerName(conversation.getCustomerId());
}
//Display a notification
NotificationHelper.showNotification( NotificationHelper.showNotification(
getApplicationContext(), getApplicationContext(),
"New Support Request", title,
"A customer is requesting assistance", "A customer is requesting assistance",
conversation.getId() conversation.getId()
); );
@@ -132,6 +191,24 @@ public class ChatNotificationService extends Service {
stompChatManager.connect(); stompChatManager.connect();
} }
// Helper function to fetch customer name for a conversation
private void fetchCustomerName(Long customerId) {
CustomerApi customerApi = RetrofitClient.getCustomerApi(this);
customerApi.getCustomerById(customerId).enqueue(new Callback<CustomerDTO>() {
@Override
public void onResponse(@NonNull Call<CustomerDTO> call, @NonNull Response<CustomerDTO> response) {
if (response.isSuccessful() && response.body() != null) {
customerIdToName.put(customerId, response.body().getFullName());
}
}
@Override
public void onFailure(@NonNull Call<CustomerDTO> call, @NonNull Throwable t) {
Log.e(TAG, "Failed to fetch customer name", t);
}
});
}
//When the service is destroyed, disconnect from the websocket //When the service is destroyed, disconnect from the websocket
@Override @Override
public void onDestroy() { public void onDestroy() {

View File

@@ -28,12 +28,15 @@
android:contentDescription="Open menu"/> android:contentDescription="Open menu"/>
<TextView <TextView
android:id="@+id/tvChatTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Customer Chat" android:text="Customer Chat"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold"/> android:textStyle="bold"
android:paddingStart="8dp"
android:paddingEnd="8dp"/>
</LinearLayout> </LinearLayout>