Notification: show userId in notification when several accounts are configured.

This commit is contained in:
Benoit Marty 2025-10-25 15:31:10 +02:00 committed by Benoit Marty
parent 353c00e032
commit 57ac39673d
20 changed files with 265 additions and 202 deletions

View file

@ -10,7 +10,6 @@ package io.element.android.libraries.push.impl.notifications
import android.app.Notification
import android.graphics.Typeface
import android.text.style.StyleSpan
import androidx.annotation.ColorInt
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import coil3.ImageLoader
@ -19,8 +18,8 @@ import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
@ -31,39 +30,37 @@ import io.element.android.services.toolbox.api.strings.StringProvider
interface NotificationDataFactory {
suspend fun toNotifications(
messages: List<NotifiableMessageEvent>,
currentUser: MatrixUser,
imageLoader: ImageLoader,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<RoomNotification>
@JvmName("toNotificationInvites")
@Suppress("INAPPLICABLE_JVM_NAME")
fun toNotifications(
invites: List<InviteNotifiableEvent>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification>
@JvmName("toNotificationSimpleEvents")
@Suppress("INAPPLICABLE_JVM_NAME")
fun toNotifications(
simpleEvents: List<SimpleNotifiableEvent>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification>
@JvmName("toNotificationFallbackEvents")
@Suppress("INAPPLICABLE_JVM_NAME")
fun toNotifications(
fallback: List<FallbackNotifiableEvent>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification>
fun createSummaryNotification(
currentUser: MatrixUser,
roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): SummaryNotification
}
@ -77,9 +74,8 @@ class DefaultNotificationDataFactory(
) : NotificationDataFactory {
override suspend fun toNotifications(
messages: List<NotifiableMessageEvent>,
currentUser: MatrixUser,
imageLoader: ImageLoader,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<RoomNotification> {
val messagesToDisplay = messages.filterNot { it.canNotBeDisplayed() }
.groupBy { it.roomId }
@ -90,13 +86,12 @@ class DefaultNotificationDataFactory(
eventsByThreadId.map { (threadId, events) ->
val notification = roomGroupMessageCreator.createRoomMessage(
currentUser = currentUser,
events = events,
roomId = roomId,
threadId = threadId,
imageLoader = imageLoader,
existingNotification = getExistingNotificationForMessages(currentUser.userId, roomId, threadId),
color = color,
existingNotification = getExistingNotificationForMessages(notificationAccountParams.user.userId, roomId, threadId),
notificationAccountParams = notificationAccountParams,
)
RoomNotification(
notification = notification,
@ -121,12 +116,12 @@ class DefaultNotificationDataFactory(
@Suppress("INAPPLICABLE_JVM_NAME")
override fun toNotifications(
invites: List<InviteNotifiableEvent>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification> {
return invites.map { event ->
OneShotNotification(
key = event.roomId.value,
notification = notificationCreator.createRoomInvitationNotification(event, color),
notification = notificationCreator.createRoomInvitationNotification(notificationAccountParams, event),
summaryLine = event.description,
isNoisy = event.noisy,
timestamp = event.timestamp
@ -138,12 +133,12 @@ class DefaultNotificationDataFactory(
@Suppress("INAPPLICABLE_JVM_NAME")
override fun toNotifications(
simpleEvents: List<SimpleNotifiableEvent>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification> {
return simpleEvents.map { event ->
OneShotNotification(
key = event.eventId.value,
notification = notificationCreator.createSimpleEventNotification(event, color),
notification = notificationCreator.createSimpleEventNotification(notificationAccountParams, event),
summaryLine = event.description,
isNoisy = event.noisy,
timestamp = event.timestamp
@ -155,12 +150,12 @@ class DefaultNotificationDataFactory(
@Suppress("INAPPLICABLE_JVM_NAME")
override fun toNotifications(
fallback: List<FallbackNotifiableEvent>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification> {
return fallback.map { event ->
OneShotNotification(
key = event.eventId.value,
notification = notificationCreator.createFallbackNotification(event, color),
notification = notificationCreator.createFallbackNotification(notificationAccountParams, event),
summaryLine = event.description.orEmpty(),
isNoisy = false,
timestamp = event.timestamp
@ -169,23 +164,21 @@ class DefaultNotificationDataFactory(
}
override fun createSummaryNotification(
currentUser: MatrixUser,
roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
@ColorInt color: Int,
notificationAccountParams: NotificationAccountParams,
): SummaryNotification {
return when {
roomNotifications.isEmpty() && invitationNotifications.isEmpty() && simpleNotifications.isEmpty() -> SummaryNotification.Removed
else -> SummaryNotification.Update(
summaryGroupMessageCreator.createSummaryNotification(
currentUser = currentUser,
roomNotifications = roomNotifications,
invitationNotifications = invitationNotifications,
simpleNotifications = simpleNotifications,
fallbackNotifications = fallbackNotifications,
color = color,
notificationAccountParams = notificationAccountParams,
)
)
}

View file

@ -15,6 +15,7 @@ import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
@ -22,6 +23,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.flow.first
import timber.log.Timber
@ -32,6 +34,7 @@ class NotificationRenderer(
private val notificationDisplayer: NotificationDisplayer,
private val notificationDataFactory: NotificationDataFactory,
private val enterpriseService: EnterpriseService,
private val sessionStore: SessionStore,
) {
suspend fun render(
currentUser: MatrixUser,
@ -41,18 +44,23 @@ class NotificationRenderer(
) {
val color = enterpriseService.brandColorsFlow(currentUser.userId).first()?.toArgb()
?: NotificationConfig.NOTIFICATION_ACCENT_COLOR
val numberOfAccounts = sessionStore.getAllSessions().size
val notificationAccountParams = NotificationAccountParams(
user = currentUser,
color = color,
showSessionId = numberOfAccounts > 1,
)
val groupedEvents = eventsToProcess.groupByType()
val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, currentUser, imageLoader, color)
val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, color)
val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, color)
val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, color)
val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, imageLoader, notificationAccountParams)
val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, notificationAccountParams)
val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, notificationAccountParams)
val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, notificationAccountParams)
val summaryNotification = notificationDataFactory.createSummaryNotification(
currentUser = currentUser,
roomNotifications = roomNotifications,
invitationNotifications = invitationNotifications,
simpleNotifications = simpleNotifications,
fallbackNotifications = fallbackNotifications,
color = color,
notificationAccountParams = notificationAccountParams,
)
// Remove summary first to avoid briefly displaying it after dismissing the last notification

View file

@ -9,15 +9,14 @@ package io.element.android.libraries.push.impl.notifications
import android.app.Notification
import android.graphics.Bitmap
import androidx.annotation.ColorInt
import coil3.ImageLoader
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
import io.element.android.libraries.push.impl.notifications.factories.isSmartReplyError
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
@ -25,13 +24,12 @@ import io.element.android.services.toolbox.api.strings.StringProvider
interface RoomGroupMessageCreator {
suspend fun createRoomMessage(
currentUser: MatrixUser,
notificationAccountParams: NotificationAccountParams,
events: List<NotifiableMessageEvent>,
roomId: RoomId,
threadId: ThreadId?,
imageLoader: ImageLoader,
existingNotification: Notification?,
@ColorInt color: Int,
): Notification
}
@ -42,13 +40,12 @@ class DefaultRoomGroupMessageCreator(
private val notificationCreator: NotificationCreator,
) : RoomGroupMessageCreator {
override suspend fun createRoomMessage(
currentUser: MatrixUser,
notificationAccountParams: NotificationAccountParams,
events: List<NotifiableMessageEvent>,
roomId: RoomId,
threadId: ThreadId?,
imageLoader: ImageLoader,
existingNotification: Notification?,
@ColorInt color: Int,
): Notification {
val lastKnownRoomEvent = events.last()
val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderDisambiguatedDisplayName ?: "Room name (${roomId.value.take(8)}…)"
@ -66,8 +63,9 @@ class DefaultRoomGroupMessageCreator(
val smartReplyErrors = events.filter { it.isSmartReplyError() }
val roomIsDm = !roomIsGroup
return notificationCreator.createMessagesListNotification(
notificationAccountParams = notificationAccountParams,
RoomEventGroupInfo(
sessionId = currentUser.userId,
sessionId = notificationAccountParams.user.userId,
roomId = roomId,
roomDisplayName = roomName,
isDm = roomIsDm,
@ -80,11 +78,9 @@ class DefaultRoomGroupMessageCreator(
largeIcon = largeBitmap,
lastMessageTimestamp = lastMessageTimestamp,
tickerText = tickerText,
currentUser = currentUser,
existingNotification = existingNotification,
imageLoader = imageLoader,
events = events,
color = color,
)
}

View file

@ -8,22 +8,20 @@
package io.element.android.libraries.push.impl.notifications
import android.app.Notification
import androidx.annotation.ColorInt
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
import io.element.android.services.toolbox.api.strings.StringProvider
interface SummaryGroupMessageCreator {
fun createSummaryNotification(
currentUser: MatrixUser,
notificationAccountParams: NotificationAccountParams,
roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
@ColorInt color: Int,
): Notification
}
@ -42,12 +40,11 @@ class DefaultSummaryGroupMessageCreator(
private val notificationCreator: NotificationCreator,
) : SummaryGroupMessageCreator {
override fun createSummaryNotification(
currentUser: MatrixUser,
notificationAccountParams: NotificationAccountParams,
roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
@ColorInt color: Int,
): Notification {
val summaryIsNoisy = roomNotifications.any { it.shouldBing } ||
invitationNotifications.any { it.isNoisy } ||
@ -61,11 +58,10 @@ class DefaultSummaryGroupMessageCreator(
val nbEvents = roomNotifications.size + simpleNotifications.size
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
return notificationCreator.createSummaryListNotification(
currentUser,
notificationAccountParams = notificationAccountParams,
sumTitle,
noisy = summaryIsNoisy,
lastMessageTimestamp = lastMessageTimestamp,
color = color,
)
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.push.impl.notifications.factories
import androidx.annotation.ColorInt
import io.element.android.libraries.matrix.api.user.MatrixUser
data class NotificationAccountParams(
val user: MatrixUser,
@ColorInt val color: Int,
val showSessionId: Boolean,
)

View file

@ -47,42 +47,40 @@ interface NotificationCreator {
* Create a notification for a Room.
*/
suspend fun createMessagesListNotification(
notificationAccountParams: NotificationAccountParams,
roomInfo: RoomEventGroupInfo,
threadId: ThreadId?,
largeIcon: Bitmap?,
lastMessageTimestamp: Long,
tickerText: String,
currentUser: MatrixUser,
existingNotification: Notification?,
imageLoader: ImageLoader,
events: List<NotifiableMessageEvent>,
@ColorInt color: Int,
): Notification
fun createRoomInvitationNotification(
notificationAccountParams: NotificationAccountParams,
inviteNotifiableEvent: InviteNotifiableEvent,
@ColorInt color: Int,
): Notification
fun createSimpleEventNotification(
notificationAccountParams: NotificationAccountParams,
simpleNotifiableEvent: SimpleNotifiableEvent,
@ColorInt color: Int,
): Notification
fun createFallbackNotification(
notificationAccountParams: NotificationAccountParams,
fallbackNotifiableEvent: FallbackNotifiableEvent,
@ColorInt color: Int,
): Notification
/**
* Create the summary notification.
*/
fun createSummaryListNotification(
currentUser: MatrixUser,
notificationAccountParams: NotificationAccountParams,
compatSummary: String,
noisy: Boolean,
lastMessageTimestamp: Long,
@ColorInt color: Int,
): Notification
fun createDiagnosticNotification(
@ -118,16 +116,15 @@ class DefaultNotificationCreator(
* Create a notification for a Room.
*/
override suspend fun createMessagesListNotification(
notificationAccountParams: NotificationAccountParams,
roomInfo: RoomEventGroupInfo,
threadId: ThreadId?,
largeIcon: Bitmap?,
lastMessageTimestamp: Long,
tickerText: String,
currentUser: MatrixUser,
existingNotification: Notification?,
imageLoader: ImageLoader,
events: List<NotifiableMessageEvent>,
@ColorInt color: Int,
): Notification {
// Build the pending intent for when the notification is clicked
val eventId = events.firstOrNull()?.eventId
@ -135,7 +132,6 @@ class DefaultNotificationCreator(
threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId, threadId)
else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId)
}
val smallIcon = CommonDrawables.ic_notification
val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION }
val channelId = if (containsMissedCall) {
notificationChannels.getChannelForIncomingCall(false)
@ -172,7 +168,7 @@ class DefaultNotificationCreator(
val messagingStyle = existingNotification?.let {
MessagingStyle.extractMessagingStyleFromNotification(it)
} ?: messagingStyleFromCurrentUser(
user = currentUser,
user = notificationAccountParams.user,
imageLoader = imageLoader,
roomName = roomInfo.roomDisplayName,
isThread = threadId != null,
@ -187,9 +183,7 @@ class DefaultNotificationCreator(
.setWhen(lastMessageTimestamp)
// MESSAGING_STYLE sets title and content for API 16 and above devices.
.setStyle(messagingStyle)
.setSmallIcon(smallIcon)
// Set primary color (important for Wear 2.0 Notifications).
.setColor(color)
.configureWith(notificationAccountParams)
// Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for
// 'importance' which is set in the NotificationChannel. The integers representing
// 'priority' are different from 'importance', so make sure you don't mix them.
@ -202,7 +196,7 @@ class DefaultNotificationCreator(
setSound(it)
}
*/
setLights(color, 500, 500)
setLights(notificationAccountParams.color, 500, 500)
} else {
priority = NotificationCompat.PRIORITY_LOW
}
@ -234,10 +228,9 @@ class DefaultNotificationCreator(
}
override fun createRoomInvitationNotification(
notificationAccountParams: NotificationAccountParams,
inviteNotifiableEvent: InviteNotifiableEvent,
@ColorInt color: Int,
): Notification {
val smallIcon = CommonDrawables.ic_notification
val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy)
return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true)
@ -245,8 +238,7 @@ class DefaultNotificationCreator(
.setContentText(inviteNotifiableEvent.description.annotateForDebug(6))
.setGroup(inviteNotifiableEvent.sessionId.value)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
.setSmallIcon(smallIcon)
.setColor(color)
.configureWith(notificationAccountParams)
.apply {
addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent))
addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent))
@ -261,7 +253,7 @@ class DefaultNotificationCreator(
setSound(it)
}
*/
setLights(color, 500, 500)
setLights(notificationAccountParams.color, 500, 500)
} else {
priority = NotificationCompat.PRIORITY_LOW
}
@ -277,10 +269,9 @@ class DefaultNotificationCreator(
}
override fun createSimpleEventNotification(
notificationAccountParams: NotificationAccountParams,
simpleNotifiableEvent: SimpleNotifiableEvent,
@ColorInt color: Int,
): Notification {
val smallIcon = CommonDrawables.ic_notification
val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy)
return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true)
@ -288,8 +279,7 @@ class DefaultNotificationCreator(
.setContentText(simpleNotifiableEvent.description.annotateForDebug(8))
.setGroup(simpleNotifiableEvent.sessionId.value)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
.setSmallIcon(smallIcon)
.setColor(color)
.configureWith(notificationAccountParams)
.setAutoCancel(true)
.setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId, null))
.apply {
@ -301,7 +291,7 @@ class DefaultNotificationCreator(
setSound(it)
}
*/
setLights(color, 500, 500)
setLights(notificationAccountParams.color, 500, 500)
} else {
priority = NotificationCompat.PRIORITY_LOW
}
@ -310,10 +300,9 @@ class DefaultNotificationCreator(
}
override fun createFallbackNotification(
notificationAccountParams: NotificationAccountParams,
fallbackNotifiableEvent: FallbackNotifiableEvent,
@ColorInt color: Int,
): Notification {
val smallIcon = CommonDrawables.ic_notification
val channelId = notificationChannels.getChannelIdForMessage(false)
return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true)
@ -321,8 +310,7 @@ class DefaultNotificationCreator(
.setContentText(fallbackNotifiableEvent.description.orEmpty().annotateForDebug(8))
.setGroup(fallbackNotifiableEvent.sessionId.value)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
.setSmallIcon(smallIcon)
.setColor(color)
.configureWith(notificationAccountParams)
.setAutoCancel(true)
.setWhen(fallbackNotifiableEvent.timestamp)
// Ideally we'd use `createOpenRoomPendingIntent` here, but the broken notification might apply to an invite
@ -343,24 +331,22 @@ class DefaultNotificationCreator(
* Create the summary notification.
*/
override fun createSummaryListNotification(
currentUser: MatrixUser,
notificationAccountParams: NotificationAccountParams,
compatSummary: String,
noisy: Boolean,
lastMessageTimestamp: Long,
@ColorInt color: Int,
): Notification {
val smallIcon = CommonDrawables.ic_notification
val channelId = notificationChannels.getChannelIdForMessage(noisy)
val userId = notificationAccountParams.user.userId
return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true)
// used in compat < N, after summary is built based on child notifications
.setWhen(lastMessageTimestamp)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setSmallIcon(smallIcon)
.setGroup(currentUser.userId.value)
.setGroup(userId.value)
// set this notification as the summary for the group
.setGroupSummary(true)
.setColor(color)
.configureWith(notificationAccountParams)
.apply {
if (noisy) {
// Compat
@ -370,14 +356,14 @@ class DefaultNotificationCreator(
setSound(it)
}
*/
setLights(color, 500, 500)
setLights(notificationAccountParams.color, 500, 500)
} else {
// compat
priority = NotificationCompat.PRIORITY_LOW
}
}
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(currentUser.userId))
.setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(currentUser.userId))
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(userId))
.setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(userId))
.build()
}
@ -487,4 +473,12 @@ class DefaultNotificationCreator(
}
}
private fun NotificationCompat.Builder.configureWith(notificationAccountParams: NotificationAccountParams) = apply {
setSmallIcon(CommonDrawables.ic_notification)
setColor(notificationAccountParams.color)
if (notificationAccountParams.showSessionId) {
setSubText(notificationAccountParams.user.userId.value)
}
}
fun NotifiableMessageEvent.isSmartReplyError() = outGoingMessage && outGoingMessageFailed