diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt index 782f70645b..88dc455e20 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt @@ -17,8 +17,8 @@ package im.vector.app.features.notifications import im.vector.app.features.invite.AutoAcceptInvites -import im.vector.app.features.notifications.Processed.KEEP -import im.vector.app.features.notifications.Processed.REMOVE +import im.vector.app.features.notifications.ProcessedType.KEEP +import im.vector.app.features.notifications.ProcessedType.REMOVE import javax.inject.Inject class NotifiableEventProcessor @Inject constructor( @@ -26,8 +26,8 @@ class NotifiableEventProcessor @Inject constructor( private val autoAcceptInvites: AutoAcceptInvites ) { - fun process(eventList: List, currentRoomId: String?): List> { - return eventList.map { + fun process(eventList: List, currentRoomId: String?, renderedEventsList: List>): List { + val processedEventList = eventList.map { when (it) { is InviteNotifiableEvent -> if (autoAcceptInvites.hideInvites) REMOVE else KEEP is NotifiableMessageEvent -> if (shouldIgnoreMessageEventInRoom(currentRoomId, it.roomId) || outdatedDetector.isMessageOutdated(it)) { @@ -36,16 +36,15 @@ class NotifiableEventProcessor @Inject constructor( is SimpleNotifiableEvent -> KEEP } to it } + + val removedEventsDiff = renderedEventsList.filter { renderedEvent -> + eventList.none { it.eventId == renderedEvent.second.eventId } + }.map { REMOVE to it.second } + + return removedEventsDiff + processedEventList } private fun shouldIgnoreMessageEventInRoom(currentRoomId: String?, roomId: String?): Boolean { return currentRoomId != null && roomId == currentRoomId } } - -enum class Processed { - KEEP, - REMOVE -} - -fun List>.onlyKeptEvents() = filter { it.first == KEEP }.map { it.second } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index c73b4b2a9c..b7bb20237c 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -56,7 +56,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } private val eventList = loadEventInfo() - private var renderedEventsList = emptyList>() + private var renderedEventsList = emptyList>() private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size) private var currentRoomId: String? = null @@ -234,14 +234,12 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } val eventsToRender = synchronized(eventList) { - notifiableEventProcessor.process(eventList, currentRoomId).also { + notifiableEventProcessor.process(eventList, currentRoomId, renderedEventsList).also { eventList.clear() eventList.addAll(it.onlyKeptEvents()) } } - - if (renderedEventsList == eventsToRender) { Timber.d("Skipping notification update due to event list not changing") } else { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt index 88f21a02a6..fe1671b58b 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt @@ -40,11 +40,11 @@ class NotificationFactory @Inject constructor( private fun NotifiableMessageEvent.canNotBeDisplayed() = isRedacted - fun List>.toNotifications(myUserId: String): List { + fun List>.toNotifications(myUserId: String): List { return map { (processed, event) -> when (processed) { - Processed.REMOVE -> OneShotNotification.Removed(key = event.roomId) - Processed.KEEP -> OneShotNotification.Append( + ProcessedType.REMOVE -> OneShotNotification.Removed(key = event.roomId) + ProcessedType.KEEP -> OneShotNotification.Append( notificationUtils.buildRoomInvitationNotification(event, myUserId), OneShotNotification.Append.Meta( key = event.roomId, @@ -58,11 +58,11 @@ class NotificationFactory @Inject constructor( } @JvmName("toNotificationsSimpleNotifiableEvent") - fun List>.toNotifications(myUserId: String): List { + fun List>.toNotifications(myUserId: String): List { return map { (processed, event) -> when (processed) { - Processed.REMOVE -> OneShotNotification.Removed(key = event.eventId) - Processed.KEEP -> OneShotNotification.Append( + ProcessedType.REMOVE -> OneShotNotification.Removed(key = event.eventId) + ProcessedType.KEEP -> OneShotNotification.Append( notificationUtils.buildSimpleEventNotification(event, myUserId), OneShotNotification.Append.Meta( key = event.eventId, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt index 31a99810e0..7cf0a8872a 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt @@ -34,7 +34,7 @@ class NotificationRenderer @Inject constructor(private val notificationDisplayer myUserDisplayName: String, myUserAvatarUrl: String?, useCompleteNotificationFormat: Boolean, - eventsToProcess: List>) { + eventsToProcess: List>) { val (roomEvents, simpleEvents, invitationEvents) = eventsToProcess.groupByType() with(notificationFactory) { val roomNotifications = roomEvents.toNotifications(myUserDisplayName, myUserAvatarUrl) @@ -108,10 +108,10 @@ class NotificationRenderer @Inject constructor(private val notificationDisplayer } } -private fun List>.groupByType(): GroupedNotificationEvents { +private fun List>.groupByType(): GroupedNotificationEvents { val roomIdToEventMap: MutableMap> = LinkedHashMap() - val simpleEvents: MutableList> = ArrayList() - val invitationEvents: MutableList> = ArrayList() + val simpleEvents: MutableList> = ArrayList() + val invitationEvents: MutableList> = ArrayList() forEach { when (val event = it.second) { is InviteNotifiableEvent -> invitationEvents.add(it.asPair()) @@ -126,10 +126,10 @@ private fun List>.groupByType(): GroupedNotific } @Suppress("UNCHECKED_CAST") -private fun Pair.asPair(): Pair = this as Pair +private fun Pair.asPair(): Pair = this as Pair data class GroupedNotificationEvents( val roomEvents: Map>, - val simpleEvents: List>, - val invitationEvents: List> + val simpleEvents: List>, + val invitationEvents: List> ) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeNotifiableEventProcessor.kt b/vector/src/main/java/im/vector/app/features/notifications/ProcessedEvent.kt similarity index 69% rename from vector/src/test/java/im/vector/app/test/fakes/FakeNotifiableEventProcessor.kt rename to vector/src/main/java/im/vector/app/features/notifications/ProcessedEvent.kt index 6143c7a907..0901757d02 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeNotifiableEventProcessor.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/ProcessedEvent.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package im.vector.app.test.fakes +package im.vector.app.features.notifications -import im.vector.app.features.notifications.NotifiableEventProcessor -import io.mockk.mockk - -class FakeNotifiableEventProcessor { - - val instance = mockk() +typealias ProcessedEvent = Pair +enum class ProcessedType { + KEEP, + REMOVE } + +fun List.onlyKeptEvents() = filter { it.first == ProcessedType.KEEP }.map { it.second } diff --git a/vector/src/test/java/im/vector/app/features/notifications/NotifiableEventProcessorTest.kt b/vector/src/test/java/im/vector/app/features/notifications/NotifiableEventProcessorTest.kt index 154763afae..1c0f5f9390 100644 --- a/vector/src/test/java/im/vector/app/features/notifications/NotifiableEventProcessorTest.kt +++ b/vector/src/test/java/im/vector/app/features/notifications/NotifiableEventProcessorTest.kt @@ -31,121 +31,104 @@ class NotifiableEventProcessorTest { private val eventProcessor = NotifiableEventProcessor(outdatedDetector.instance, autoAcceptInvites) @Test - fun `given simple events when processing then return without mutating`() { - val (events, originalEvents) = createEventsList( + fun `given simple events when processing then keep simple events`() { + val events = listOf( aSimpleNotifiableEvent(eventId = "event-1"), aSimpleNotifiableEvent(eventId = "event-2") ) - val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = renderedEventsList) + val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = emptyList()) - result shouldBeEqualTo aProcessedNotificationEvents( - simpleEvents = mapOf( - "event-1" to events[0] as SimpleNotifiableEvent, - "event-2" to events[1] as SimpleNotifiableEvent - ) + result shouldBeEqualTo listOf( + ProcessedType.KEEP to events[0], + ProcessedType.KEEP to events[1] ) - events shouldBeEqualTo originalEvents } @Test fun `given invites are auto accepted when processing then remove invitations`() { autoAcceptInvites._isEnabled = true - val events = mutableListOf( + val events = listOf( anInviteNotifiableEvent(roomId = "room-1"), anInviteNotifiableEvent(roomId = "room-2") ) - val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = renderedEventsList) + val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = emptyList()) - result shouldBeEqualTo aProcessedNotificationEvents( - invitationEvents = mapOf( - "room-1" to null, - "room-2" to null - ) + result shouldBeEqualTo listOf( + ProcessedType.REMOVE to events[0], + ProcessedType.REMOVE to events[1] ) - events shouldBeEqualTo emptyList() } @Test - fun `given invites are not auto accepted when processing then return without mutating`() { + fun `given invites are not auto accepted when processing then keep invitation events`() { autoAcceptInvites._isEnabled = false - val (events, originalEvents) = createEventsList( + val events = listOf( anInviteNotifiableEvent(roomId = "room-1"), anInviteNotifiableEvent(roomId = "room-2") ) - val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = renderedEventsList) + val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = emptyList()) - result shouldBeEqualTo aProcessedNotificationEvents( - invitationEvents = mapOf( - "room-1" to originalEvents[0] as InviteNotifiableEvent, - "room-2" to originalEvents[1] as InviteNotifiableEvent - ) + result shouldBeEqualTo listOf( + ProcessedType.KEEP to events[0], + ProcessedType.KEEP to events[1] ) - events shouldBeEqualTo originalEvents } @Test - fun `given out of date message event when processing then removes message`() { - val (events) = createEventsList(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1")) + fun `given out of date message event when processing then removes message event`() { + val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1")) outdatedDetector.givenEventIsOutOfDate(events[0]) - val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = renderedEventsList) + val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = emptyList()) - result shouldBeEqualTo aProcessedNotificationEvents( - roomEvents = mapOf( - "room-1" to emptyList() - ) + result shouldBeEqualTo listOf( + ProcessedType.REMOVE to events[0], ) - events shouldBeEqualTo emptyList() } @Test - fun `given in date message event when processing then without mutating`() { - val (events, originalEvents) = createEventsList(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1")) + fun `given in date message event when processing then keep message event`() { + val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1")) outdatedDetector.givenEventIsInDate(events[0]) - val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = renderedEventsList) + val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = emptyList()) - result shouldBeEqualTo aProcessedNotificationEvents( - roomEvents = mapOf( - "room-1" to listOf(events[0] as NotifiableMessageEvent) - ) + result shouldBeEqualTo listOf( + ProcessedType.KEEP to events[0], ) - events shouldBeEqualTo originalEvents } @Test fun `given viewing the same room as message event when processing then removes message`() { - val (events) = createEventsList(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1")) + val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1")) - val result = eventProcessor.process(events, currentRoomId = "room-1", renderedEventsList = renderedEventsList) + val result = eventProcessor.process(events, currentRoomId = "room-1", renderedEventsList = emptyList()) - result shouldBeEqualTo aProcessedNotificationEvents( - roomEvents = mapOf( - "room-1" to emptyList() - ) + result shouldBeEqualTo listOf( + ProcessedType.REMOVE to events[0], + ) + } + + @Test + fun `given events are different to rendered events when processing then removes difference`() { + val events = listOf(aSimpleNotifiableEvent(eventId = "event-1")) + val renderedEvents = listOf( + ProcessedType.KEEP to events[0], + ProcessedType.KEEP to anInviteNotifiableEvent(roomId = "event-2") + ) + + val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEventsList = renderedEvents) + + result shouldBeEqualTo listOf( + ProcessedType.REMOVE to renderedEvents[1].second, + ProcessedType.KEEP to renderedEvents[0].second ) - events shouldBeEqualTo emptyList() } } -fun createEventsList(vararg event: NotifiableEvent): Pair, List> { - val mutableEvents = mutableListOf(*event) - val immutableEvents = mutableEvents.toList() - return mutableEvents to immutableEvents -} - -fun aProcessedNotificationEvents(simpleEvents: Map = emptyMap(), - invitationEvents: Map = emptyMap(), - roomEvents: Map> = emptyMap() -) = GroupedNotificationEvents( - roomEvents = roomEvents, - simpleEvents = simpleEvents, - invitationEvents = invitationEvents, -) - fun aSimpleNotifiableEvent(eventId: String) = SimpleNotifiableEvent( matrixID = null, eventId = eventId, diff --git a/vector/src/test/java/im/vector/app/features/notifications/NotificationFactoryTest.kt b/vector/src/test/java/im/vector/app/features/notifications/NotificationFactoryTest.kt index fc20f09811..98684f278d 100644 --- a/vector/src/test/java/im/vector/app/features/notifications/NotificationFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/notifications/NotificationFactoryTest.kt @@ -46,7 +46,7 @@ class NotificationFactoryTest { @Test fun `given a room invitation when mapping to notification then is Append`() = testWith(notificationFactory) { val expectedNotification = notificationUtils.givenBuildRoomInvitationNotificationFor(AN_INVITATION_EVENT, MY_USER_ID) - val roomInvitation = mapOf(A_ROOM_ID to AN_INVITATION_EVENT) + val roomInvitation = listOf(ProcessedType.KEEP to AN_INVITATION_EVENT) val result = roomInvitation.toNotifications(MY_USER_ID) @@ -63,7 +63,7 @@ class NotificationFactoryTest { @Test fun `given a missing event in room invitation when mapping to notification then is Removed`() = testWith(notificationFactory) { - val missingEventRoomInvitation: Map = mapOf(A_ROOM_ID to null) + val missingEventRoomInvitation = listOf(ProcessedType.REMOVE to AN_INVITATION_EVENT) val result = missingEventRoomInvitation.toNotifications(MY_USER_ID) @@ -75,7 +75,7 @@ class NotificationFactoryTest { @Test fun `given a simple event when mapping to notification then is Append`() = testWith(notificationFactory) { val expectedNotification = notificationUtils.givenBuildSimpleInvitationNotificationFor(A_SIMPLE_EVENT, MY_USER_ID) - val roomInvitation = mapOf(AN_EVENT_ID to A_SIMPLE_EVENT) + val roomInvitation = listOf(ProcessedType.KEEP to A_SIMPLE_EVENT) val result = roomInvitation.toNotifications(MY_USER_ID) @@ -92,7 +92,7 @@ class NotificationFactoryTest { @Test fun `given a missing simple event when mapping to notification then is Removed`() = testWith(notificationFactory) { - val missingEventRoomInvitation: Map = mapOf(AN_EVENT_ID to null) + val missingEventRoomInvitation = listOf(ProcessedType.REMOVE to A_SIMPLE_EVENT) val result = missingEventRoomInvitation.toNotifications(MY_USER_ID) diff --git a/vector/src/test/java/im/vector/app/features/notifications/NotificationRendererTest.kt b/vector/src/test/java/im/vector/app/features/notifications/NotificationRendererTest.kt index bd0d1e8d3f..4f65c3861a 100644 --- a/vector/src/test/java/im/vector/app/features/notifications/NotificationRendererTest.kt +++ b/vector/src/test/java/im/vector/app/features/notifications/NotificationRendererTest.kt @@ -29,8 +29,8 @@ private const val AN_EVENT_ID = "event-id" private const val A_ROOM_ID = "room-id" private const val USE_COMPLETE_NOTIFICATION_FORMAT = true -private val AN_EVENT_LIST = mapOf() -private val A_PROCESSED_EVENTS = GroupedNotificationEvents(emptyMap(), emptyMap(), emptyMap()) +private val AN_EVENT_LIST = listOf>() +private val A_PROCESSED_EVENTS = GroupedNotificationEvents(emptyMap(), emptyList(), emptyList()) private val A_SUMMARY_NOTIFICATION = SummaryNotification.Update(mockk()) private val A_REMOVE_SUMMARY_NOTIFICATION = SummaryNotification.Removed private val A_NOTIFICATION = mockk() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeNotificationFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeNotificationFactory.kt index cc6f84f813..a6e7d1a078 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeNotificationFactory.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeNotificationFactory.kt @@ -16,9 +16,9 @@ package im.vector.app.test.fakes +import im.vector.app.features.notifications.GroupedNotificationEvents import im.vector.app.features.notifications.NotificationFactory import im.vector.app.features.notifications.OneShotNotification -import im.vector.app.features.notifications.GroupedNotificationEvents import im.vector.app.features.notifications.RoomNotification import im.vector.app.features.notifications.SummaryNotification import io.mockk.every @@ -50,7 +50,6 @@ class FakeNotificationFactory { useCompleteNotificationFormat ) } returns summaryNotification - } } }