Merge pull request #6190 from element-hq/feature/bma/fallbackNotificationCleanup
Fallback notification cleanup
This commit is contained in:
commit
f9210a3506
10 changed files with 375 additions and 73 deletions
|
|
@ -1238,7 +1238,7 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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(
|
val presenter = createMessagesPresenter(
|
||||||
joinedRoom = FakeJoinedRoom(
|
joinedRoom = FakeJoinedRoom(
|
||||||
baseRoom = FakeBaseRoom(
|
baseRoom = FakeBaseRoom(
|
||||||
|
|
|
||||||
|
|
@ -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.NotificationCleaner
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
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.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.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.SessionListener
|
||||||
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
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.AppNavigationStateService
|
||||||
import io.element.android.services.appnavstate.api.NavigationState
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -71,7 +79,10 @@ class DefaultNotificationDrawerManager(
|
||||||
private fun onAppNavigationStateChange(navigationState: NavigationState) {
|
private fun onAppNavigationStateChange(navigationState: NavigationState) {
|
||||||
when (navigationState) {
|
when (navigationState) {
|
||||||
NavigationState.Root -> {}
|
NavigationState.Root -> {}
|
||||||
is NavigationState.Session -> {}
|
is NavigationState.Session -> {
|
||||||
|
// Cleanup the fallback notification
|
||||||
|
clearFallbackForSession(navigationState.sessionId)
|
||||||
|
}
|
||||||
is NavigationState.Room -> {
|
is NavigationState.Room -> {
|
||||||
// Cleanup notification for current room
|
// Cleanup notification for current room
|
||||||
clearMessagesForRoom(
|
clearMessagesForRoom(
|
||||||
|
|
@ -94,14 +105,11 @@ class DefaultNotificationDrawerManager(
|
||||||
* Events might be grouped and there might not be one notification per event!
|
* Events might be grouped and there might not be one notification per event!
|
||||||
*/
|
*/
|
||||||
suspend fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
suspend fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||||
if (notifiableEvent.shouldIgnoreEventInRoom(appNavigationStateService.appNavigationState.value)) {
|
onNotifiableEventsReceived(listOf(notifiableEvent))
|
||||||
return
|
|
||||||
}
|
|
||||||
renderEvents(listOf(notifiableEvent))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onNotifiableEventsReceived(notifiableEvents: List<NotifiableEvent>) {
|
suspend fun onNotifiableEventsReceived(notifiableEvents: List<NotifiableEvent>) {
|
||||||
val eventsToNotify = notifiableEvents.filter { !it.shouldIgnoreEventInRoom(appNavigationStateService.appNavigationState.value) }
|
val eventsToNotify = notifiableEvents.filter { !appNavigationStateService.appNavigationState.value.shouldIgnoreEvent(it) }
|
||||||
renderEvents(eventsToNotify)
|
renderEvents(eventsToNotify)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,6 +129,17 @@ class DefaultNotificationDrawerManager(
|
||||||
.forEach { notificationDisplayer.cancelNotification(it.tag, it.id) }
|
.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].
|
* 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.
|
* 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,12 +144,12 @@ class DefaultNotificationDataFactory(
|
||||||
.getFallbackNotification(notificationAccountParams.user.userId)
|
.getFallbackNotification(notificationAccountParams.user.userId)
|
||||||
?.notification
|
?.notification
|
||||||
val notification = notificationCreator.createFallbackNotification(
|
val notification = notificationCreator.createFallbackNotification(
|
||||||
existingNotification,
|
existingNotification = existingNotification,
|
||||||
notificationAccountParams,
|
notificationAccountParams = notificationAccountParams,
|
||||||
fallback,
|
fallbackNotifiableEvents = fallback,
|
||||||
)
|
)
|
||||||
return OneShotNotification(
|
return OneShotNotification(
|
||||||
tag = "FALLBACK",
|
tag = FALLBACK_NOTIFICATION_TAG,
|
||||||
notification = notification,
|
notification = notification,
|
||||||
isNoisy = false,
|
isNoisy = false,
|
||||||
timestamp = fallback.first().timestamp
|
timestamp = fallback.first().timestamp
|
||||||
|
|
@ -174,6 +174,10 @@ class DefaultNotificationDataFactory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FALLBACK_NOTIFICATION_TAG = "FALLBACK"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoomNotification(
|
data class RoomNotification(
|
||||||
|
|
|
||||||
|
|
@ -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.ThreadId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
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(
|
data class NotifiableMessageEvent(
|
||||||
override val sessionId: SessionId,
|
override val sessionId: SessionId,
|
||||||
|
|
@ -56,24 +52,3 @@ data class NotifiableMessageEvent(
|
||||||
val imageUri: Uri?
|
val imageUri: Uri?
|
||||||
get() = imageUriString?.toUri()
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,11 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
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.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_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
|
||||||
|
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
|
||||||
|
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.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
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.FakeNotificationDisplayer
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
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.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.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.SessionStore
|
||||||
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver
|
import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver
|
||||||
import io.element.android.services.appnavstate.api.AppNavigationState
|
import io.element.android.services.appnavstate.api.AppNavigationState
|
||||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
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.FakeAppNavigationStateService
|
||||||
import io.element.android.services.appnavstate.test.aNavigationState
|
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.any
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runCurrent
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -92,26 +98,25 @@ class DefaultNotificationDrawerManagerTest {
|
||||||
@Test
|
@Test
|
||||||
fun `react to applicationStateChange`() = runTest {
|
fun `react to applicationStateChange`() = runTest {
|
||||||
// For now just call all the API. Later, add more valuable tests.
|
// For now just call all the API. Later, add more valuable tests.
|
||||||
val appNavigationStateFlow: MutableStateFlow<AppNavigationState> = MutableStateFlow(
|
val appNavigationStateService = FakeAppNavigationStateService()
|
||||||
AppNavigationState(
|
|
||||||
navigationState = NavigationState.Root,
|
|
||||||
isInForeground = true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val appNavigationStateService = FakeAppNavigationStateService(appNavigationState = appNavigationStateFlow)
|
|
||||||
createDefaultNotificationDrawerManager(
|
createDefaultNotificationDrawerManager(
|
||||||
appNavigationStateService = appNavigationStateService
|
appNavigationStateService = appNavigationStateService
|
||||||
)
|
)
|
||||||
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true))
|
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(), isInForeground = true))
|
||||||
runCurrent()
|
runCurrent()
|
||||||
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID), isInForeground = true))
|
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(A_SESSION_ID), isInForeground = true))
|
||||||
runCurrent()
|
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()
|
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()
|
runCurrent()
|
||||||
// Like a user sign out
|
// Like a user sign out
|
||||||
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true))
|
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(), isInForeground = true))
|
||||||
runCurrent()
|
runCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,6 +239,262 @@ class DefaultNotificationDrawerManagerTest {
|
||||||
listOf(value(null), value(summaryId)),
|
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<NotifiableEvent>,
|
||||||
|
shouldEmitNotification: Boolean,
|
||||||
|
extraInvocationsForNotificationSummary: Int = 0,
|
||||||
|
) = runTest {
|
||||||
|
val showNotificationResult = lambdaRecorder<String?, Int, Notification, Boolean> { _, _, _ ->
|
||||||
|
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(
|
fun TestScope.createDefaultNotificationDrawerManager(
|
||||||
|
|
@ -251,7 +512,7 @@ fun TestScope.createDefaultNotificationDrawerManager(
|
||||||
return DefaultNotificationDrawerManager(
|
return DefaultNotificationDrawerManager(
|
||||||
notificationDisplayer = notificationDisplayer,
|
notificationDisplayer = notificationDisplayer,
|
||||||
notificationRenderer = notificationRenderer ?: NotificationRenderer(
|
notificationRenderer = notificationRenderer ?: NotificationRenderer(
|
||||||
notificationDisplayer = FakeNotificationDisplayer(),
|
notificationDisplayer = notificationDisplayer,
|
||||||
notificationDataFactory = DefaultNotificationDataFactory(
|
notificationDataFactory = DefaultNotificationDataFactory(
|
||||||
notificationCreator = FakeNotificationCreator(),
|
notificationCreator = FakeNotificationCreator(),
|
||||||
roomGroupMessageCreator = roomGroupMessageCreator,
|
roomGroupMessageCreator = roomGroupMessageCreator,
|
||||||
|
|
|
||||||
|
|
@ -144,8 +144,10 @@ fun aNotifiableCallEvent(
|
||||||
rtcNotificationType = rtcNotificationType,
|
rtcNotificationType = rtcNotificationType,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aFallbackNotifiableEvent() = FallbackNotifiableEvent(
|
fun aFallbackNotifiableEvent(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId: SessionId = A_SESSION_ID,
|
||||||
|
) = FallbackNotifiableEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
editedEventId = null,
|
editedEventId = null,
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
// Make sure it's warm by changing its internal state
|
// 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()
|
runCurrent()
|
||||||
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
// The transaction should be present now
|
// The transaction should be present now
|
||||||
|
|
@ -63,9 +63,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Opening the app in a cold state does nothing`() = runTest {
|
fun `Opening the app in a cold state does nothing`() = runTest {
|
||||||
val navigationStateService = FakeAppNavigationStateService().apply {
|
val navigationStateService = FakeAppNavigationStateService(
|
||||||
appNavigationState.emit(AppNavigationState(NavigationState.Root, false))
|
initialAppNavigationState = AppNavigationState(NavigationState.Root, false)
|
||||||
}
|
)
|
||||||
val roomListService = FakeRoomListService().apply {
|
val roomListService = FakeRoomListService().apply {
|
||||||
postState(RoomListService.State.Idle)
|
postState(RoomListService.State.Idle)
|
||||||
}
|
}
|
||||||
|
|
@ -110,9 +110,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
// Make sure it's warm by changing its internal state
|
// 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()
|
runCurrent()
|
||||||
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
// The transaction should be present now
|
// The transaction should be present now
|
||||||
|
|
@ -145,9 +145,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
// Make sure it's warm by changing its internal state
|
// 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()
|
runCurrent()
|
||||||
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
// The transaction was never added
|
// The transaction was never added
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import io.sentry.Sentry
|
||||||
import io.sentry.SentryTracer
|
import io.sentry.SentryTracer
|
||||||
import io.sentry.protocol.SentryId
|
import io.sentry.protocol.SentryId
|
||||||
import io.sentry.protocol.SentryTransaction
|
import io.sentry.protocol.SentryTransaction
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
@ -149,7 +148,7 @@ class SentryAnalyticsProviderTest {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
appNavigationStateService = FakeAppNavigationStateService(
|
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 {
|
).run {
|
||||||
init()
|
init()
|
||||||
|
|
@ -182,7 +181,7 @@ class SentryAnalyticsProviderTest {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
appNavigationStateService = FakeAppNavigationStateService(
|
appNavigationStateService = FakeAppNavigationStateService(
|
||||||
MutableStateFlow(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
|
initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)
|
||||||
)
|
)
|
||||||
).run {
|
).run {
|
||||||
init()
|
init()
|
||||||
|
|
@ -203,7 +202,7 @@ class SentryAnalyticsProviderTest {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
appNavigationStateService = FakeAppNavigationStateService(
|
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 {
|
).run {
|
||||||
init()
|
init()
|
||||||
|
|
@ -221,7 +220,7 @@ class SentryAnalyticsProviderTest {
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
buildMeta: BuildMeta = aBuildMeta(),
|
||||||
getDatabaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) },
|
getDatabaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) },
|
||||||
appNavigationStateService: FakeAppNavigationStateService = FakeAppNavigationStateService(
|
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(
|
) = SentryAnalyticsProvider(
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||||
|
|
|
||||||
|
|
@ -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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
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.ThreadId
|
||||||
|
import io.element.android.services.appnavstate.api.AppNavigationState
|
||||||
import io.element.android.services.appnavstate.api.NavigationState
|
import io.element.android.services.appnavstate.api.NavigationState
|
||||||
|
|
||||||
const val A_SESSION_OWNER = "aSessionOwner"
|
const val A_SESSION_OWNER = "aSessionOwner"
|
||||||
|
|
@ -35,3 +36,11 @@ fun aNavigationState(
|
||||||
}
|
}
|
||||||
return NavigationState.Thread(A_THREAD_OWNER, threadId, room)
|
return NavigationState.Thread(A_THREAD_OWNER, threadId, room)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun anAppNavigationState(
|
||||||
|
navigationState: NavigationState = aNavigationState(),
|
||||||
|
isInForeground: Boolean = true,
|
||||||
|
) = AppNavigationState(
|
||||||
|
navigationState = navigationState,
|
||||||
|
isInForeground = isInForeground,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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.AppNavigationStateService
|
||||||
import io.element.android.services.appnavstate.api.NavigationState
|
import io.element.android.services.appnavstate.api.NavigationState
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
class FakeAppNavigationStateService(
|
class FakeAppNavigationStateService(
|
||||||
override val appNavigationState: MutableStateFlow<AppNavigationState> = MutableStateFlow(
|
initialAppNavigationState: AppNavigationState = AppNavigationState(
|
||||||
AppNavigationState(
|
navigationState = NavigationState.Root,
|
||||||
navigationState = NavigationState.Root,
|
isInForeground = true,
|
||||||
isInForeground = true,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
) : AppNavigationStateService {
|
) : AppNavigationStateService {
|
||||||
|
private val _appNavigationState: MutableStateFlow<AppNavigationState> = MutableStateFlow(initialAppNavigationState)
|
||||||
|
override val appNavigationState = _appNavigationState.asStateFlow()
|
||||||
|
|
||||||
|
fun emitNavigationState(state: AppNavigationState) {
|
||||||
|
_appNavigationState.value = state
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNavigateToSession(owner: String, sessionId: SessionId) = Unit
|
override fun onNavigateToSession(owner: String, sessionId: SessionId) = Unit
|
||||||
override fun onLeavingSession(owner: String) = Unit
|
override fun onLeavingSession(owner: String) = Unit
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue