From 177ac844ee2cad24a7ec9e803e5441e1449e094f Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:13:27 -0600 Subject: [PATCH] Added push notifications when reciving any message and added filter status on pets in Andriod --- .../activities/HomeActivity.java | 8 +- .../fragments/ChatFragment.java | 13 +- .../fragments/listfragments/PetFragment.java | 55 ++++++-- .../services/ChatNotificationService.java | 119 +++++++++++++----- .../utils/NotificationHelper.java | 5 +- .../app/src/main/res/layout/fragment_pet.xml | 36 ++++-- 6 files changed, 181 insertions(+), 55 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java b/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java index 9a286f00..bc646e26 100644 --- a/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java +++ b/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java @@ -88,7 +88,13 @@ public class HomeActivity extends AppCompatActivity { // like clicking a notification or just launching the app from a fresh start private void handleIntent(Intent intent) { if (intent != null && "chat".equals(intent.getStringExtra("navigate_to"))) { - loadFragment(new ChatFragment()); + ChatFragment chatFragment = new ChatFragment(); + if (intent.hasExtra("conversation_id")) { + Bundle args = new Bundle(); + args.putLong("conversation_id", intent.getLongExtra("conversation_id", -1)); + chatFragment.setArguments(args); + } + loadFragment(chatFragment); bottomNav.setSelectedItemId(R.id.nav_chat); } else { loadFragment(new ListFragment()); 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 b73fdc6b..08d9e2f4 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 @@ -121,6 +121,10 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis Log.e(TAG, "No token found"); } + if (getArguments() != null && getArguments().containsKey("conversation_id")) { + activeConversationId = getArguments().getLong("conversation_id"); + } + loadCustomers(); } @@ -165,7 +169,14 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis .collect(Collectors.toList()); chatList.addAll(loaded); chatAdapter.notifyDataSetChanged(); - if (activeConversationId == null) { + + if (activeConversationId != null) { + setConversationActive(true); + if (stompChatManager != null) { + stompChatManager.subscribeToConversation(activeConversationId); + } + loadMessageHistory(activeConversationId); + } else { messageList.clear(); messageAdapter.notifyDataSetChanged(); setConversationActive(false); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java index 6448b5f3..91257673 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java @@ -13,8 +13,11 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.Spinner; import android.widget.Toast; import com.example.petstoremobile.R; @@ -43,6 +46,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen private PetApi api; private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; + private Spinner spinnerStatus; //load pet view @Override @@ -57,6 +61,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen setupRecyclerView(view); setupSearch(view); + setupStatusFilter(view); setupSwipeRefresh(view); loadPetData(); @@ -82,24 +87,48 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen etSearch.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - filterPets(s.toString()); + filterPets(); } @Override public void afterTextChanged(Editable s) {} }); } - private void filterPets(String query) { + //Setup the status filter spinner + private void setupStatusFilter(View view) { + spinnerStatus = view.findViewById(R.id.spinnerStatus); + String[] statuses = {"All Statuses", "Available", "Adopted"}; + ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statuses); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerStatus.setAdapter(adapter); + + spinnerStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + filterPets(); + } + + @Override + public void onNothingSelected(AdapterView parent) {} + }); + } + + // Helper function to filter pets based on search and status filter + private void filterPets() { + String query = etSearch.getText().toString().toLowerCase(); + String selectedStatus = spinnerStatus.getSelectedItem().toString(); + filteredList.clear(); - if (query.isEmpty()) { - filteredList.addAll(petList); - } else { - String lower = query.toLowerCase(); - for (PetDTO p : petList) { - if (p.getPetName().toLowerCase().contains(lower) - || p.getPetSpecies().toLowerCase().contains(lower) - || p.getPetBreed().toLowerCase().contains(lower)) { - filteredList.add(p); - } + for (PetDTO p : petList) { + boolean matchesSearch = query.isEmpty() || + p.getPetName().toLowerCase().contains(query) || + p.getPetSpecies().toLowerCase().contains(query) || + p.getPetBreed().toLowerCase().contains(query); + + boolean matchesStatus = selectedStatus.equals("All Statuses") || + p.getPetStatus().equalsIgnoreCase(selectedStatus); + + if (matchesSearch && matchesStatus) { + filteredList.add(p); } } adapter.notifyDataSetChanged(); @@ -173,7 +202,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen if (response.isSuccessful() && response.body() != null) { petList.clear(); petList.addAll(response.body().getContent()); - filterPets(etSearch.getText().toString()); + filterPets(); } else { Log.e("onResponse: ", response.message()); 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 fadd1886..5d1ef582 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 @@ -5,18 +5,26 @@ import android.content.Intent; import android.os.IBinder; import android.util.Log; import androidx.annotation.Nullable; +import com.example.petstoremobile.api.ChatApi; +import com.example.petstoremobile.api.RetrofitClient; import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.dtos.ConversationDTO; +import com.example.petstoremobile.dtos.MessageDTO; import com.example.petstoremobile.utils.NotificationHelper; import com.example.petstoremobile.websocket.StompChatManager; import java.util.HashSet; +import java.util.List; import java.util.Set; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; // Service to receive notifications when a new conversation is created public class ChatNotificationService extends Service { private static final String TAG = "ChatNotificationService"; private StompChatManager stompChatManager; private final Set knownConversationIds = new HashSet<>(); + private Long currentUserId; //When the service starts, connect to the websocket @Override @@ -32,45 +40,98 @@ public class ChatNotificationService extends Service { TokenManager tm = TokenManager.getInstance(this); String token = tm.getToken(); String role = tm.getRole(); - + currentUserId = tm.getUserId(); if (token != null && stompChatManager == null) { - stompChatManager = new StompChatManager(token, role); - - //When a conversation gets created, show a notification - stompChatManager.setConversationListener(conversation -> { - //check if the conversation exists - if (conversation != null && conversation.getId() != null) { - //check if the conversation is new - if (!knownConversationIds.contains(conversation.getId())) { - //add the conversation to the set of known conversations - knownConversationIds.add(conversation.getId()); - NotificationHelper.showNotification( - getApplicationContext(), - "Customer Support", - "A customer request for assistance" - ); - + // Fetch existing conversations + ChatApi chatApi = RetrofitClient.getChatApi(this); + chatApi.getAllConversations().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, 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()); + } + } + } + Log.d(TAG, "Loaded " + knownConversationIds.size() + " existing conversations"); } - } - }); - stompChatManager.setConnectionListener(new StompChatManager.ConnectionListener() { - // when the websocket is connected, set isFirstLoad to false after a delay - @Override - public void onSocketOpened() { - Log.d(TAG, "WebSocket connected in service"); + startStomp(token, role); } @Override - public void onSocketClosed() { Log.d(TAG, "WebSocket closed in service"); } - - @Override - public void onSocketError() { Log.e(TAG, "WebSocket error in service"); } + 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); + } }); - stompChatManager.connect(); } } + private void startStomp(String token, String role) { + if (stompChatManager != null) return; + + stompChatManager = new StompChatManager(token, role); + + // Listen for messages in existing conversations + stompChatManager.setMessageListener(message -> { + if (message != null && !message.getSenderId().equals(currentUserId)) { + NotificationHelper.showNotification( + getApplicationContext(), + "New Message", + message.getContent(), + message.getConversationId() + ); + } + }); + + //When a conversation gets created, show a notification + stompChatManager.setConversationListener(conversation -> { + //check if the conversation exists + if (conversation != null && conversation.getId() != null) { + //check if the conversation is new + if (!knownConversationIds.contains(conversation.getId())) { + //add the conversation to the set of known conversations + knownConversationIds.add(conversation.getId()); + + // Subscribe to the new conversation's messages + stompChatManager.subscribeToConversation(conversation.getId()); + + NotificationHelper.showNotification( + getApplicationContext(), + "New Support Request", + "A customer is requesting assistance", + conversation.getId() + ); + } + } + }); + + // Subscribe to existing conversations if they were already loaded + for (Long id : knownConversationIds) { + stompChatManager.subscribeToConversation(id); + } + + stompChatManager.setConnectionListener(new StompChatManager.ConnectionListener() { + @Override + public void onSocketOpened() { + Log.d(TAG, "WebSocket connected in service"); + } + + @Override + public void onSocketClosed() { Log.d(TAG, "WebSocket closed in service"); } + + @Override + public void onSocketError() { Log.e(TAG, "WebSocket error in service"); } + }); + stompChatManager.connect(); + } + //When the service is destroyed, disconnect from the websocket @Override public void onDestroy() { diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/NotificationHelper.java b/android/app/src/main/java/com/example/petstoremobile/utils/NotificationHelper.java index 7ec80cb7..cf89b42b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/NotificationHelper.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/NotificationHelper.java @@ -18,7 +18,7 @@ public class NotificationHelper { private static final int NOTIFICATION_ID = 1; // a function to show a notification - public static void showNotification(Context context, String title, String message) { + public static void showNotification(Context context, String title, String message, Long conversationId) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); //check if the device is running on Oreo or higher so we can set up a notification channel @@ -34,6 +34,9 @@ public class NotificationHelper { Intent intent = new Intent(context, HomeActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("navigate_to", "chat"); + if (conversationId != null) { + intent.putExtra("conversation_id", conversationId); + } PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); diff --git a/android/app/src/main/res/layout/fragment_pet.xml b/android/app/src/main/res/layout/fragment_pet.xml index 2ad2d22d..1f625bac 100644 --- a/android/app/src/main/res/layout/fragment_pet.xml +++ b/android/app/src/main/res/layout/fragment_pet.xml @@ -38,18 +38,34 @@ - + android:orientation="horizontal" + android:padding="8dp" + android:gravity="center_vertical"> + + + + +