diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java index 08d9e2f4..0e405c63 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java @@ -25,6 +25,7 @@ import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.SendMessageRequest; import com.example.petstoremobile.models.Chat; import com.example.petstoremobile.models.Message; +import com.example.petstoremobile.services.ChatNotificationService; import com.example.petstoremobile.websocket.StompChatManager; import java.util.*; import java.util.stream.Collectors; @@ -40,6 +41,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis private RecyclerView rvChatList, rvMessages; private EditText etMessage; private Button btnSend; + private TextView tvChatTitle; // Adapters private ChatAdapter chatAdapter; @@ -75,6 +77,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis rvMessages = view.findViewById(R.id.rvMessages); etMessage = view.findViewById(R.id.etMessage); btnSend = view.findViewById(R.id.btnSend); + tvChatTitle = view.findViewById(R.id.tvChatTitle); ImageButton hamburger = view.findViewById(R.id.btnHamburger); hamburger.setOnClickListener(v -> drawerLayout.openDrawer(GravityCompat.START)); @@ -172,6 +175,13 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis if (activeConversationId != null) { 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) { stompChatManager.subscribeToConversation(activeConversationId); } @@ -197,6 +207,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis public void onChatClick(Chat chat) { activeConversationId = Long.parseLong(chat.getChatId()); setConversationActive(true); + tvChatTitle.setText(chat.getCustomerName()); drawerLayout.closeDrawer(GravityCompat.START); if (stompChatManager != null) { @@ -316,6 +327,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis if (activeConversationId != null && activeConversationId.equals(dto.getId())) { setConversationActive(true); + tvChatTitle.setText(name); } } @@ -397,6 +409,8 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis etMessage.setEnabled(active); if (!active) { activeConversationId = null; + ChatNotificationService.activeConversationIdInUi = null; + if (tvChatTitle != null) tvChatTitle.setText("Customer Chat"); if (stompChatManager != null) { stompChatManager.clearConversationSubscription(); } @@ -406,6 +420,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis etMessage.setHint("Select a chat to start messaging"); } else { etMessage.setHint("Type a message..."); + ChatNotificationService.activeConversationIdInUi = activeConversationId; } } @@ -413,6 +428,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis @Override public void onDestroyView() { super.onDestroyView(); + ChatNotificationService.activeConversationIdInUi = null; if (stompChatManager != null) stompChatManager.disconnect(); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java b/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java index 5d1ef582..a1e16fa6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java +++ b/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java @@ -4,16 +4,22 @@ import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.example.petstoremobile.api.ChatApi; +import com.example.petstoremobile.api.CustomerApi; import com.example.petstoremobile.api.RetrofitClient; import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.dtos.ConversationDTO; +import com.example.petstoremobile.dtos.CustomerDTO; import com.example.petstoremobile.dtos.MessageDTO; +import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.utils.NotificationHelper; import com.example.petstoremobile.websocket.StompChatManager; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import retrofit2.Call; import retrofit2.Callback; @@ -22,8 +28,13 @@ import retrofit2.Response; // Service to receive notifications when a new conversation is created public class ChatNotificationService extends Service { private static final String TAG = "ChatNotificationService"; + + public static Long activeConversationIdInUi = null; + private StompChatManager stompChatManager; private final Set knownConversationIds = new HashSet<>(); + private final Map conversationToCustomerId = new HashMap<>(); + private final Map customerIdToName = new HashMap<>(); private Long currentUserId; //When the service starts, connect to the websocket @@ -43,36 +54,59 @@ public class ChatNotificationService extends Service { currentUserId = tm.getUserId(); if (token != null && stompChatManager == null) { - // Fetch existing conversations - ChatApi chatApi = RetrofitClient.getChatApi(this); - chatApi.getAllConversations().enqueue(new Callback>() { + //load customers to have names associated with customer ids + CustomerApi customerApi = RetrofitClient.getCustomerApi(this); + customerApi.getAllCustomers(0, 1000).enqueue(new Callback>() { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful() && response.body() != null) { - for (ConversationDTO conversation : response.body()) { - if (conversation.getId() != null) { - knownConversationIds.add(conversation.getId()); - // subscribe to existing conversations to get message notifications - if (stompChatManager != null) { - stompChatManager.subscribeToConversation(conversation.getId()); - } - } + for (CustomerDTO customer : response.body().getContent()) { + customerIdToName.put(customer.getCustomerId(), customer.getFullName()); } - Log.d(TAG, "Loaded " + knownConversationIds.size() + " existing conversations"); } - startStomp(token, role); + loadConversationsAndStartStomp(token, role); } @Override - public void onFailure(Call> call, Throwable t) { - Log.e(TAG, "Failed to load existing conversations", t); - //tries to connect if loading fails - startStomp(token, role); + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + Log.e(TAG, "Failed to load customers", t); + loadConversationsAndStartStomp(token, role); } }); } } + private void loadConversationsAndStartStomp(String token, String role) { + // Fetch existing conversations + ChatApi chatApi = RetrofitClient.getChatApi(this); + chatApi.getAllConversations().enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> 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> 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) { if (stompChatManager != null) return; @@ -81,9 +115,23 @@ public class ChatNotificationService extends Service { // Listen for messages in existing conversations stompChatManager.setMessageListener(message -> { 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( getApplicationContext(), - "New Message", + title, message.getContent(), message.getConversationId() ); @@ -98,13 +146,24 @@ public class ChatNotificationService extends Service { if (!knownConversationIds.contains(conversation.getId())) { //add the conversation to the set of known conversations knownConversationIds.add(conversation.getId()); - + conversationToCustomerId.put(conversation.getId(), conversation.getCustomerId()); + // Subscribe to the new conversation's messages 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( getApplicationContext(), - "New Support Request", + title, "A customer is requesting assistance", conversation.getId() ); @@ -132,6 +191,24 @@ public class ChatNotificationService extends Service { 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() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful() && response.body() != null) { + customerIdToName.put(customerId, response.body().getFullName()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "Failed to fetch customer name", t); + } + }); + } + //When the service is destroyed, disconnect from the websocket @Override public void onDestroy() { diff --git a/android/app/src/main/res/layout/fragment_chat.xml b/android/app/src/main/res/layout/fragment_chat.xml index e56e283f..875bccfd 100644 --- a/android/app/src/main/res/layout/fragment_chat.xml +++ b/android/app/src/main/res/layout/fragment_chat.xml @@ -28,12 +28,15 @@ android:contentDescription="Open menu"/> + android:textStyle="bold" + android:paddingStart="8dp" + android:paddingEnd="8dp"/>