WIP push notifications likes

This commit is contained in:
erik-everardo 2024-03-05 22:03:47 -06:00
parent 0aa7b1001f
commit 16f9d7f90d
10 changed files with 134 additions and 20 deletions

View File

@ -1,21 +1,24 @@
package com.isolaatti package com.isolaatti
import android.app.Application import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import com.google.firebase.Firebase
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import com.isolaatti.connectivity.ConnectivityCallbackImpl import com.isolaatti.connectivity.ConnectivityCallbackImpl
import com.isolaatti.push_notifications.FcmService
import com.isolaatti.push_notifications.PushNotificationsApi import com.isolaatti.push_notifications.PushNotificationsApi
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.awaitResponse import retrofit2.awaitResponse
import javax.inject.Inject import javax.inject.Inject
@ -27,6 +30,8 @@ class MyApplication : Application() {
companion object { companion object {
lateinit var myApp: MyApplication lateinit var myApp: MyApplication
const val LOG_TAG = "MyApplication" const val LOG_TAG = "MyApplication"
const val LIKES_NOTIFICATION_CHANNEL_ID = "like notification"
} }
@Inject @Inject
@ -42,18 +47,27 @@ class MyApplication : Application() {
connectivityCallbackImpl = ConnectivityCallbackImpl() connectivityCallbackImpl = ConnectivityCallbackImpl()
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(connectivityCallbackImpl) getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(connectivityCallbackImpl)
FirebaseMessaging.getInstance().token.addOnCompleteListener { FirebaseMessaging.getInstance().token.addOnSuccessListener {
if(!it.isSuccessful) { CoroutineScope(Dispatchers.IO).launch {
Log.w(LOG_TAG, "Failed fetching fcm token") val response = pushNotificationsApi.registerDevice(it.toRequestBody()).awaitResponse()
return@addOnCompleteListener }
} }
CoroutineScope(Dispatchers.IO).launch { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val response = pushNotificationsApi.registerDevice(it.result).awaitResponse() val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
Log.d(FcmService.LOG_TAG, "Device registered. FCM token: $it.result") createLikesNotificationChannel(notificationManager)
Log.d(FcmService.LOG_TAG, "Response: isSuccessful: ${response.isSuccessful}")
} }
} }
@RequiresApi(Build.VERSION_CODES.O)
private fun createLikesNotificationChannel(notificationManager: NotificationManager) {
val name = getString(R.string.likes_notification_channel)
val description = getString(R.string.likes_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(LIKES_NOTIFICATION_CHANNEL_ID, name, importance).also {
it.description = description
}
notificationManager.createNotificationChannel(channel)
} }
override fun onTerminate() { override fun onTerminate() {

View File

@ -19,4 +19,17 @@ object CoilImageLoader {
add(SvgDecoder.Factory()) add(SvgDecoder.Factory())
}.build() }.build()
} }
fun getImageLoader(context: Context): ImageLoader {
return ImageLoader
.Builder(context)
.memoryCache {
MemoryCache.Builder(context)
.maxSizePercent(0.25)
.build()
}
.components {
add(SvgDecoder.Factory())
}.build()
}
} }

View File

@ -1,22 +1,52 @@
package com.isolaatti.push_notifications package com.isolaatti.push_notifications
import android.Manifest import android.Manifest
import android.content.pm.PackageManager
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.graphics.drawable.toBitmap
import coil.Coil
import coil.request.ImageRequest
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import dagger.hilt.EntryPoint import com.isolaatti.MyApplication
import com.isolaatti.R
import com.isolaatti.common.CoilImageLoader
import com.isolaatti.notifications.domain.LikeNotification
import com.isolaatti.utils.UrlGen
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.awaitResponse import retrofit2.awaitResponse
import retrofit2.http.Url
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FcmService : FirebaseMessagingService() { class FcmService : FirebaseMessagingService() {
companion object { companion object {
const val LOG_TAG = "FcmService" const val LOG_TAG = "FcmService"
object NotificationsConstants {
const val NOTIFICATION_ID = "id"
const val TIMESTAMP = "timeStamp"
const val USER_ID = "userId"
const val READ = "read"
const val RELATED_NOTIFICATIONS = "relatedNotifications"
object LikeNotificationConstants {
const val LIKE_ID = "likeId"
const val POST_ID = "postId"
const val AUTHOR_ID = "authorId"
const val AUTHOR_NAME = "authorName"
}
}
} }
@Inject @Inject
@ -25,19 +55,70 @@ class FcmService : FirebaseMessagingService() {
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val response = pushNotificationsApi.registerDevice(token).awaitResponse() val response = pushNotificationsApi.registerDevice(token.toRequestBody()).awaitResponse()
Log.d(LOG_TAG, "Device registered. FCM token: $token")
Log.d(LOG_TAG, "Response: isSuccessful: ${response.isSuccessful}")
} }
} }
override fun onMessageReceived(message: RemoteMessage) { override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message) super.onMessageReceived(message)
Log.d(LOG_TAG, "onMessageReceived")
val type = message.data["type"]
Log.d(LOG_TAG, "Message received") when(type) {
message.data.forEach { t, u -> LikeNotification.TYPE -> showLikeNotification(message.data)
Log.d(LOG_TAG, "$t $u")
else -> {
Log.i(LOG_TAG, "Not showing notification of unknown type: ${message.data}")
} }
} }
} }
private fun showLikeNotification(data: Map<String, String>) {
val notificationId = data[NotificationsConstants.NOTIFICATION_ID]?.toIntOrNull()
val relatedNotifications = data[NotificationsConstants.RELATED_NOTIFICATIONS]?.trimStart('[')?.trimEnd(']')?.split(",")
val likeId = data[NotificationsConstants.LikeNotificationConstants.LIKE_ID]
val postId = data[NotificationsConstants.LikeNotificationConstants.POST_ID]
val authorId = data[NotificationsConstants.LikeNotificationConstants.AUTHOR_ID]?.toIntOrNull()
val authorName = data[NotificationsConstants.LikeNotificationConstants.AUTHOR_NAME]
val imageUrl = authorId?.let { UrlGen.userProfileImage(it, true) }
Log.d(LOG_TAG, data.toString())
val imageRequest = ImageRequest
.Builder(this)
.data(imageUrl)
.fallback(R.drawable.baseline_person_24)
.target { drawable ->
val notificationBuilder = NotificationCompat.Builder(this, MyApplication.LIKES_NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(getString(R.string.like_notification_title, authorName ?: ""))
.setContentText(getString(R.string.like_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setLargeIcon(drawable.toBitmap())
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return@target
}
Log.v(LOG_TAG, "Notification id: $notificationId")
// notificationId should never be null
if(notificationId == null) {
return@target
}
NotificationManagerCompat.from(this).run {
relatedNotifications?.forEach {
it.toIntOrNull()?.let { relatedNotificationId -> cancel(relatedNotificationId) }
}
notify(notificationId, notificationBuilder.build())
}
}.build()
CoilImageLoader.getImageLoader(this).enqueue(imageRequest)
}
}

View File

@ -1,5 +1,7 @@
package com.isolaatti.push_notifications package com.isolaatti.push_notifications
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Multipart import retrofit2.http.Multipart
import retrofit2.http.PUT import retrofit2.http.PUT
@ -9,6 +11,6 @@ interface PushNotificationsApi {
@PUT("/api/push_notifications/register_device") @PUT("/api/push_notifications/register_device")
@Multipart @Multipart
fun registerDevice(@Part("token") token: String): Call<Unit> fun registerDevice(@Part("token") token: RequestBody): Call<Unit>
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

View File

@ -189,4 +189,8 @@
<string name="push_notifications_dialog_title">Push notifications</string> <string name="push_notifications_dialog_title">Push notifications</string>
<string name="push_notifications_dialog_message">Receive notifications to stay informed about your profile activity</string> <string name="push_notifications_dialog_message">Receive notifications to stay informed about your profile activity</string>
<string name="push_notifications_dialog_rejected_message">You won\'t receive notifications. You can change this on your device settings.</string> <string name="push_notifications_dialog_rejected_message">You won\'t receive notifications. You can change this on your device settings.</string>
<string name="likes_notification_channel">Likes notifications</string>
<string name="likes_notification_channel_description">Get notified when someone likes your posts.</string>
<string name="like_notification_title">%s claps to your post</string>
<string name="like_notification_text">Tap this notification to go to the post.</string>
</resources> </resources>