mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 16:22:49 +00:00
Stop repeating messages that are already shown in a room notification
A room notification is rebuilt from the one currently on screen: its messaging style is extracted and the incoming events are appended. Every event was appended unconditionally, so an event re-delivered while its notification was still showing was added again, and the notification re-alerted each time. Skip events whose id is already present in the existing messaging style, and set onlyAlertOnce when the batch adds nothing new. Smart reply failures are exempt, because that path deliberately reuses the same synthesised event id to append the "message not sent" line. Filtering is per event rather than per message so an image and its caption stay a unit. This fixes the visible duplication, not the re-delivery that triggers it, and does not help once the notification has been dismissed.
This commit is contained in:
+12
-2
@@ -196,11 +196,12 @@ class DefaultNotificationCreator(
|
||||
isThread = threadId != null,
|
||||
roomIsGroup = !roomInfo.isDm,
|
||||
)
|
||||
messagingStyle.addMessagesFromEvents(events, imageLoader)
|
||||
val newEvents = messagingStyle.filterOutAlreadyDisplayedEvents(events)
|
||||
messagingStyle.addMessagesFromEvents(newEvents, imageLoader)
|
||||
return builder
|
||||
.setCategory(category)
|
||||
.setNumber(events.size)
|
||||
.setOnlyAlertOnce(roomInfo.isUpdated)
|
||||
.setOnlyAlertOnce(roomInfo.isUpdated || newEvents.isEmpty())
|
||||
.setWhen(lastMessageTimestamp)
|
||||
// MESSAGING_STYLE sets title and content for API 16 and above devices.
|
||||
.setStyle(messagingStyle)
|
||||
@@ -416,6 +417,15 @@ class DefaultNotificationCreator(
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun MessagingStyle.filterOutAlreadyDisplayedEvents(
|
||||
events: List<NotifiableMessageEvent>,
|
||||
): List<NotifiableMessageEvent> {
|
||||
val displayedEventIds = messages.mapNotNullTo(mutableSetOf()) { it.extras.getString(MESSAGE_EVENT_ID) }
|
||||
return events.filter { event ->
|
||||
event.isSmartReplyError() || displayedEventIds.add(event.eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MessagingStyle.addMessagesFromEvents(
|
||||
events: List<NotifiableMessageEvent>,
|
||||
imageLoader: ImageLoader,
|
||||
|
||||
+77
@@ -19,6 +19,7 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_COLOR_INT
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
@@ -40,6 +41,7 @@ import io.element.android.libraries.push.impl.notifications.factories.action.Rej
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aFallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
@@ -302,6 +304,81 @@ class DefaultNotificationCreatorTest : RobolectricTest() {
|
||||
result.commonAssertions()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createMessagesListNotification does not add an event which is already displayed`() = runTest {
|
||||
val sut = createNotificationCreator()
|
||||
val event = aNotifiableMessageEvent(body = "A message")
|
||||
val existingNotification = sut.createRoomNotification(events = listOf(event))
|
||||
assertThat(existingNotification.flags and Notification.FLAG_ONLY_ALERT_ONCE).isEqualTo(0)
|
||||
val result = sut.createRoomNotification(events = listOf(event), existingNotification = existingNotification)
|
||||
assertThat(result.messageTexts()).containsExactly("A message")
|
||||
assertThat(result.flags and Notification.FLAG_ONLY_ALERT_ONCE).isNotEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createMessagesListNotification adds a new event to the already displayed ones`() = runTest {
|
||||
val sut = createNotificationCreator()
|
||||
val existingNotification = sut.createRoomNotification(events = listOf(aNotifiableMessageEvent(body = "A message")))
|
||||
val result = sut.createRoomNotification(
|
||||
events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID_2, body = "Another message")),
|
||||
existingNotification = existingNotification,
|
||||
)
|
||||
assertThat(result.messageTexts()).containsExactly("A message", "Another message").inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createMessagesListNotification adds the smart reply error of an already displayed event`() = runTest {
|
||||
val sut = createNotificationCreator()
|
||||
val event = aNotifiableMessageEvent(body = "A message").copy(outGoingMessage = true)
|
||||
val existingNotification = sut.createRoomNotification(events = listOf(event))
|
||||
val result = sut.createRoomNotification(
|
||||
events = listOf(event.copy(outGoingMessageFailed = true)),
|
||||
existingNotification = existingNotification,
|
||||
)
|
||||
assertThat(result.messageTexts()).containsExactly("A message", "test").inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createMessagesListNotification does not add an image and its caption twice`() = runTest {
|
||||
val sut = createNotificationCreator()
|
||||
val event = aNotifiableMessageEvent(body = "A caption").copy(
|
||||
imageUriString = "aUri",
|
||||
imageMimeType = "image/jpeg",
|
||||
)
|
||||
val existingNotification = sut.createRoomNotification(events = listOf(event))
|
||||
assertThat(existingNotification.messageTexts()).containsExactly("test", "A caption").inOrder()
|
||||
val result = sut.createRoomNotification(events = listOf(event), existingNotification = existingNotification)
|
||||
assertThat(result.messageTexts()).containsExactly("test", "A caption").inOrder()
|
||||
}
|
||||
|
||||
private suspend fun NotificationCreator.createRoomNotification(
|
||||
events: List<NotifiableMessageEvent>,
|
||||
existingNotification: Notification? = null,
|
||||
) = createMessagesListNotification(
|
||||
notificationAccountParams = aNotificationAccountParams(),
|
||||
roomInfo = RoomEventGroupInfo(
|
||||
sessionId = A_SESSION_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
roomDisplayName = "roomDisplayName",
|
||||
hasSmartReplyError = false,
|
||||
shouldBing = false,
|
||||
customSound = null,
|
||||
isUpdated = false,
|
||||
),
|
||||
threadId = null,
|
||||
largeIcon = null,
|
||||
lastMessageTimestamp = 123_456L,
|
||||
tickerText = "tickerText",
|
||||
existingNotification = existingNotification,
|
||||
imageLoader = FakeImageLoader(),
|
||||
events = events,
|
||||
)
|
||||
|
||||
private fun Notification.messageTexts(): List<String?> {
|
||||
val messagingStyle = checkNotNull(NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(this))
|
||||
return messagingStyle.messages.map { it.text?.toString() }
|
||||
}
|
||||
|
||||
private fun Notification.commonAssertions(
|
||||
expectedGroup: String? = aMatrixUser().userId.value,
|
||||
expectedCategory: String? = NotificationCompat.CATEGORY_MESSAGE,
|
||||
|
||||
Reference in New Issue
Block a user