diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index d52179f3ef7..f6967de0e51 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -1238,7 +1238,7 @@ class MessagesPresenterTest { } @Test - fun `present - shows a "world_readable" icon if the room is encrypted and history is world_readable`() = runTest { + fun `present - shows a 'world_readable' icon if the room is encrypted and history is world_readable`() = runTest { val presenter = createMessagesPresenter( joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( 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 3dbf09e2273..e328c462092 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 @@ -22,12 +22,20 @@ import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.api.notifications.NotificationIdProvider 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 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.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.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver +import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState +import io.element.android.services.appnavstate.api.currentRoomId +import io.element.android.services.appnavstate.api.currentSessionId +import io.element.android.services.appnavstate.api.currentThreadId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -71,7 +79,10 @@ class DefaultNotificationDrawerManager( private fun onAppNavigationStateChange(navigationState: NavigationState) { when (navigationState) { NavigationState.Root -> {} - is NavigationState.Session -> {} + is NavigationState.Session -> { + // Cleanup the fallback notification + clearFallbackForSession(navigationState.sessionId) + } is NavigationState.Room -> { // Cleanup notification for current room clearMessagesForRoom( @@ -94,14 +105,11 @@ class DefaultNotificationDrawerManager( * Events might be grouped and there might not be one notification per event! */ suspend fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) { - if (notifiableEvent.shouldIgnoreEventInRoom(appNavigationStateService.appNavigationState.value)) { - return - } - renderEvents(listOf(notifiableEvent)) + onNotifiableEventsReceived(listOf(notifiableEvent)) } suspend fun onNotifiableEventsReceived(notifiableEvents: List) { - val eventsToNotify = notifiableEvents.filter { !it.shouldIgnoreEventInRoom(appNavigationStateService.appNavigationState.value) } + val eventsToNotify = notifiableEvents.filter { !appNavigationStateService.appNavigationState.value.shouldIgnoreEvent(it) } renderEvents(eventsToNotify) } @@ -121,6 +129,17 @@ class DefaultNotificationDrawerManager( .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } } + /** + * Remove the fallback notification for the session. + */ + fun clearFallbackForSession(sessionId: SessionId) { + notificationDisplayer.cancelNotification( + DefaultNotificationDataFactory.FALLBACK_NOTIFICATION_TAG, + NotificationIdProvider.getFallbackNotificationId(sessionId), + ) + clearSummaryNotificationIfNeeded(sessionId) + } + /** * Should be called when the application is currently opened and showing timeline for the given [roomId]. * Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room. @@ -192,3 +211,30 @@ class DefaultNotificationDrawerManager( } } } + +/** + * Used to check if a notifiableEvent should be ignored based on the current application navigation state. + */ +private fun AppNavigationState.shouldIgnoreEvent(event: NotifiableEvent): Boolean { + if (!isInForeground) return false + return navigationState.currentSessionId() == event.sessionId && + when (event) { + is NotifiableRingingCallEvent -> { + // Never ignore ringing call notifications + // Note that NotifiableRingingCallEvent are not handled by DefaultNotificationDrawerManager + false + } + is FallbackNotifiableEvent -> { + // Ignore if the room list is currently displayed + navigationState is NavigationState.Session + } + is InviteNotifiableEvent, + is SimpleNotifiableEvent -> { + event.roomId == navigationState.currentRoomId() + } + is NotifiableMessageEvent -> { + event.roomId == navigationState.currentRoomId() && + event.threadId == navigationState.currentThreadId() + } + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt index 53315191105..487b3df597d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt @@ -144,12 +144,12 @@ class DefaultNotificationDataFactory( .getFallbackNotification(notificationAccountParams.user.userId) ?.notification val notification = notificationCreator.createFallbackNotification( - existingNotification, - notificationAccountParams, - fallback, + existingNotification = existingNotification, + notificationAccountParams = notificationAccountParams, + fallbackNotifiableEvents = fallback, ) return OneShotNotification( - tag = "FALLBACK", + tag = FALLBACK_NOTIFICATION_TAG, notification = notification, isNoisy = false, timestamp = fallback.first().timestamp @@ -174,6 +174,10 @@ class DefaultNotificationDataFactory( ) } } + + companion object { + const val FALLBACK_NOTIFICATION_TAG = "FALLBACK" + } } data class RoomNotification( diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt index 07f7f8b3c71..1b29527cac7 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt @@ -15,10 +15,6 @@ 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.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.EventType -import io.element.android.services.appnavstate.api.AppNavigationState -import io.element.android.services.appnavstate.api.currentRoomId -import io.element.android.services.appnavstate.api.currentSessionId -import io.element.android.services.appnavstate.api.currentThreadId data class NotifiableMessageEvent( override val sessionId: SessionId, @@ -56,24 +52,3 @@ data class NotifiableMessageEvent( val imageUri: Uri? get() = imageUriString?.toUri() } - -/** - * Used to check if a notification should be ignored based on the current app and navigation state. - */ -fun NotifiableEvent.shouldIgnoreEventInRoom(appNavigationState: AppNavigationState): Boolean { - val currentSessionId = appNavigationState.navigationState.currentSessionId() ?: return false - return when (val currentRoomId = appNavigationState.navigationState.currentRoomId()) { - null -> false - else -> { - // Never ignore ringing call notifications - if (this is NotifiableRingingCallEvent) { - false - } else { - appNavigationState.isInForeground && - sessionId == currentSessionId && - roomId == currentRoomId && - (this as? NotifiableMessageEvent)?.threadId == appNavigationState.navigationState.currentThreadId() - } - } - } -} 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 83891fe4d05..b080c77a2d1 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 @@ -15,8 +15,11 @@ import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.A_THREAD_ID +import io.element.android.libraries.matrix.test.A_THREAD_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.ui.components.aMatrixUser @@ -28,23 +31,26 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeNotificatio import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator +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.fixtures.aSimpleNotifiableEvent +import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent +import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent 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 import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.services.appnavstate.test.aNavigationState +import io.element.android.services.appnavstate.test.anAppNavigationState import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -92,26 +98,25 @@ class DefaultNotificationDrawerManagerTest { @Test fun `react to applicationStateChange`() = runTest { // For now just call all the API. Later, add more valuable tests. - val appNavigationStateFlow: MutableStateFlow = MutableStateFlow( - AppNavigationState( - navigationState = NavigationState.Root, - isInForeground = true, - ) - ) - val appNavigationStateService = FakeAppNavigationStateService(appNavigationState = appNavigationStateFlow) + val appNavigationStateService = FakeAppNavigationStateService() createDefaultNotificationDrawerManager( appNavigationStateService = appNavigationStateService ) - appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true)) + appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(), isInForeground = true)) runCurrent() - appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID), isInForeground = true)) + appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(A_SESSION_ID), isInForeground = true)) runCurrent() - appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID, A_ROOM_ID), isInForeground = true)) + appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(A_SESSION_ID, A_ROOM_ID), isInForeground = true)) runCurrent() - appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID), isInForeground = true)) + appNavigationStateService.emitNavigationState( + AppNavigationState( + aNavigationState(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID), + isInForeground = true + ) + ) runCurrent() // Like a user sign out - appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true)) + appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(), isInForeground = true)) runCurrent() } @@ -234,6 +239,262 @@ class DefaultNotificationDrawerManagerTest { listOf(value(null), value(summaryId)), ) } + + @Test + fun `when the application is in background, all events trigger a notification`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID), + isInForeground = false, + ), + notifiableEvents = listOf( + aFallbackNotifiableEvent(sessionId = A_SESSION_ID), + aFallbackNotifiableEvent(sessionId = A_SESSION_ID_2), + anInviteNotifiableEvent(sessionId = A_SESSION_ID), + anInviteNotifiableEvent(sessionId = A_SESSION_ID_2), + aSimpleNotifiableEvent(sessionId = A_SESSION_ID), + aSimpleNotifiableEvent(sessionId = A_SESSION_ID_2), + aNotifiableMessageEvent(sessionId = A_SESSION_ID), + aNotifiableMessageEvent(sessionId = A_SESSION_ID_2), + aNotifiableMessageEvent(sessionId = A_SESSION_ID, threadId = A_THREAD_ID), + aNotifiableMessageEvent(sessionId = A_SESSION_ID_2, threadId = A_THREAD_ID_2), + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 2, + ) + + @Test + fun `fallback event is ignored when the room list is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID), + ), + notifiableEvents = listOf(aFallbackNotifiableEvent(sessionId = A_SESSION_ID)), + shouldEmitNotification = false, + ) + + @Test + fun `fallback event is not ignored when a room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID), + ), + notifiableEvents = listOf(aFallbackNotifiableEvent(sessionId = A_SESSION_ID)), + shouldEmitNotification = true, + ) + + @Test + fun `fallback event for other session is not ignored when the room list is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID_2), + ), + notifiableEvents = listOf(aFallbackNotifiableEvent(sessionId = A_SESSION_ID)), + shouldEmitNotification = true, + ) + + @Test + fun `invite notifiable event is emits a notification when the room list is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID), + ), + notifiableEvents = listOf(anInviteNotifiableEvent(sessionId = A_SESSION_ID)), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `invite notifiable event does not emit a notification when the same room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID), + ), + notifiableEvents = listOf( + anInviteNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ) + ), + shouldEmitNotification = false, + ) + + @Test + fun `invite notifiable event emits a notification when another room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2), + ), + notifiableEvents = listOf( + anInviteNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ) + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `simple notifiable event is emits a notification when the room list is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID), + ), + notifiableEvents = listOf(aSimpleNotifiableEvent(sessionId = A_SESSION_ID)), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `simple notifiable event does not emit a notification when the same room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID), + ), + notifiableEvents = listOf( + aSimpleNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ) + ), + shouldEmitNotification = false, + ) + + @Test + fun `simple notifiable event emits a notification when another room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2), + ), + notifiableEvents = listOf( + aSimpleNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ) + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `notifiable event is emits a notification when the room list is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID), + ), + notifiableEvents = listOf(aNotifiableMessageEvent(sessionId = A_SESSION_ID)), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `notifiable event does not emit a notification when the same room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID), + ), + notifiableEvents = listOf( + aNotifiableMessageEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ) + ), + shouldEmitNotification = false, + ) + + @Test + fun `notifiable event for a thread emits a notification when the same room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID), + ), + notifiableEvents = listOf( + aNotifiableMessageEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `notifiable event for a thread does not emit a notification when the same thread is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID), + ), + notifiableEvents = listOf( + aNotifiableMessageEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + ), + shouldEmitNotification = false, + ) + + @Test + fun `notifiable event for a thread emits a notification when another thread is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID_2), + ), + notifiableEvents = listOf( + aNotifiableMessageEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `notifiable event for a thread emits a notification when a thread of another room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2, threadId = A_THREAD_ID_2), + ), + notifiableEvents = listOf( + aNotifiableMessageEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + @Test + fun `notifiable event emits a notification when another room is displayed`() = testOnNotifiableEventReceived( + appNavigationState = anAppNavigationState( + navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2), + ), + notifiableEvents = listOf( + aNotifiableMessageEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ) + ), + shouldEmitNotification = true, + extraInvocationsForNotificationSummary = 1, + ) + + private fun testOnNotifiableEventReceived( + appNavigationState: AppNavigationState, + notifiableEvents: List, + shouldEmitNotification: Boolean, + extraInvocationsForNotificationSummary: Int = 0, + ) = runTest { + val showNotificationResult = lambdaRecorder { _, _, _ -> + true + } + val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( + appNavigationStateService = FakeAppNavigationStateService( + initialAppNavigationState = appNavigationState, + ), + notificationDisplayer = FakeNotificationDisplayer( + showNotificationResult = showNotificationResult, + ) + ) + defaultNotificationDrawerManager.onNotifiableEventsReceived(notifiableEvents) + showNotificationResult.assertions().isCalledExactly( + if (shouldEmitNotification) { + notifiableEvents.size + extraInvocationsForNotificationSummary + } else { + 0 + } + ) + } } fun TestScope.createDefaultNotificationDrawerManager( @@ -251,7 +512,7 @@ fun TestScope.createDefaultNotificationDrawerManager( return DefaultNotificationDrawerManager( notificationDisplayer = notificationDisplayer, notificationRenderer = notificationRenderer ?: NotificationRenderer( - notificationDisplayer = FakeNotificationDisplayer(), + notificationDisplayer = notificationDisplayer, notificationDataFactory = DefaultNotificationDataFactory( notificationCreator = FakeNotificationCreator(), roomGroupMessageCreator = roomGroupMessageCreator, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt index 9b7929b6a02..0ab39d51807 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt @@ -144,8 +144,10 @@ fun aNotifiableCallEvent( rtcNotificationType = rtcNotificationType, ) -fun aFallbackNotifiableEvent() = FallbackNotifiableEvent( - sessionId = A_SESSION_ID, +fun aFallbackNotifiableEvent( + sessionId: SessionId = A_SESSION_ID, +) = FallbackNotifiableEvent( + sessionId = sessionId, roomId = A_ROOM_ID, eventId = AN_EVENT_ID, editedEventId = null, diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt index 8796d6a2eec..5e378f6a315 100644 --- a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt @@ -43,9 +43,9 @@ class DefaultAnalyticsRoomListStateWatcherTest { runCurrent() // Make sure it's warm by changing its internal state - navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) + navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) runCurrent() - navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) runCurrent() // The transaction should be present now @@ -63,9 +63,9 @@ class DefaultAnalyticsRoomListStateWatcherTest { @Test fun `Opening the app in a cold state does nothing`() = runTest { - val navigationStateService = FakeAppNavigationStateService().apply { - appNavigationState.emit(AppNavigationState(NavigationState.Root, false)) - } + val navigationStateService = FakeAppNavigationStateService( + initialAppNavigationState = AppNavigationState(NavigationState.Root, false) + ) val roomListService = FakeRoomListService().apply { postState(RoomListService.State.Idle) } @@ -110,9 +110,9 @@ class DefaultAnalyticsRoomListStateWatcherTest { runCurrent() // Make sure it's warm by changing its internal state - navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) + navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) runCurrent() - navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) runCurrent() // The transaction should be present now @@ -145,9 +145,9 @@ class DefaultAnalyticsRoomListStateWatcherTest { runCurrent() // Make sure it's warm by changing its internal state - navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) + navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) runCurrent() - navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) runCurrent() // The transaction was never added diff --git a/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt b/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt index 24b938d9a53..a8dc9f30869 100644 --- a/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt +++ b/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt @@ -31,7 +31,6 @@ import io.sentry.Sentry import io.sentry.SentryTracer import io.sentry.protocol.SentryId import io.sentry.protocol.SentryTransaction -import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Test import org.junit.runner.RunWith @@ -149,7 +148,7 @@ class SentryAnalyticsProviderTest { ) }, appNavigationStateService = FakeAppNavigationStateService( - MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)) + initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true) ) ).run { init() @@ -182,7 +181,7 @@ class SentryAnalyticsProviderTest { ) }, appNavigationStateService = FakeAppNavigationStateService( - MutableStateFlow(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Root, isInForeground = true) ) ).run { init() @@ -203,7 +202,7 @@ class SentryAnalyticsProviderTest { ) }, appNavigationStateService = FakeAppNavigationStateService( - MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)) + initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true) ) ).run { init() @@ -221,7 +220,7 @@ class SentryAnalyticsProviderTest { buildMeta: BuildMeta = aBuildMeta(), getDatabaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) }, appNavigationStateService: FakeAppNavigationStateService = FakeAppNavigationStateService( - MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)) + initialAppNavigationState = AppNavigationState(NavigationState.Session("owner", A_SESSION_ID), isInForeground = true) ) ) = SentryAnalyticsProvider( context = InstrumentationRegistry.getInstrumentation().targetContext, diff --git a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt index 7860320eeea..e8338cb09c3 100644 --- a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt +++ b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt @@ -11,6 +11,7 @@ package io.element.android.services.appnavstate.test 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.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.NavigationState const val A_SESSION_OWNER = "aSessionOwner" @@ -35,3 +36,11 @@ fun aNavigationState( } return NavigationState.Thread(A_THREAD_OWNER, threadId, room) } + +fun anAppNavigationState( + navigationState: NavigationState = aNavigationState(), + isInForeground: Boolean = true, +) = AppNavigationState( + navigationState = navigationState, + isInForeground = isInForeground, +) diff --git a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt index fd23b7c4225..1e9289f0b51 100644 --- a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt +++ b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt @@ -15,15 +15,21 @@ import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow class FakeAppNavigationStateService( - override val appNavigationState: MutableStateFlow = MutableStateFlow( - AppNavigationState( - navigationState = NavigationState.Root, - isInForeground = true, - ) + initialAppNavigationState: AppNavigationState = AppNavigationState( + navigationState = NavigationState.Root, + isInForeground = true, ), ) : AppNavigationStateService { + private val _appNavigationState: MutableStateFlow = MutableStateFlow(initialAppNavigationState) + override val appNavigationState = _appNavigationState.asStateFlow() + + fun emitNavigationState(state: AppNavigationState) { + _appNavigationState.value = state + } + override fun onNavigateToSession(owner: String, sessionId: SessionId) = Unit override fun onLeavingSession(owner: String) = Unit