From e3ace3c87eccab65483e30e2b82dae52152c8333 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Feb 2026 17:45:23 +0100 Subject: [PATCH] Rely on the SessionObserver to detect a sign out. --- .../DefaultNotificationDrawerManager.kt | 22 +++-- .../DefaultNotificationDrawerManagerTest.kt | 91 +++++++++++++------ ...aultOnMissedCallNotificationHandlerTest.kt | 12 +-- 3 files changed, 77 insertions(+), 48 deletions(-) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index ee72c34b9e..cd3f0faf45 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -24,9 +24,10 @@ import io.element.android.libraries.push.api.notifications.NotificationIdProvide import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreEventInRoom +import io.element.android.libraries.sessionstorage.api.observer.SessionListener +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState -import io.element.android.services.appnavstate.api.currentSessionId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -46,28 +47,30 @@ class DefaultNotificationDrawerManager( private val matrixClientProvider: MatrixClientProvider, private val imageLoaderHolder: ImageLoaderHolder, private val activeNotificationsProvider: ActiveNotificationsProvider, + sessionObserver: SessionObserver, ) : NotificationCleaner { // TODO EAx add a setting per user for this private var useCompleteNotificationFormat = true + private val sessionListener = object : SessionListener { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { + // User signed out, clear all notifications related to the session. + clearAllEvents(SessionId(userId)) + } + } + init { // Observe application state coroutineScope.launch { appNavigationStateService.appNavigationState .collect { onAppNavigationStateChange(it.navigationState) } } + sessionObserver.addListener(sessionListener) } - private var currentAppNavigationState: NavigationState? = null - private fun onAppNavigationStateChange(navigationState: NavigationState) { when (navigationState) { - NavigationState.Root -> { - currentAppNavigationState?.currentSessionId()?.let { sessionId -> - // User signed out, clear all notifications related to the session. - clearAllEvents(sessionId) - } - } + NavigationState.Root -> {} is NavigationState.Session -> {} is NavigationState.Space -> {} is NavigationState.Room -> { @@ -85,7 +88,6 @@ class DefaultNotificationDrawerManager( ) } } - currentAppNavigationState = navigationState } /** diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index 58bb86e683..9bbc2175e5 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -31,7 +31,9 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMe import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.sessionstorage.test.InMemorySessionStore +import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState @@ -205,35 +207,70 @@ class DefaultNotificationDrawerManagerTest { ) } - private fun TestScope.createDefaultNotificationDrawerManager( - notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), - appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), - roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), - summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), - activeNotificationsProvider: FakeActiveNotificationsProvider = FakeActiveNotificationsProvider(), - matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), - sessionStore: SessionStore = InMemorySessionStore(), - enterpriseService: EnterpriseService = FakeEnterpriseService(), - ): DefaultNotificationDrawerManager { - return DefaultNotificationDrawerManager( + @Test + fun `when a session is signed out, clearAllEvent is invoked`() = runTest { + val cancelNotificationResult = lambdaRecorder { _, _ -> } + val notificationDisplayer = FakeNotificationDisplayer( + cancelNotificationResult = cancelNotificationResult, + ) + val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID) + val activeNotificationsProvider = FakeActiveNotificationsProvider( + getNotificationsForSessionResult = { + listOf( + mockk { + every { id } returns summaryId + every { tag } returns null + }, + ) + }, + countResult = { 1 }, + ) + val sessionObserver = FakeSessionObserver() + createDefaultNotificationDrawerManager( notificationDisplayer = notificationDisplayer, - notificationRenderer = NotificationRenderer( - notificationDisplayer = FakeNotificationDisplayer(), - notificationDataFactory = DefaultNotificationDataFactory( - notificationCreator = FakeNotificationCreator(), - roomGroupMessageCreator = roomGroupMessageCreator, - summaryGroupMessageCreator = summaryGroupMessageCreator, - activeNotificationsProvider = activeNotificationsProvider, - stringProvider = FakeStringProvider(), - ), - enterpriseService = enterpriseService, - sessionStore = sessionStore, - ), - appNavigationStateService = appNavigationStateService, - coroutineScope = backgroundScope, - matrixClientProvider = matrixClientProvider, - imageLoaderHolder = FakeImageLoaderHolder(), activeNotificationsProvider = activeNotificationsProvider, + sessionObserver = sessionObserver, + ) + // Simulate a session sign out + sessionObserver.onSessionDeleted(A_SESSION_ID.value) + // Verify we asked to cancel the notification with summaryId + cancelNotificationResult.assertions().isCalledExactly(1).withSequence( + listOf(value(null), value(summaryId)), ) } } + +fun TestScope.createDefaultNotificationDrawerManager( + notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), + notificationRenderer: NotificationRenderer? = null, + appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), + roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), + summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), + activeNotificationsProvider: FakeActiveNotificationsProvider = FakeActiveNotificationsProvider(), + matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), + sessionStore: SessionStore = InMemorySessionStore(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), + sessionObserver: SessionObserver = FakeSessionObserver(), +): DefaultNotificationDrawerManager { + return DefaultNotificationDrawerManager( + notificationDisplayer = notificationDisplayer, + notificationRenderer = notificationRenderer ?: NotificationRenderer( + notificationDisplayer = FakeNotificationDisplayer(), + notificationDataFactory = DefaultNotificationDataFactory( + notificationCreator = FakeNotificationCreator(), + roomGroupMessageCreator = roomGroupMessageCreator, + summaryGroupMessageCreator = summaryGroupMessageCreator, + activeNotificationsProvider = activeNotificationsProvider, + stringProvider = FakeStringProvider(), + ), + enterpriseService = enterpriseService, + sessionStore = sessionStore, + ), + appNavigationStateService = appNavigationStateService, + coroutineScope = backgroundScope, + matrixClientProvider = matrixClientProvider, + imageLoaderHolder = FakeImageLoaderHolder(), + activeNotificationsProvider = activeNotificationsProvider, + sessionObserver = sessionObserver, + ) +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt index 1a47200e12..9bba1c32d3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt @@ -16,13 +16,9 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notification.aNotificationData -import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder -import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver -import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -47,16 +43,10 @@ class DefaultOnMissedCallNotificationHandlerTest { }) val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler( matrixClientProvider = matrixClientProvider, - defaultNotificationDrawerManager = DefaultNotificationDrawerManager( - notificationDisplayer = FakeNotificationDisplayer(), + defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( notificationRenderer = createNotificationRenderer( notificationDataFactory = dataFactory, ), - appNavigationStateService = FakeAppNavigationStateService(), - coroutineScope = backgroundScope, - matrixClientProvider = FakeMatrixClientProvider(), - imageLoaderHolder = FakeImageLoaderHolder(), - activeNotificationsProvider = FakeActiveNotificationsProvider(), ), callNotificationEventResolver = FakeCallNotificationEventResolver(resolveEventLambda = { _, _, _ -> Result.success(aNotifiableMessageEvent())