Notification events resolving and rendering in batches (#4722)

- Use `NotiticationService.getNotifications()` function so we resolve the events in bulk.
- Added `NotifierResolverQueue` to group the notifications to resolve based on a debounce strategy.
- Batch rendering of these events as notifications.
This commit is contained in:
Jorge Martin Espinosa 2025-05-26 17:10:20 +02:00 committed by GitHub
parent f0c9f8294a
commit f455085e08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 882 additions and 523 deletions

View file

@ -152,7 +152,7 @@ class RustMatrixClient(
)
private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(innerSyncService)
private val innerNotificationClient = runBlocking { innerClient.notificationClient(notificationProcessSetup) }
private val notificationService = RustNotificationService(innerNotificationClient, dispatchers, clock)
private val notificationService = RustNotificationService(sessionId, innerNotificationClient, dispatchers, clock)
private val notificationSettingsService = RustNotificationSettingsService(innerClient, sessionCoroutineScope, dispatchers)
private val encryptionService = RustEncryptionService(
client = innerClient,

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.EventId
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.UserId
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
@ -25,6 +26,7 @@ class NotificationMapper(
private val notificationContentMapper = NotificationContentMapper()
fun map(
sessionId: SessionId,
eventId: EventId,
roomId: RoomId,
notificationItem: NotificationItem
@ -35,6 +37,7 @@ class NotificationMapper(
activeMembersCount = item.roomInfo.joinedMembersCount.toInt(),
)
NotificationData(
sessionId = sessionId,
eventId = eventId,
// FIXME once the `NotificationItem` in the SDK returns the thread id
threadId = null,

View file

@ -10,28 +10,45 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.EventId
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.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
import timber.log.Timber
class RustNotificationService(
private val sessionId: SessionId,
private val notificationClient: NotificationClient,
private val dispatchers: CoroutineDispatchers,
clock: SystemClock,
) : NotificationService {
private val notificationMapper: NotificationMapper = NotificationMapper(clock)
override suspend fun getNotification(
roomId: RoomId,
eventId: EventId,
): Result<NotificationData?> = withContext(dispatchers.io) {
override suspend fun getNotifications(
ids: Map<RoomId, List<EventId>>
): Result<Map<EventId, NotificationData>> = withContext(dispatchers.io) {
runCatching {
val item = notificationClient.getNotification(roomId.value, eventId.value)
item?.use {
notificationMapper.map(eventId, roomId, it)
val requests = ids.map { (roomId, eventIds) ->
NotificationItemsRequest(
roomId = roomId.value,
eventIds = eventIds.map { it.value }
)
}
val items = notificationClient.getNotifications(requests)
buildMap {
val eventIds = requests.flatMap { it.eventIds }
for (eventId in eventIds) {
val item = items[eventId]
if (item != null) {
val roomId = RoomId(requests.find { it.eventIds.contains(eventId) }?.roomId!!)
put(EventId(eventId), notificationMapper.map(sessionId, EventId(eventId), roomId, item))
} else {
Timber.e("Could not retrieve event for notification with $eventId")
}
}
}
}
}

View file

@ -7,15 +7,15 @@
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.tests.testutils.simulateLongTask
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.NotificationItem
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
class FakeRustNotificationClient(
var notificationItemResult: NotificationItem? = null
var notificationItemResult: Map<String, NotificationItem> = emptyMap(),
) : NotificationClient(NoPointer) {
override suspend fun getNotification(roomId: String, eventId: String): NotificationItem? = simulateLongTask {
notificationItemResult
override suspend fun getNotifications(requests: List<NotificationItemsRequest>): Map<String, NotificationItem> {
return notificationItemResult
}
}

View file

@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustNotificat
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
@ -28,12 +29,12 @@ class RustNotificationServiceTest {
@Test
fun test() = runTest {
val notificationClient = FakeRustNotificationClient(
notificationItemResult = aRustNotificationItem(),
notificationItemResult = mapOf(AN_EVENT_ID.value to aRustNotificationItem()),
)
val sut = createRustNotificationService(
notificationClient = notificationClient,
)
val result = sut.getNotification(A_ROOM_ID, AN_EVENT_ID).getOrThrow()!!
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!
assertThat(result.isEncrypted).isTrue()
assertThat(result.content).isEqualTo(
NotificationContent.MessageLike.RoomMessage(
@ -51,6 +52,7 @@ class RustNotificationServiceTest {
clock: SystemClock = FakeSystemClock(),
) =
RustNotificationService(
sessionId = A_SESSION_ID,
notificationClient = notificationClient,
dispatchers = testCoroutineDispatchers(),
clock = clock,