added push notification when a new conversation is made

This commit is contained in:
Alex
2026-03-24 22:41:54 -06:00
parent b46705396d
commit d3a69b7aea
6 changed files with 216 additions and 13 deletions

View File

@@ -8,6 +8,7 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature
android:name="android.hardware.camera"
@@ -24,6 +25,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PetStoreMobile">
<service
android:name=".services.ChatNotificationService"
android:exported="false" />
<activity
android:name=".activities.HomeActivity"
android:windowSoftInputMode="adjustResize"

View File

@@ -7,7 +7,5 @@ public class PetStoreApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Clear login data on app so when the application closes, the user is logged out and have to re-login
TokenManager.getInstance(this).clearLoginData();
}
}

View File

@@ -1,25 +1,40 @@
package com.example.petstoremobile.activities;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.fragment.app.Fragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.fragments.ChatFragment;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.fragments.ProfileFragment;
import com.example.petstoremobile.services.ChatNotificationService;
import com.google.android.material.bottomnavigation.BottomNavigationView;
public class HomeActivity extends AppCompatActivity {
private BottomNavigationView bottomNav;
// Launcher to ask for notification permission
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (!isGranted) {
Log.w("HomeActivity", "Notification permission denied");
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -34,17 +49,15 @@ public class HomeActivity extends AppCompatActivity {
});
//get the bottom navbar from the layout
BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation);
// Load ListFragment by default only if this is a fresh start
bottomNav = findViewById(R.id.bottom_navigation);
//load the list fragment by default if it's a fresh start
if (savedInstanceState == null) {
loadFragment(new ListFragment());
bottomNav.setSelectedItemId(R.id.nav_list);
handleIntent(getIntent());
}
//when an item in the bar is selected, load the corresponding fragment
//when an item in the bottom bar is selected, load the corresponding fragment
bottomNav.setOnItemSelectedListener(item -> {
if (item.getItemId() == R.id.nav_list) {
loadFragment(new ListFragment());
return true;
@@ -57,13 +70,53 @@ public class HomeActivity extends AppCompatActivity {
}
return false;
});
// Start the notification service and request for notification permission
startNotificationService();
requestNotificationPermission();
}
// Handle new intents when the activity is already running,
// like clicking a notification while the app is in use
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}
//helper function to load a fragment
// Helper function to process intents for navigation.
// 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());
bottomNav.setSelectedItemId(R.id.nav_chat);
} else {
loadFragment(new ListFragment());
bottomNav.setSelectedItemId(R.id.nav_list);
}
}
// Helper function to start the notification service in the background
// to receive notifications when a new conversation is created
private void startNotificationService() {
Intent serviceIntent = new Intent(this, ChatNotificationService.class);
startService(serviceIntent);
}
//Helper function to request for notification permission
private void requestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
}
}
}
//Helper function to load a fragment
private void loadFragment(Fragment fragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
}
}
}

View File

@@ -33,6 +33,7 @@ import com.example.petstoremobile.api.auth.AuthApi;
import com.example.petstoremobile.api.auth.TokenManager;
import com.example.petstoremobile.dtos.ErrorResponse;
import com.example.petstoremobile.dtos.UserDTO;
import com.example.petstoremobile.services.ChatNotificationService;
import com.example.petstoremobile.utils.InputValidator;
import com.google.gson.Gson;
@@ -229,6 +230,10 @@ public class ProfileFragment extends Fragment {
//Logout button
btnLogout.setOnClickListener(v -> {
// Stop notification service before logging out so notifications stop
Intent serviceIntent = new Intent(requireContext(), ChatNotificationService.class);
requireContext().stopService(serviceIntent);
TokenManager.getInstance(requireContext()).clearLoginData(); // clear the token for next login
//get the intent to the main activity and clear the back stack so the back button won't allow the user to go back to the previous screen
Intent intent = new Intent(getActivity(), MainActivity.class);

View File

@@ -0,0 +1,88 @@
package com.example.petstoremobile.services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import com.example.petstoremobile.api.auth.TokenManager;
import com.example.petstoremobile.dtos.ConversationDTO;
import com.example.petstoremobile.utils.NotificationHelper;
import com.example.petstoremobile.websocket.StompChatManager;
import java.util.HashSet;
import java.util.Set;
// 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<Long> knownConversationIds = new HashSet<>();
//When the service starts, connect to the websocket
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Service started");
connectWebSocket();
return START_STICKY;
}
// helper function to connect to the websocket
private void connectWebSocket() {
//get the token and role from the shared preferences
TokenManager tm = TokenManager.getInstance(this);
String token = tm.getToken();
String role = tm.getRole();
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"
);
}
}
});
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");
}
@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() {
if (stompChatManager != null) {
stompChatManager.disconnect();
}
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@@ -0,0 +1,53 @@
package com.example.petstoremobile.utils;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import com.example.petstoremobile.R;
import com.example.petstoremobile.activities.HomeActivity;
// Helper class to show notifications when called
public class NotificationHelper {
private static final String CHANNEL_ID = "chat_notifications";
private static final String CHANNEL_NAME = "Chat Notifications";
private static final String CHANNEL_DESC = "Notifications for new conversations";
private static final int NOTIFICATION_ID = 1;
// a function to show a notification
public static void showNotification(Context context, String title, String message) {
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
// for these devices
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create a notification channel
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(CHANNEL_DESC);
notificationManager.createNotificationChannel(channel);
}
//make the notification navigate the chat if it is clicked
Intent intent = new Intent(context, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("navigate_to", "chat");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
//build the notification for display
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
}