From 16f9d7f90d2f70c6dd44a8e8288fbd98718ea66a Mon Sep 17 00:00:00 2001 From: erik-everardo Date: Tue, 5 Mar 2024 22:03:47 -0600 Subject: [PATCH] WIP push notifications likes --- .../main/java/com/isolaatti/MyApplication.kt | 36 +++++-- .../com/isolaatti/common/CoilImageLoader.kt | 13 +++ .../push_notifications/FcmService.kt | 97 ++++++++++++++++-- .../PushNotificationsApi.kt | 4 +- .../res/drawable-hdpi/ic_notification.png | Bin 0 -> 420 bytes .../res/drawable-mdpi/ic_notification.png | Bin 0 -> 338 bytes .../res/drawable-xhdpi/ic_notification.png | Bin 0 -> 534 bytes .../res/drawable-xxhdpi/ic_notification.png | Bin 0 -> 751 bytes .../res/drawable-xxxhdpi/ic_notification.png | Bin 0 -> 974 bytes app/src/main/res/values/strings.xml | 4 + 10 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-mdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_notification.png diff --git a/app/src/main/java/com/isolaatti/MyApplication.kt b/app/src/main/java/com/isolaatti/MyApplication.kt index 1f65939..6fe80a8 100644 --- a/app/src/main/java/com/isolaatti/MyApplication.kt +++ b/app/src/main/java/com/isolaatti/MyApplication.kt @@ -1,21 +1,24 @@ package com.isolaatti import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager import android.content.Context import android.net.ConnectivityManager +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore -import com.google.firebase.Firebase import com.google.firebase.messaging.FirebaseMessaging import com.isolaatti.connectivity.ConnectivityCallbackImpl -import com.isolaatti.push_notifications.FcmService import com.isolaatti.push_notifications.PushNotificationsApi import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import okhttp3.RequestBody.Companion.toRequestBody import retrofit2.awaitResponse import javax.inject.Inject @@ -27,6 +30,8 @@ class MyApplication : Application() { companion object { lateinit var myApp: MyApplication const val LOG_TAG = "MyApplication" + + const val LIKES_NOTIFICATION_CHANNEL_ID = "like notification" } @Inject @@ -42,18 +47,27 @@ class MyApplication : Application() { connectivityCallbackImpl = ConnectivityCallbackImpl() getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(connectivityCallbackImpl) - FirebaseMessaging.getInstance().token.addOnCompleteListener { - if(!it.isSuccessful) { - Log.w(LOG_TAG, "Failed fetching fcm token") - return@addOnCompleteListener - } - + FirebaseMessaging.getInstance().token.addOnSuccessListener { CoroutineScope(Dispatchers.IO).launch { - val response = pushNotificationsApi.registerDevice(it.result).awaitResponse() - Log.d(FcmService.LOG_TAG, "Device registered. FCM token: $it.result") - Log.d(FcmService.LOG_TAG, "Response: isSuccessful: ${response.isSuccessful}") + val response = pushNotificationsApi.registerDevice(it.toRequestBody()).awaitResponse() } } + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + createLikesNotificationChannel(notificationManager) + } + } + + @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() { diff --git a/app/src/main/java/com/isolaatti/common/CoilImageLoader.kt b/app/src/main/java/com/isolaatti/common/CoilImageLoader.kt index 2f49474..baace6e 100644 --- a/app/src/main/java/com/isolaatti/common/CoilImageLoader.kt +++ b/app/src/main/java/com/isolaatti/common/CoilImageLoader.kt @@ -19,4 +19,17 @@ object CoilImageLoader { add(SvgDecoder.Factory()) }.build() } + + fun getImageLoader(context: Context): ImageLoader { + return ImageLoader + .Builder(context) + .memoryCache { + MemoryCache.Builder(context) + .maxSizePercent(0.25) + .build() + } + .components { + add(SvgDecoder.Factory()) + }.build() + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/push_notifications/FcmService.kt b/app/src/main/java/com/isolaatti/push_notifications/FcmService.kt index c3d3d81..eeb72cb 100644 --- a/app/src/main/java/com/isolaatti/push_notifications/FcmService.kt +++ b/app/src/main/java/com/isolaatti/push_notifications/FcmService.kt @@ -1,22 +1,52 @@ package com.isolaatti.push_notifications import android.Manifest +import android.content.pm.PackageManager 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.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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.ResponseBody.Companion.toResponseBody import retrofit2.awaitResponse +import retrofit2.http.Url import javax.inject.Inject @AndroidEntryPoint class FcmService : FirebaseMessagingService() { companion object { 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 @@ -25,19 +55,70 @@ class FcmService : FirebaseMessagingService() { override fun onNewToken(token: String) { CoroutineScope(Dispatchers.IO).launch { - val response = pushNotificationsApi.registerDevice(token).awaitResponse() - Log.d(LOG_TAG, "Device registered. FCM token: $token") - Log.d(LOG_TAG, "Response: isSuccessful: ${response.isSuccessful}") + val response = pushNotificationsApi.registerDevice(token.toRequestBody()).awaitResponse() } } override fun onMessageReceived(message: RemoteMessage) { super.onMessageReceived(message) + Log.d(LOG_TAG, "onMessageReceived") + val type = message.data["type"] - Log.d(LOG_TAG, "Message received") - message.data.forEach { t, u -> - Log.d(LOG_TAG, "$t $u") + when(type) { + LikeNotification.TYPE -> showLikeNotification(message.data) + + else -> { + Log.i(LOG_TAG, "Not showing notification of unknown type: ${message.data}") + } } } + + private fun showLikeNotification(data: Map) { + 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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/push_notifications/PushNotificationsApi.kt b/app/src/main/java/com/isolaatti/push_notifications/PushNotificationsApi.kt index bfd97a7..5f463d8 100644 --- a/app/src/main/java/com/isolaatti/push_notifications/PushNotificationsApi.kt +++ b/app/src/main/java/com/isolaatti/push_notifications/PushNotificationsApi.kt @@ -1,5 +1,7 @@ package com.isolaatti.push_notifications +import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.Call import retrofit2.http.Multipart import retrofit2.http.PUT @@ -9,6 +11,6 @@ interface PushNotificationsApi { @PUT("/api/push_notifications/register_device") @Multipart - fun registerDevice(@Part("token") token: String): Call + fun registerDevice(@Part("token") token: RequestBody): Call } \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8cd7f549361eff76743a6089d2c1de509338ad GIT binary patch literal 420 zcmV;V0bBlwP)N(~)PKWhe z6WD<+Wc@_H+Y?|1I+OKt{ccZy9cW+H-{~Qu+jt|#H9AN8^}JkQ2b!dBQY*^~J)#48 zt_h4lac!4=NWBU-wC$g3f{Z|MeSu!mXQYpTqU`v6O^_4FhqWR6-tv}i1kBRAsND}& z+AkH92vi34*^<8Zx+fLiuNAF3NfqMV@cl zdUh&kCQu&rZ+kQo=#?JRK|R+5#v3D||B1f&6?Gj^ukw$MKp+tKL%so)D$wEhtmAh8 O0000UtP)cF_B{-CHWu=i5IYiz<6)h> z471lP7&`Y$zHIL7oio|&-A$zu67~v!EWHv>RsE0_Xqi@HJlB~U@a8Sn+)M3(zNvZ~ zU7PEG89)!{P}RQZr#KSF;zVrPTnC~9a%%ld)3#?P5FL%QH;Gl&kz(5bAK^o2fo ze55^kXEQf||0UPxLe=AF*<1%a1EhXN>$FWf9v|pT`c_YRqH`ViWxzIYe?#@&*!uza z;{f~|=Yi_Iw0i>J8Nll+)@M9C^WirTeiQSsiG0vwn;iiEfK|Y@ywhu&>wxE9#A^xB k|Bbami}c85Cj0}w0WZ3!;91qUZvX%Q07*qoM6N<$f>VH)DgXcg literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_notification.png b/app/src/main/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..101c1f548f339a936fd476941cf227ab623b8181 GIT binary patch literal 534 zcmV+x0_pvUP)lmeL}qr~xGC-jU$L3HbN4K}+1btI6h%=KMNyPG01VLuVOiGwz_C0Xqli_6mYKuA!b1Q_Uzxuf`~VI zPEUnrj%NJA@sidYujn-`xW^R?`~e2#V8kvhx^ra$`~fEAU__rgPiDX$U|J56FSNqh zH^3iYR1QW=xbtKN`~iCCrz{{Zu=~!@atvqa2<>%`BQeoINaWw>fhX#-&5nVhZp=IKvFn=iN4FGq$+E@AcFt~_PCKv$*QdLf(!z{zFR!Wres;x zdci*jfW2>4WW$JAS?>h_0l2*fyX1A3!A7$*c9+U#>Fh40UR4milQirq9~2| Y29@>Za6}F6H~;_u07*qoM6N<$f*Ty`ga7~l literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification.png b/app/src/main/res/drawable-xxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..97e60b430f3e934f3fee08bb4e6cf6d511fe6fff GIT binary patch literal 751 zcmVHCU2tTRP{Z~P3M8PJ|Qe0@0`pAX0t@n21b-sCt@%VB(-U+fst64Q1Dfa=)S{YrO}ElsoiQ3H z7tjLwQVVOsj?%Mqp3WHslnBV+`bzpyi)-W}x^X0-WIzV8lkfwnuaoUMV=xE^$Ut_| z-i+)q>-Ya#E62V(;-0t>7*HP8E~Ojj9%NVCN9nOZkJEF=o^j8F0i7!dL=32#aE@MN z7pc=7r9;{q1QH3T1?M@V0}9u)CkUh$kX`q5;*s_Qf%F2hb6h8$YEKYIFCg2T?8KP% z1cCGdx}pOLm$fGdBoYv^&4L5;x6G&P*WMsdA|S2}bT_>&5uFc1=yHHmM1(hBJmbrkI%F` z=Sh)?vjAl8`KM@njG`@I1LAzoXVDgLkq1XAz5?vuXIZ}T;5yu^<%Q*vWuQ*ZSkC6; zLQdR&4EG$ha>a73DzkZgLBUS}TX|p|cPuL{lXzRnfu8~n@W31oEK_+K$%3B(`gvfE z?L0qe@Kb<$uPx%YWhu{38vGRCevuX7e(`ql{G_=b2>+CF)GFXS53IqR{p{lTNrRsP z`gmZDojgBj@KeBk9+<(=DHPZ~<>EBbJ*1 za~;`H+EL3sWFH#Kb0FY$2E~n8fMbuH=D{WQ(=u$?jb?u+guepTAiLHZo7a{dmR4R< zL%>r34#xM1xJ=yP@d?WmUQ=-JTR?~9F;9xf4K%t0cr3uNG4m{scw$73@tT5y=K>sL zuY_L2bvsIhJ6*Dw*Axr_6i{K~S6R+kzR`BdZiLqo3<4HVVe{r9JJRU0+_$_(xvw9) zv$iXE9YLT01u$fHP_!d^tD1wXpb%MTS#RmFyu;$V4wonOElsGSOru-HFs;xYF=AVPyrS0xPKF`D>$eC=dPuj z*VGV5w*Ysl;hAL@nt6*PJbhUQ%3DC?OF`UaMt3Rnw&jRr9VXJ>s3H4`0{-fkGNjwG w1dTeb7ZDK=5fKp)5fKp)5fKp)5s?Y!FL1%HgN@SOwg3PC07*qoM6N<$g5jdU#sB~S literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1a75a8..4bceca9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,4 +189,8 @@ Push notifications Receive notifications to stay informed about your profile activity You won\'t receive notifications. You can change this on your device settings. + Likes notifications + Get notified when someone likes your posts. + %s claps to your post + Tap this notification to go to the post. \ No newline at end of file