From 5213849f8bff26074219faf94eff99a23c63e83e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Feb 2024 09:35:52 +0100 Subject: [PATCH] Typing notification: disable rendering when the user preference `isRenderTypingNotificationsEnabled` is false. --- .../typing/TypingNotificationPresenter.kt | 46 ++++++++++++------- .../impl/typing/TypingNotificationState.kt | 1 + .../typing/TypingNotificationStateProvider.kt | 1 + .../impl/typing/TypingNotificationView.kt | 2 +- .../messages/impl/MessagesPresenterTest.kt | 5 +- .../typing/TypingNotificationPresenterTest.kt | 43 +++++++++++++++++ 6 files changed, 80 insertions(+), 18 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt index fdbb698a02..92cae4c4d9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt @@ -18,10 +18,12 @@ package io.element.android.features.messages.impl.typing import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -29,6 +31,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.roomMembers import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn @@ -37,30 +40,41 @@ import javax.inject.Inject class TypingNotificationPresenter @Inject constructor( private val room: MatrixRoom, + private val sessionPreferencesStore: SessionPreferencesStore, ) : Presenter { @Composable override fun present(): TypingNotificationState { - var typingMembers by remember { mutableStateOf(emptyList()) } - LaunchedEffect(Unit) { - combine(room.roomTypingMembersFlow, room.membersStateFlow) { typingMembers, membersState -> - typingMembers - .map { userId -> - membersState.roomMembers() - ?.firstOrNull { roomMember -> roomMember.userId == userId } - ?: createDefaultRoomMemberForTyping(userId) - } + val typingMembersState = remember { mutableStateOf(emptyList()) } + val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true) + LaunchedEffect(renderTypingNotifications) { + if (renderTypingNotifications) { + observeRoomTypingMembers(typingMembersState) + } else { + typingMembersState.value = emptyList() } - .distinctUntilChanged() - .onEach { members -> - typingMembers = members - } - .launchIn(this) } return TypingNotificationState( - typingMembers = typingMembers.toImmutableList(), + renderTypingNotifications = renderTypingNotifications, + typingMembers = typingMembersState.value.toImmutableList(), ) } + + private fun CoroutineScope.observeRoomTypingMembers(typingMembersState: MutableState>) { + combine(room.roomTypingMembersFlow, room.membersStateFlow) { typingMembers, membersState -> + typingMembers + .map { userId -> + membersState.roomMembers() + ?.firstOrNull { roomMember -> roomMember.userId == userId } + ?: createDefaultRoomMemberForTyping(userId) + } + } + .distinctUntilChanged() + .onEach { members -> + typingMembersState.value = members + } + .launchIn(this) + } } /** diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt index d4398a6351..380586f9c7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt @@ -20,5 +20,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList data class TypingNotificationState( + val renderTypingNotifications: Boolean, val typingMembers: ImmutableList, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt index 1de315c8c5..92d7e357d2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt @@ -74,6 +74,7 @@ class TypingNotificationStateProvider : PreviewParameterProvider = emptyList(), ) = TypingNotificationState( + renderTypingNotifications = true, typingMembers = typingMembers.toImmutableList(), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt index 97ed4f85dd..633758c541 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt @@ -42,7 +42,7 @@ fun TypingNotificationView( state: TypingNotificationState, modifier: Modifier = Modifier, ) { - if (state.typingMembers.isEmpty()) return + if (state.typingMembers.isEmpty() || !state.renderTypingNotifications) return val typingNotificationText = computeTypingNotificationText(state.typingMembers) Text( modifier = modifier 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 9e729e32e1..44dc96d804 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 @@ -731,7 +731,10 @@ class MessagesPresenterTest { } } val actionListPresenter = ActionListPresenter(appPreferencesStore = appPreferencesStore) - val typingNotificationPresenter = TypingNotificationPresenter(matrixRoom) + val typingNotificationPresenter = TypingNotificationPresenter( + room = matrixRoom, + sessionPreferencesStore = sessionPreferencesStore, + ) val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter() val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 4bedd6f6a3..a879c5a4bc 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -20,6 +20,8 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState @@ -49,10 +51,47 @@ class TypingNotificationPresenterTest { presenter.present() }.test { val initialState = awaitItem() + assertThat(initialState.renderTypingNotifications).isTrue() assertThat(initialState.typingMembers).isEmpty() } } + @Test + fun `present - typing notification disabled`() = runTest { + val aDefaultRoomMember = createDefaultRoomMember(A_USER_ID_2) + val room = FakeMatrixRoom() + val sessionPreferencesStore = InMemorySessionPreferencesStore( + isRenderTypingNotificationsEnabled = false + ) + val presenter = createPresenter( + matrixRoom = room, + sessionPreferencesStore = sessionPreferencesStore, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.renderTypingNotifications).isFalse() + assertThat(initialState.typingMembers).isEmpty() + room.givenRoomTypingMembers(listOf(A_USER_ID_2)) + expectNoEvents() + // Preferences changes + sessionPreferencesStore.setRenderTypingNotifications(true) + skipItems(1) + val oneMemberTypingState = awaitItem() + assertThat(oneMemberTypingState.renderTypingNotifications).isTrue() + assertThat(oneMemberTypingState.typingMembers.size).isEqualTo(1) + assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember) + // Preferences changes again + sessionPreferencesStore.setRenderTypingNotifications(false) + skipItems(1) + val finalState = awaitItem() + assertThat(finalState.renderTypingNotifications).isFalse() + assertThat(finalState.typingMembers).isEmpty() + } + } + @Test fun `present - state is updated when a member is typing, member is not known`() = runTest { val aDefaultRoomMember = createDefaultRoomMember(A_USER_ID_2) @@ -136,9 +175,13 @@ class TypingNotificationPresenterTest { matrixRoom: MatrixRoom = FakeMatrixRoom().apply { givenRoomInfo(aRoomInfo(id = roomId.value, name = "")) }, + sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore( + isRenderTypingNotificationsEnabled = true + ), ): TypingNotificationPresenter { return TypingNotificationPresenter( room = matrixRoom, + sessionPreferencesStore = sessionPreferencesStore, ) }