Add read all messages feature (#7049)
* Add read all messages feature: read all messages using Client.markAllRoomsAsRead() method * Move the 'mark all rooms as read' footer as part of the list item * Replace `showMarkAllRoomsAsReadConfirmation` and its associated event with `MarkAllRoomsAsRead(val needsConfirmation: Boolean)` and a `AsyncAction.Confirming` state --------- Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
committed by
GitHub
parent
c4f32db4be
commit
b8752152cf
+2
@@ -15,4 +15,6 @@ sealed interface DeveloperSettingsEvents {
|
||||
data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvents
|
||||
data object ClearCache : DeveloperSettingsEvents
|
||||
data object VacuumStores : DeveloperSettingsEvents
|
||||
data class MarkAllRoomsAsRead(val needsConfirmation: Boolean) : DeveloperSettingsEvents
|
||||
data object DismissMarkAllRoomsAsReadConfirmation : DeveloperSettingsEvents
|
||||
}
|
||||
|
||||
+26
-2
@@ -22,6 +22,7 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsState
|
||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.MarkAllRoomsAsRead
|
||||
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
|
||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -46,6 +47,7 @@ class DeveloperSettingsPresenter(
|
||||
private val vacuumStoresUseCase: VacuumStoresUseCase,
|
||||
private val databaseSizesUseCase: GetDatabaseSizesUseCase,
|
||||
private val fileSizeFormatter: FileSizeFormatter,
|
||||
private val markAllRoomsAsRead: MarkAllRoomsAsRead,
|
||||
) : Presenter<DeveloperSettingsState> {
|
||||
@Composable
|
||||
override fun present(): DeveloperSettingsState {
|
||||
@@ -58,6 +60,9 @@ class DeveloperSettingsPresenter(
|
||||
val clearCacheAction = remember {
|
||||
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||
}
|
||||
val markAllRoomsAsReadAction = remember {
|
||||
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||
}
|
||||
var showColorPicker by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
@@ -88,6 +93,18 @@ class DeveloperSettingsPresenter(
|
||||
DeveloperSettingsEvents.VacuumStores -> coroutineScope.launch {
|
||||
vacuumStoresUseCase()
|
||||
}
|
||||
is DeveloperSettingsEvents.MarkAllRoomsAsRead -> {
|
||||
if (event.needsConfirmation) {
|
||||
markAllRoomsAsReadAction.value = AsyncAction.ConfirmingNoParams
|
||||
} else {
|
||||
coroutineScope.markAllRoomsAsRead(
|
||||
markAllRoomsAsReadAction = markAllRoomsAsReadAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
DeveloperSettingsEvents.DismissMarkAllRoomsAsReadConfirmation -> {
|
||||
markAllRoomsAsReadAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +114,7 @@ class DeveloperSettingsPresenter(
|
||||
cacheSize = cacheSize.value,
|
||||
databaseSizes = databaseSizes.value,
|
||||
clearCacheAction = clearCacheAction.value,
|
||||
markAllRoomsAsReadAction = markAllRoomsAsReadAction.value,
|
||||
isEnterpriseBuild = enterpriseService.isEnterpriseBuild,
|
||||
showColorPicker = showColorPicker,
|
||||
eventSink = ::handleEvent,
|
||||
@@ -131,8 +149,14 @@ class DeveloperSettingsPresenter(
|
||||
}
|
||||
|
||||
private fun CoroutineScope.clearCache(clearCacheAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
suspend { clearCacheUseCase() }.runCatchingUpdatingState(state = clearCacheAction)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.markAllRoomsAsRead(
|
||||
markAllRoomsAsReadAction: MutableState<AsyncAction<Unit>>,
|
||||
) = launch {
|
||||
suspend {
|
||||
clearCacheUseCase()
|
||||
}.runCatchingUpdatingState(clearCacheAction)
|
||||
markAllRoomsAsRead().getOrThrow()
|
||||
}.runCatchingUpdatingState(state = markAllRoomsAsReadAction)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -18,9 +18,10 @@ data class DeveloperSettingsState(
|
||||
val cacheSize: AsyncData<String>,
|
||||
val databaseSizes: AsyncData<ImmutableMap<String, String>>,
|
||||
val clearCacheAction: AsyncAction<Unit>,
|
||||
val markAllRoomsAsReadAction: AsyncAction<Unit>,
|
||||
val isEnterpriseBuild: Boolean,
|
||||
val showColorPicker: Boolean,
|
||||
val eventSink: (DeveloperSettingsEvents) -> Unit
|
||||
) {
|
||||
val showLoader = clearCacheAction is AsyncAction.Loading
|
||||
val showLoader = clearCacheAction is AsyncAction.Loading || markAllRoomsAsReadAction is AsyncAction.Loading
|
||||
}
|
||||
|
||||
+2
@@ -37,6 +37,7 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider<DeveloperSe
|
||||
fun aDeveloperSettingsState(
|
||||
appDeveloperSettingsState: AppDeveloperSettingsState = anAppDeveloperSettingsState(),
|
||||
clearCacheAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
markAllRoomsAsReadAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
isEnterpriseBuild: Boolean = false,
|
||||
showColorPicker: Boolean = false,
|
||||
eventSink: (DeveloperSettingsEvents) -> Unit = {},
|
||||
@@ -45,6 +46,7 @@ fun aDeveloperSettingsState(
|
||||
cacheSize = AsyncData.Success("1.2 MB"),
|
||||
databaseSizes = AsyncData.Success(persistentMapOf("state_store" to "1.2MB")),
|
||||
clearCacheAction = clearCacheAction,
|
||||
markAllRoomsAsReadAction = markAllRoomsAsReadAction,
|
||||
isEnterpriseBuild = isEnterpriseBuild,
|
||||
showColorPicker = showColorPicker,
|
||||
eventSink = eventSink,
|
||||
|
||||
+38
@@ -18,9 +18,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsView
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
@@ -45,6 +47,15 @@ fun DeveloperSettingsView(
|
||||
if (state.showLoader) {
|
||||
ProgressDialog()
|
||||
}
|
||||
if (state.markAllRoomsAsReadAction.isConfirming()) {
|
||||
ConfirmationDialog(
|
||||
title = "Are you sure you want to mark all the rooms as read?",
|
||||
content = "",
|
||||
submitText = stringResource(CommonStrings.action_yes),
|
||||
onSubmitClick = { state.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = false)) },
|
||||
onDismiss = { state.eventSink(DeveloperSettingsEvents.DismissMarkAllRoomsAsReadConfirmation) },
|
||||
)
|
||||
}
|
||||
BackHandler(
|
||||
enabled = !state.showLoader,
|
||||
onBack = onBackClick,
|
||||
@@ -64,6 +75,7 @@ fun DeveloperSettingsView(
|
||||
onOpenShowkase = onOpenShowkase,
|
||||
)
|
||||
NotificationCategory(onPushHistoryClick)
|
||||
MarkAllRoomsAsReadCategory(state)
|
||||
|
||||
if (state.isEnterpriseBuild) {
|
||||
PreferenceCategory(title = "Theme") {
|
||||
@@ -152,6 +164,32 @@ fun DeveloperSettingsView(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MarkAllRoomsAsReadCategory(state: DeveloperSettingsState) {
|
||||
PreferenceCategory(title = "Room list") {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text("Mark all rooms as read")
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = """
|
||||
This will send a private read receipt and a read marker in every room you are part of.
|
||||
It's a long running operation that might get rate limited.
|
||||
It will run in the background but the app must be alive for it to finish.
|
||||
""".trimIndent(),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
enabled = !state.showLoader,
|
||||
onClick = {
|
||||
state.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = true))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationCategory(onPushHistoryClick: () -> Unit) {
|
||||
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_title)) {
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface MarkAllRoomsAsRead {
|
||||
suspend operator fun invoke(): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultMarkAllRoomsAsRead(
|
||||
private val client: MatrixClient,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : MarkAllRoomsAsRead {
|
||||
override suspend fun invoke(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
client.markAllRoomsAsRead()
|
||||
.onSuccess {
|
||||
notificationCleaner.clearAllMessagesEvents(client.sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
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 kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface MarkRoomAsRead {
|
||||
suspend operator fun invoke(roomId: RoomId): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultMarkRoomAsRead(
|
||||
private val client: MatrixClient,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : MarkRoomAsRead {
|
||||
override suspend fun invoke(roomId: RoomId): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
notificationCleaner.clearMessagesForRoom(client.sessionId, roomId)
|
||||
val room = client.getRoom(roomId) ?: return@withContext Result.failure(IllegalStateException("Room not found"))
|
||||
room.use {
|
||||
it.setUnreadFlag(isUnread = false)
|
||||
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
|
||||
ReceiptType.READ
|
||||
} else {
|
||||
ReceiptType.READ_PRIVATE
|
||||
}
|
||||
it.markAsRead(receiptType)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -17,6 +17,7 @@ import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.preferences.impl.developer.appsettings.anAppDeveloperSettingsState
|
||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeMarkAllRoomsAsRead
|
||||
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
|
||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -127,6 +128,28 @@ class DeveloperSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - confirm mark all rooms as read`() = runTest {
|
||||
val markAllRoomsAsRead = FakeMarkAllRoomsAsRead()
|
||||
val presenter = createDeveloperSettingsPresenter(markAllRoomsAsRead = markAllRoomsAsRead)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = true))
|
||||
val stateWithConfirmation = awaitItem()
|
||||
assertThat(stateWithConfirmation.markAllRoomsAsReadAction.isConfirming()).isTrue()
|
||||
stateWithConfirmation.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = false))
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.markAllRoomsAsReadAction.isConfirming()).isFalse()
|
||||
assertThat(state.markAllRoomsAsReadAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.markAllRoomsAsReadAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
assertThat(markAllRoomsAsRead.invokeCallCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - VacuumStores action invokes the VacuumStoresUseCase`() = runTest {
|
||||
var vacuumCalled = false
|
||||
@@ -151,6 +174,7 @@ class DeveloperSettingsPresenterTest {
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
vacuumStoresUseCase: VacuumStoresUseCase = VacuumStoresUseCase {},
|
||||
databaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) },
|
||||
markAllRoomsAsRead: FakeMarkAllRoomsAsRead = FakeMarkAllRoomsAsRead(),
|
||||
): DeveloperSettingsPresenter {
|
||||
return DeveloperSettingsPresenter(
|
||||
appDeveloperSettingsPresenter = { anAppDeveloperSettingsState() },
|
||||
@@ -161,6 +185,7 @@ class DeveloperSettingsPresenterTest {
|
||||
vacuumStoresUseCase = vacuumStoresUseCase,
|
||||
databaseSizesUseCase = databaseSizesUseCase,
|
||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||
markAllRoomsAsRead = markAllRoomsAsRead,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ class DeveloperSettingsViewTest : RobolectricTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h2200dp")
|
||||
@Config(qualifiers = "h2400dp")
|
||||
@Test
|
||||
fun `clicking on clear cache emits the expected event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>()
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeMarkAllRoomsAsRead(
|
||||
private val invokeLambda: suspend () -> Result<Unit> = { Result.success(Unit) },
|
||||
) : MarkAllRoomsAsRead {
|
||||
var invokeCallCount = 0
|
||||
private set
|
||||
|
||||
override suspend fun invoke(): Result<Unit> = simulateLongTask {
|
||||
invokeCallCount++
|
||||
invokeLambda()
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
class FakeMarkRoomAsRead(
|
||||
private val invokeLambda: suspend (RoomId) -> Result<Unit> = { Result.success(Unit) },
|
||||
) : MarkRoomAsRead {
|
||||
val invokedRoomIds = mutableListOf<RoomId>()
|
||||
|
||||
override suspend fun invoke(roomId: RoomId): Result<Unit> {
|
||||
invokedRoomIds.add(roomId)
|
||||
return invokeLambda(roomId)
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class MarkAllRoomsAsReadTest {
|
||||
@Test
|
||||
fun `invoke - delegates to client and clears all notifications`() = runTest {
|
||||
val markAllRoomsAsReadLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val clearAllMessagesEventsLambda = lambdaRecorder<SessionId, Unit> { }
|
||||
val markAllRoomsAsRead = DefaultMarkAllRoomsAsRead(
|
||||
client = FakeMatrixClient(
|
||||
markAllRoomsAsReadResult = markAllRoomsAsReadLambda,
|
||||
),
|
||||
notificationCleaner = FakeNotificationCleaner(
|
||||
clearAllMessagesEventsLambda = clearAllMessagesEventsLambda,
|
||||
),
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
|
||||
val result = markAllRoomsAsRead()
|
||||
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
markAllRoomsAsReadLambda.assertions().isCalledOnce()
|
||||
clearAllMessagesEventsLambda.assertions().isCalledOnce().with(value(A_SESSION_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke - does not clear notifications when client fails`() = runTest {
|
||||
val clearAllMessagesEventsLambda = lambdaRecorder<SessionId, Unit> { }
|
||||
val markAllRoomsAsRead = DefaultMarkAllRoomsAsRead(
|
||||
client = FakeMatrixClient(
|
||||
markAllRoomsAsReadResult = { Result.failure(IllegalStateException("Failed")) },
|
||||
),
|
||||
notificationCleaner = FakeNotificationCleaner(
|
||||
clearAllMessagesEventsLambda = clearAllMessagesEventsLambda,
|
||||
),
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
|
||||
val result = markAllRoomsAsRead()
|
||||
|
||||
assertThat(result.isFailure).isTrue()
|
||||
clearAllMessagesEventsLambda.assertions().isNeverCalled()
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class MarkRoomAsReadTest {
|
||||
@Test
|
||||
fun `invoke - clears notifications, clears unread flag and sends public read receipt`() = runTest {
|
||||
val markAsReadResult = lambdaRecorder<ReceiptType, Result<Unit>> { Result.success(Unit) }
|
||||
val room = FakeBaseRoom(markAsReadResult = markAsReadResult)
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val clearMessagesForRoomLambda = lambdaRecorder { _: SessionId, _: RoomId -> }
|
||||
val notificationCleaner = FakeNotificationCleaner(clearMessagesForRoomLambda = clearMessagesForRoomLambda)
|
||||
val sessionPreferencesStore = InMemorySessionPreferencesStore()
|
||||
val markRoomAsRead = DefaultMarkRoomAsRead(
|
||||
client = matrixClient,
|
||||
notificationCleaner = notificationCleaner,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
|
||||
val result = markRoomAsRead(A_ROOM_ID)
|
||||
runCurrent()
|
||||
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
clearMessagesForRoomLambda.assertions().isCalledOnce()
|
||||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false))
|
||||
markAsReadResult.assertions().isCalledOnce().with(value(ReceiptType.READ))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke - sends private read receipt when public receipts are disabled`() = runTest {
|
||||
val markAsReadResult = lambdaRecorder<ReceiptType, Result<Unit>> { Result.success(Unit) }
|
||||
val room = FakeBaseRoom(markAsReadResult = markAsReadResult)
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
|
||||
val markRoomAsRead = DefaultMarkRoomAsRead(
|
||||
client = matrixClient,
|
||||
notificationCleaner = FakeNotificationCleaner(clearMessagesForRoomLambda = { _, _ -> }),
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
|
||||
val result = markRoomAsRead(A_ROOM_ID)
|
||||
runCurrent()
|
||||
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
markAsReadResult.assertions().isCalledOnce().with(value(ReceiptType.READ_PRIVATE))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user