Merge pull request #6182 from element-hq/feature/bma/fixAppNavigationState

Rely on the SessionObserver to detect a sign out.
This commit is contained in:
Benoit Marty
2026-02-11 20:40:32 +01:00
committed by GitHub
3 changed files with 77 additions and 48 deletions

View File

@@ -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
}
/**

View File

@@ -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<String?, Int, Unit> { _, _ -> }
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,
)
}

View File

@@ -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())