Typing notification: disable rendering when the user preference isRenderTypingNotificationsEnabled is false.
This commit is contained in:
parent
6f70e587cf
commit
996c5fad95
6 changed files with 80 additions and 18 deletions
|
|
@ -18,10 +18,12 @@ package io.element.android.features.messages.impl.typing
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.architecture.Presenter
|
||||||
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.room.MatrixRoom
|
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.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -37,30 +40,41 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class TypingNotificationPresenter @Inject constructor(
|
class TypingNotificationPresenter @Inject constructor(
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
|
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||||
) : Presenter<TypingNotificationState> {
|
) : Presenter<TypingNotificationState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): TypingNotificationState {
|
override fun present(): TypingNotificationState {
|
||||||
var typingMembers by remember { mutableStateOf(emptyList<RoomMember>()) }
|
val typingMembersState = remember { mutableStateOf(emptyList<RoomMember>()) }
|
||||||
LaunchedEffect(Unit) {
|
val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true)
|
||||||
combine(room.roomTypingMembersFlow, room.membersStateFlow) { typingMembers, membersState ->
|
LaunchedEffect(renderTypingNotifications) {
|
||||||
typingMembers
|
if (renderTypingNotifications) {
|
||||||
.map { userId ->
|
observeRoomTypingMembers(typingMembersState)
|
||||||
membersState.roomMembers()
|
} else {
|
||||||
?.firstOrNull { roomMember -> roomMember.userId == userId }
|
typingMembersState.value = emptyList()
|
||||||
?: createDefaultRoomMemberForTyping(userId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
|
||||||
.onEach { members ->
|
|
||||||
typingMembers = members
|
|
||||||
}
|
|
||||||
.launchIn(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypingNotificationState(
|
return TypingNotificationState(
|
||||||
typingMembers = typingMembers.toImmutableList(),
|
renderTypingNotifications = renderTypingNotifications,
|
||||||
|
typingMembers = typingMembersState.value.toImmutableList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.observeRoomTypingMembers(typingMembersState: MutableState<List<RoomMember>>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class TypingNotificationState(
|
data class TypingNotificationState(
|
||||||
|
val renderTypingNotifications: Boolean,
|
||||||
val typingMembers: ImmutableList<RoomMember>,
|
val typingMembers: ImmutableList<RoomMember>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ class TypingNotificationStateProvider : PreviewParameterProvider<TypingNotificat
|
||||||
internal fun aTypingNotificationState(
|
internal fun aTypingNotificationState(
|
||||||
typingMembers: List<RoomMember> = emptyList(),
|
typingMembers: List<RoomMember> = emptyList(),
|
||||||
) = TypingNotificationState(
|
) = TypingNotificationState(
|
||||||
|
renderTypingNotifications = true,
|
||||||
typingMembers = typingMembers.toImmutableList(),
|
typingMembers = typingMembers.toImmutableList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fun TypingNotificationView(
|
||||||
state: TypingNotificationState,
|
state: TypingNotificationState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (state.typingMembers.isEmpty()) return
|
if (state.typingMembers.isEmpty() || !state.renderTypingNotifications) return
|
||||||
val typingNotificationText = computeTypingNotificationText(state.typingMembers)
|
val typingNotificationText = computeTypingNotificationText(state.typingMembers)
|
||||||
Text(
|
Text(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
|
||||||
|
|
@ -731,7 +731,10 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val actionListPresenter = ActionListPresenter(appPreferencesStore = appPreferencesStore)
|
val actionListPresenter = ActionListPresenter(appPreferencesStore = appPreferencesStore)
|
||||||
val typingNotificationPresenter = TypingNotificationPresenter(matrixRoom)
|
val typingNotificationPresenter = TypingNotificationPresenter(
|
||||||
|
room = matrixRoom,
|
||||||
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
|
)
|
||||||
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
||||||
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||||
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
|
|
@ -49,10 +51,47 @@ class TypingNotificationPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.renderTypingNotifications).isTrue()
|
||||||
assertThat(initialState.typingMembers).isEmpty()
|
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
|
@Test
|
||||||
fun `present - state is updated when a member is typing, member is not known`() = runTest {
|
fun `present - state is updated when a member is typing, member is not known`() = runTest {
|
||||||
val aDefaultRoomMember = createDefaultRoomMember(A_USER_ID_2)
|
val aDefaultRoomMember = createDefaultRoomMember(A_USER_ID_2)
|
||||||
|
|
@ -136,9 +175,13 @@ class TypingNotificationPresenterTest {
|
||||||
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
||||||
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
|
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
|
||||||
},
|
},
|
||||||
|
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(
|
||||||
|
isRenderTypingNotificationsEnabled = true
|
||||||
|
),
|
||||||
): TypingNotificationPresenter {
|
): TypingNotificationPresenter {
|
||||||
return TypingNotificationPresenter(
|
return TypingNotificationPresenter(
|
||||||
room = matrixRoom,
|
room = matrixRoom,
|
||||||
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue