diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index dac4e1c507..60403b25b0 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { implementation(projects.libraries.permissions.api) implementation(projects.libraries.permissions.noop) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.push.api) implementation(projects.features.invite.api) implementation(projects.features.networkmonitor.api) implementation(projects.features.leaveroom.api) @@ -79,6 +80,7 @@ dependencies { testImplementation(projects.libraries.permissions.noop) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) testImplementation(projects.features.networkmonitor.test) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 493047bc8b..d5f9404eff 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList @@ -97,6 +98,7 @@ class RoomListPresenter @Inject constructor( private val analyticsService: AnalyticsService, private val acceptDeclineInvitePresenter: Presenter, private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter, + private val notificationCleaner: NotificationCleaner, ) : Presenter { private val encryptionService: EncryptionService = client.encryptionService() private val syncService: SyncService = client.syncService() @@ -268,6 +270,7 @@ class RoomListPresenter @Inject constructor( } private fun CoroutineScope.markAsRead(roomId: RoomId) = launch { + notificationCleaner.clearMessagesForRoom(client.sessionId, roomId) client.getRoom(roomId)?.use { room -> room.setUnreadFlag(isUnread = false) val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) { diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt index e1e0970756..9528def3e1 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt @@ -51,6 +51,7 @@ import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPe import io.element.android.libraries.indicator.impl.DefaultIndicatorService import io.element.android.libraries.matrix.api.MatrixClient 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.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.room.CurrentUserMembership @@ -62,6 +63,9 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EXCEPTION 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_ROOM_ID_3 +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -75,6 +79,8 @@ import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.libraries.push.api.notifications.NotificationCleaner +import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.EventsRecorder @@ -503,35 +509,54 @@ class RoomListPresenterTest { @Test fun `present - check that the room is marked as read with correct RR and as unread`() = runTest { val room = FakeMatrixRoom() + val room2 = FakeMatrixRoom(roomId = A_ROOM_ID_2) + val room3 = FakeMatrixRoom(roomId = A_ROOM_ID_3) + val allRooms = setOf(room, room2, room3) val sessionPreferencesStore = InMemorySessionPreferencesStore() val matrixClient = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) + givenGetRoomResult(A_ROOM_ID_2, room2) + givenGetRoomResult(A_ROOM_ID_3, room3) } val analyticsService = FakeAnalyticsService() val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val clearMessagesForRoomLambda = lambdaRecorder { _, _ -> } + val notificationCleaner = FakeNotificationCleaner( + clearMessagesForRoomLambda = clearMessagesForRoomLambda, + ) val presenter = createRoomListPresenter( client = matrixClient, coroutineScope = scope, sessionPreferencesStore = sessionPreferencesStore, analyticsService = analyticsService, + notificationCleaner = notificationCleaner, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() - assertThat(room.markAsReadCalls).isEmpty() - assertThat(room.setUnreadFlagCalls).isEmpty() + allRooms.forEach { + assertThat(it.markAsReadCalls).isEmpty() + assertThat(it.setUnreadFlagCalls).isEmpty() + } initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID)) assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ)) assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false)) - initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID)) - assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ)) - assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true)) + clearMessagesForRoomLambda.assertions().isCalledOnce() + .with(value(A_SESSION_ID), value(A_ROOM_ID)) + initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID_2)) + assertThat(room2.markAsReadCalls).isEqualTo(emptyList()) + assertThat(room2.setUnreadFlagCalls).isEqualTo(listOf(true)) // Test again with private read receipts sessionPreferencesStore.setSendPublicReadReceipts(false) - initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID)) - assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ, ReceiptType.READ_PRIVATE)) - assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true, false)) + initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID_3)) + assertThat(room3.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ_PRIVATE)) + assertThat(room3.setUnreadFlagCalls).isEqualTo(listOf(false)) + clearMessagesForRoomLambda.assertions().isCalledExactly(2) + .withSequence( + listOf(value(A_SESSION_ID), value(A_ROOM_ID)), + listOf(value(A_SESSION_ID), value(A_ROOM_ID_3)), + ) assertThat(analyticsService.capturedEvents).containsExactly( Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle), Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle), @@ -633,6 +658,7 @@ class RoomListPresenterTest { filtersPresenter: Presenter = Presenter { aRoomListFiltersState() }, searchPresenter: Presenter = Presenter { aRoomListSearchState() }, acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, + notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), ) = RoomListPresenter( client = client, networkMonitor = networkMonitor, @@ -660,5 +686,6 @@ class RoomListPresenterTest { analyticsService = analyticsService, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, fullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter(), + notificationCleaner = notificationCleaner, ) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index d10d1ad0d2..a84fe1edf6 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -48,6 +48,7 @@ val A_SPACE_ID = SpaceId("!aSpaceId:domain") val A_SPACE_ID_2 = SpaceId("!aSpaceId2:domain") val A_ROOM_ID = RoomId("!aRoomId:domain") val A_ROOM_ID_2 = RoomId("!aRoomId2:domain") +val A_ROOM_ID_3 = RoomId("!aRoomId3:domain") val A_THREAD_ID = ThreadId("\$aThreadId") val A_THREAD_ID_2 = ThreadId("\$aThreadId2") val AN_EVENT_ID = EventId("\$anEventId") diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index c0b8fd929d..b91daeecfd 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -158,6 +158,7 @@ class RoomListScreen( ) } }, + notificationCleaner = FakeNotificationCleaner(), ) @Composable