SDPCoachMe/SDP-2023

View on GitHub
app/src/main/java/com/github/sdpcoachme/messaging/InAppNotifier.kt

Summary

Maintainability
A
1 hr
Test Coverage
A
94%
package com.github.sdpcoachme.messaging

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import androidx.core.app.NotificationCompat
import com.github.sdpcoachme.auth.LoginActivity
import com.github.sdpcoachme.database.CachingStore
import com.github.sdpcoachme.profile.CoachesListActivity
import com.google.firebase.messaging.FirebaseMessagingService
import java.util.concurrent.CompletableFuture

/**
 * This class is used to send in-app push notifications to the user while the app is in the foreground.
 * Whenever a push notification is received when in the foreground, this class is called from the
 * InAppNotificationService class to send the adapted notification (as we know more about the app's state
 * when the app is open).
 * @see InAppNotificationService
 *
 * @param context Context of the application
 * @param store CachingStore of the application
 */
class InAppNotifier(val context: Context, val store: CachingStore) {
    private val channelId = "fcm_default_channel"

    /**
     * Sends a push notification with the supplied arguments as parameters.
     *
     * @param title Title of the notification
     * @param body Body of the notification
     * @param chatId Id of the chat
     * @param notificationType Type of the notification
     */
    fun sendNotification(title: String?, body: String?, chatId: String?, notificationType: String?) {
        val notificationTitle = title?: "New message"
        val notificationBody = body?: "You have a new message"
        val id = chatId ?: ""

        // to enable multiple notification types, we check the notificationType field
        val type = notificationType ?: ""

        // Create and send a customized notification.
        if (type == "messaging") {
            sendMessagingNotification(notificationTitle, notificationBody, id)
        }
    }

    /**
     * Create and show a notification containing the received FCM message for in-app push notifications.
     * Depending on the current state of the application, the notification will open the chat activity,
     * the coaches list activity or the login activity (based on current user's email and the sender argument).
     *
     * @param notificationTitle Title of the notification
     * @param notificationBody Body of the notification
     * @param chatId Id of the chat
     */
    private fun sendMessagingNotification(notificationTitle: String, notificationBody: String, chatId: String) {

        // The more info we receive, the more we can customize the notification's behaviour (up until the chat itself)
        store.isLoggedIn().thenCompose { isLoggedIn ->
            if (isLoggedIn) store.getCurrentEmail() else CompletableFuture.completedFuture("")
        }.thenAccept { email ->
            val intent = when {
                email.isEmpty() ->
                    Intent(context, LoginActivity::class.java)
                        .putExtra("chatId", chatId)
                        .setAction("OPEN_CHAT_ACTIVITY")
                else -> {
                    val emailExistsIntent = Intent(context, CoachesListActivity::class.java)
                        .putExtra("isViewingContacts", true)
                        .putExtra("pushNotification_currentUserEmail", email)
                    if (chatId.isNotEmpty()) {
                        emailExistsIntent
                            .putExtra("openChat", true)
                            .putExtra("chatId", chatId)
                    }
                    emailExistsIntent
                }
            }

            // Create the pending intent to be used when the notification is clicked
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            val pendingIntent = PendingIntent.getActivity(
                context,
                0 /* Request code */,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            val (notificationBuilder, notificationManager, channel) =
                createNotificationElements(notificationTitle, notificationBody, pendingIntent)

            notificationManager.createNotificationChannel(channel)
            // current time is used to make sure that each notification id is unique
            notificationManager.notify(System.currentTimeMillis().toInt(), notificationBuilder.build())
        }
    }

    /**
     * Creates the notification elements.
     *
     * @param notificationTitle Title of the notification
     * @param notificationBody Body of the notification
     * @param pendingIntent Intent to be used when the notification is clicked
     */
    private fun createNotificationElements(
        notificationTitle: String,
        notificationBody: String,
        pendingIntent: PendingIntent?
    ): Triple<NotificationCompat.Builder, NotificationManager, NotificationChannel> {

        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(context, channelId)
            .setContentTitle(notificationTitle) // Set the title of the notification
            .setContentText(notificationBody) // Set the body of the notification
            .setSmallIcon(context.applicationInfo.icon)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent)
        val notificationManager =
            context.getSystemService(FirebaseMessagingService.NOTIFICATION_SERVICE) as NotificationManager

        // Since android Oreo notification channel is needed.
        val channel = NotificationChannel(
            channelId,
            "Channel human readable title",
            NotificationManager.IMPORTANCE_DEFAULT
        )
        return Triple(notificationBuilder, notificationManager, channel)
    }
}