From ce12ffecb73618c74187d883a051bd1904c2b6d0 Mon Sep 17 00:00:00 2001 From: Infi Date: Sat, 15 Nov 2025 01:12:26 +0100 Subject: [PATCH] feat(notifications): use bubbles Signed-off-by: Infi --- .../chat/stoat/c2dm/ChannelRegistrator.kt | 23 +++++-- .../java/chat/stoat/c2dm/HandlerService.kt | 67 +++++++++++++++++-- app/src/main/res/values/strings.xml | 2 + 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/chat/stoat/c2dm/ChannelRegistrator.kt b/app/src/main/java/chat/stoat/c2dm/ChannelRegistrator.kt index ff1af301..298ee88f 100644 --- a/app/src/main/java/chat/stoat/c2dm/ChannelRegistrator.kt +++ b/app/src/main/java/chat/stoat/c2dm/ChannelRegistrator.kt @@ -15,18 +15,18 @@ import chat.stoat.R class ChannelRegistrator(val context: Context) { companion object { - const val CHANNEL_ID_GROUP_CONVERSATIONS = "chat.revolt.c2dm.conversations" + const val CHANNEL_ID_GROUP_CONVERSATIONS = "chat.stoat.c2dm.conversations" + const val CHANNEL_ID_GROUP_CONVERSATIONS_MESSAGES = + "chat.stoat.c2dm.conversations.messages" - const val CHANNEL_ID_GROUP_SOCIAL = "chat.revolt.c2dm.social" - const val CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS = "chat.revolt.c2dm.social.friendrequests" + const val CHANNEL_ID_GROUP_SOCIAL = "chat.stoat.c2dm.social" + const val CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS = "chat.stoat.c2dm.social.friendrequests" } private val notificationManager = getSystemService(context, NotificationManager::class.java) as NotificationManager private fun registerGroups() { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) return - notificationManager.createNotificationChannelGroup( NotificationChannelGroup( CHANNEL_ID_GROUP_CONVERSATIONS, @@ -42,8 +42,6 @@ class ChannelRegistrator(val context: Context) { } private fun registerChannels() { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) return - notificationManager.createNotificationChannel( NotificationChannel( CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS, @@ -55,6 +53,17 @@ class ChannelRegistrator(val context: Context) { context.getString(R.string.notification_channel_friend_requests_description) } ) + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_ID_GROUP_CONVERSATIONS_MESSAGES, + context.getString(R.string.notification_channel_messages), + NotificationManager.IMPORTANCE_HIGH + ).apply { + group = CHANNEL_ID_GROUP_CONVERSATIONS + description = + context.getString(R.string.notification_channel_messages_description) + } + ) } fun register() { diff --git a/app/src/main/java/chat/stoat/c2dm/HandlerService.kt b/app/src/main/java/chat/stoat/c2dm/HandlerService.kt index 01a6ad39..779423ae 100644 --- a/app/src/main/java/chat/stoat/c2dm/HandlerService.kt +++ b/app/src/main/java/chat/stoat/c2dm/HandlerService.kt @@ -3,13 +3,18 @@ package chat.stoat.c2dm import android.app.PendingIntent import android.content.Intent import android.content.pm.PackageManager +import android.os.Build import android.util.Log import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person import androidx.core.app.RemoteInput +import androidx.core.content.LocusIdCompat +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat +import chat.stoat.BuildConfig import chat.stoat.R import chat.stoat.activities.MainActivity import chat.stoat.api.STOAT_BASE @@ -18,7 +23,7 @@ import chat.stoat.api.internals.ULID import chat.stoat.api.routes.push.subscribePush import chat.stoat.api.schemas.Message import chat.stoat.api.schemas.User -import chat.stoat.c2dm.ChannelRegistrator.Companion.CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS +import chat.stoat.c2dm.ChannelRegistrator.Companion.CHANNEL_ID_GROUP_CONVERSATIONS_MESSAGES import chat.stoat.persistence.Database import chat.stoat.persistence.SqlStorage import com.bumptech.glide.Glide @@ -127,6 +132,25 @@ class HandlerService : FirebaseMessagingService() { return } + val shortcutId = "${BuildConfig.APPLICATION_ID}.channel.${message.channel}" + + val conversationIntent = Intent(this, MainActivity::class.java).apply { + action = Intent.ACTION_VIEW + putExtra("channelId", message.channel) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + + val shortcut = ShortcutInfoCompat.Builder(this, shortcutId) + .setShortLabel(channelName) + .setLongLabel(channelName) + .setIcon(IconCompat.createWithBitmap(bitmap)) + .setIntent(conversationIntent) + .setLongLived(true) + .setPerson(author) + .build() + + ShortcutManagerCompat.pushDynamicShortcut(this, shortcut) + val remoteInput = RemoteInput.Builder("content").run { setLabel(getString(R.string.message_context_sheet_actions_reply)) build() @@ -139,17 +163,25 @@ class HandlerService : FirebaseMessagingService() { PendingIntent.getActivity( this, 0, - Intent(this, MainActivity::class.java), - PendingIntent.FLAG_MUTABLE + conversationIntent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) ) .addRemoteInput(remoteInput) .build() - val builder = NotificationCompat.Builder(this, CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS) + val contentIntent = PendingIntent.getActivity( + this, + message.channel.hashCode(), + conversationIntent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + + val builder = NotificationCompat.Builder(this, CHANNEL_ID_GROUP_CONVERSATIONS_MESSAGES) .setSmallIcon(R.drawable.icn_chat_24dp) .setContentTitle(user.displayName ?: user.username) .setContentText(message.content) + .setContentIntent(contentIntent) .setCategory(NotificationCompat.CATEGORY_MESSAGE) .setStyle( NotificationCompat.MessagingStyle(author) @@ -161,7 +193,32 @@ class HandlerService : FirebaseMessagingService() { ) ) .addAction(action) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + + // Android 11 bubbles + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + builder.setShortcutId(shortcutId) + builder.setLocusId(LocusIdCompat(shortcutId)) + + val bubbleIntent = PendingIntent.getActivity( + this, + message.channel.hashCode(), + conversationIntent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + + val bubbleMetadata = NotificationCompat.BubbleMetadata.Builder( + bubbleIntent, + IconCompat.createWithBitmap(bitmap) + ) + .setDesiredHeight(600) + .setAutoExpandBubble(false) + .setSuppressNotification(false) + .build() + + builder.setBubbleMetadata(bubbleMetadata) + } NotificationManagerCompat.from(this).apply { if (ActivityCompat.checkSelfPermission( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bc8a8bba..bbc6254a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -805,6 +805,8 @@ Friends and Social Friend Requests Incoming friend requests, as well as accepted requests you\'ve sent. + Messages + Unread messages from your conversations and channels. Messaging New Line