From 51eb2a014cfcd220d7d8d4a00d13890dadf7657c Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Oct 2024 11:42:46 +0200 Subject: [PATCH] knock : improve a bit code and add tests. --- .../features/joinroom/impl/JoinRoomEvents.kt | 3 +- .../joinroom/impl/JoinRoomPresenter.kt | 25 +++++---- .../features/joinroom/impl/JoinRoomState.kt | 1 + .../joinroom/impl/JoinRoomStateProvider.kt | 2 + .../features/joinroom/impl/JoinRoomView.kt | 26 ++++----- .../joinroom/impl/di/CancelKnockRoom.kt | 28 ++++++++++ .../joinroom/impl/di/JoinRoomModule.kt | 2 + .../features/joinroom/impl/di/KnockRoom.kt | 16 ++++-- .../joinroom/impl/FakeCancelKnockRoom.kt | 20 +++++++ .../features/joinroom/impl/FakeKnockRoom.kt | 8 +-- .../joinroom/impl/JoinRoomPresenterTest.kt | 56 +++++++++++++++++-- .../joinroom/impl/JoinRoomViewTest.kt | 30 ++++++++++ .../libraries/matrix/api/MatrixClient.kt | 4 +- .../libraries/matrix/impl/RustMatrixClient.kt | 10 ++-- .../matrix/impl/room/RustRoomFactory.kt | 1 - .../libraries/matrix/test/FakeMatrixClient.kt | 8 ++- .../matrix/ui/components/InviteSenderView.kt | 3 +- 17 files changed, 192 insertions(+), 51 deletions(-) create mode 100644 features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt create mode 100644 features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt index 48d30eb763..eddfa79717 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt @@ -10,8 +10,9 @@ package io.element.android.features.joinroom.impl sealed interface JoinRoomEvents { data object RetryFetchingContent : JoinRoomEvents data object JoinRoom : JoinRoomEvents - data class KnockRoom(val message: String) : JoinRoomEvents + data object KnockRoom : JoinRoomEvents data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvents + data class UpdateKnockMessage(val message: String) : JoinRoomEvents data object ClearActionStates : JoinRoomEvents data object AcceptInvite : JoinRoomEvents data object DeclineInvite : JoinRoomEvents diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 19d33ca8a3..0b3e828275 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -24,6 +25,7 @@ import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData +import io.element.android.features.joinroom.impl.di.CancelKnockRoom import io.element.android.features.joinroom.impl.di.KnockRoom import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction @@ -46,6 +48,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.util.Optional +private const val MAX_KNOCK_MESSAGE_LENGTH = 500 + class JoinRoomPresenter @AssistedInject constructor( @Assisted private val roomId: RoomId, @Assisted private val roomIdOrAlias: RoomIdOrAlias, @@ -55,6 +59,7 @@ class JoinRoomPresenter @AssistedInject constructor( private val matrixClient: MatrixClient, private val joinRoom: JoinRoom, private val knockRoom: KnockRoom, + private val cancelKnockRoom: CancelKnockRoom, private val acceptDeclineInvitePresenter: Presenter, private val buildMeta: BuildMeta, ) : Presenter { @@ -76,6 +81,7 @@ class JoinRoomPresenter @AssistedInject constructor( val joinAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val knockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val cancelKnockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + var knockMessage by rememberSaveable { mutableStateOf("") } val contentState by produceState( initialValue = ContentState.Loading(roomIdOrAlias), key1 = roomInfo, @@ -111,7 +117,7 @@ class JoinRoomPresenter @AssistedInject constructor( fun handleEvents(event: JoinRoomEvents) { when (event) { JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction) - is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, event.message) + is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage) JoinRoomEvents.AcceptInvite -> { val inviteData = contentState.toInviteData() ?: return acceptDeclineInviteState.eventSink( @@ -133,6 +139,9 @@ class JoinRoomPresenter @AssistedInject constructor( joinAction.value = AsyncAction.Uninitialized cancelKnockAction.value = AsyncAction.Uninitialized } + is JoinRoomEvents.UpdateKnockMessage -> { + knockMessage = event.message.take(MAX_KNOCK_MESSAGE_LENGTH) + } } } @@ -143,6 +152,7 @@ class JoinRoomPresenter @AssistedInject constructor( knockAction = knockAction.value, cancelKnockAction = cancelKnockAction.value, applicationName = buildMeta.applicationName, + knockMessage = knockMessage, eventSink = ::handleEvents ) } @@ -159,7 +169,7 @@ class JoinRoomPresenter @AssistedInject constructor( private fun CoroutineScope.knockRoom(knockAction: MutableState>, message: String) = launch { knockAction.runUpdatingState { - knockRoom(roomId) + knockRoom(roomIdOrAlias, message, serverNames) } } @@ -167,15 +177,8 @@ class JoinRoomPresenter @AssistedInject constructor( if (requiresConfirmation) { cancelKnockAction.value = AsyncAction.ConfirmingNoParams } else { - val room = matrixClient.getRoom(roomId) - if (room == null) { - cancelKnockAction.value = AsyncAction.Failure(RuntimeException()) - } else { - room.use { - cancelKnockAction.runUpdatingState { - room.leave() - } - } + cancelKnockAction.runUpdatingState { + cancelKnockRoom(roomId) } } } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index cdf93484a9..6049e6cd5a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -26,6 +26,7 @@ data class JoinRoomState( val knockAction: AsyncAction, val cancelKnockAction: AsyncAction, val applicationName: String, + val knockMessage: String, val eventSink: (JoinRoomEvents) -> Unit ) { val joinAuthorisationStatus = when (contentState) { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt index ab59b7ae2f..33dcf786e5 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt @@ -131,6 +131,7 @@ fun aJoinRoomState( joinAction: AsyncAction = AsyncAction.Uninitialized, knockAction: AsyncAction = AsyncAction.Uninitialized, cancelKnockAction: AsyncAction = AsyncAction.Uninitialized, + knockMessage: String = "", eventSink: (JoinRoomEvents) -> Unit = {} ) = JoinRoomState( contentState = contentState, @@ -139,6 +140,7 @@ fun aJoinRoomState( knockAction = knockAction, cancelKnockAction = cancelKnockAction, applicationName = "AppName", + knockMessage = knockMessage, eventSink = eventSink ) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index 6937219aa3..181e923c2c 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.rememberScrollState @@ -27,10 +26,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -85,7 +80,6 @@ fun JoinRoomView( Box( modifier = modifier.fillMaxSize(), ) { - var knockMessage by rememberSaveable { mutableStateOf("") } LightGradientBackground() HeaderFooterPage( containerColor = Color.Transparent, @@ -97,8 +91,8 @@ fun JoinRoomView( JoinRoomContent( contentState = state.contentState, applicationName = state.applicationName, - knockMessage = knockMessage, - onKnockMessageUpdate = { knockMessage = it }, + knockMessage = state.knockMessage, + onKnockMessageUpdate = { state.eventSink(JoinRoomEvents.UpdateKnockMessage(it)) }, ) }, footer = { @@ -114,7 +108,7 @@ fun JoinRoomView( state.eventSink(JoinRoomEvents.JoinRoom) }, onKnockRoom = { - state.eventSink(JoinRoomEvents.KnockRoom(knockMessage)) + state.eventSink(JoinRoomEvents.KnockRoom) }, onCancelKnock = { state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = true)) @@ -169,9 +163,11 @@ private fun JoinRoomFooter( onGoBack: () -> Unit, modifier: Modifier = Modifier, ) { - Box(modifier = modifier - .fillMaxWidth() - .padding(top = 8.dp)) { + Box( + modifier = modifier + .fillMaxWidth() + .padding(top = 8.dp) + ) { if (state.contentState is ContentState.Failure) { Button( text = stringResource(CommonStrings.action_retry), @@ -397,9 +393,9 @@ private fun DefaultLoadedContent( OutlinedTextField( value = knockMessage, onValueChange = onKnockMessageUpdate, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 90.dp) + maxLines = 3, + minLines = 3, + modifier = Modifier.fillMaxWidth() ) Text( text = stringResource(R.string.screen_join_room_knock_message_description), diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt new file mode 100644 index 0000000000..cf928ea30a --- /dev/null +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.joinroom.impl.di + +import com.squareup.anvil.annotations.ContributesBinding +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 javax.inject.Inject + +interface CancelKnockRoom { + suspend operator fun invoke(roomId: RoomId): Result +} + +@ContributesBinding(SessionScope::class) +class DefaultCancelKnockRoom @Inject constructor(private val client: MatrixClient) : CancelKnockRoom { + override suspend fun invoke(roomId: RoomId): Result { + return client + .getPendingRoom(roomId) + ?.leave() + ?: Result.failure(IllegalStateException("No pending room found")) + } +} diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index db710b66ad..e981d6c46b 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -31,6 +31,7 @@ object JoinRoomModule { client: MatrixClient, joinRoom: JoinRoom, knockRoom: KnockRoom, + cancelKnockRoom: CancelKnockRoom, acceptDeclineInvitePresenter: Presenter, buildMeta: BuildMeta, ): JoinRoomPresenter.Factory { @@ -51,6 +52,7 @@ object JoinRoomModule { matrixClient = client, joinRoom = joinRoom, knockRoom = knockRoom, + cancelKnockRoom = cancelKnockRoom, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, buildMeta = buildMeta, ) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt index 9056687b37..9b82aa43e2 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt @@ -10,18 +10,26 @@ package io.element.android.features.joinroom.impl.di import com.squareup.anvil.annotations.ContributesBinding 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.core.RoomIdOrAlias import javax.inject.Inject interface KnockRoom { - suspend operator fun invoke(roomId: RoomId): Result + suspend operator fun invoke( + roomIdOrAlias: RoomIdOrAlias, + message: String, + serverNames: List, + ): Result } @ContributesBinding(SessionScope::class) class DefaultKnockRoom @Inject constructor(private val client: MatrixClient) : KnockRoom { - override suspend fun invoke(roomId: RoomId): Result { + override suspend fun invoke( + roomIdOrAlias: RoomIdOrAlias, + message: String, + serverNames: List + ): Result { return client - .knockRoom(roomId) + .knockRoom(roomIdOrAlias, message, serverNames) .map { } } } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt new file mode 100644 index 0000000000..baf7f10307 --- /dev/null +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.joinroom.impl + +import io.element.android.features.joinroom.impl.di.CancelKnockRoom +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.tests.testutils.simulateLongTask + +class FakeCancelKnockRoom( + var lambda: (RoomId) -> Result = { Result.success(Unit) } +) : CancelKnockRoom { + override suspend fun invoke(roomId: RoomId) = simulateLongTask { + lambda(roomId) + } +} diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt index e60707d165..991a35554e 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt @@ -8,13 +8,13 @@ package io.element.android.features.joinroom.impl import io.element.android.features.joinroom.impl.di.KnockRoom -import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.tests.testutils.simulateLongTask class FakeKnockRoom( - var lambda: (RoomId) -> Result = { Result.success(Unit) } + var lambda: (RoomIdOrAlias, String, List) -> Result = { _, _, _ -> Result.success(Unit) } ) : KnockRoom { - override suspend fun invoke(roomId: RoomId) = simulateLongTask { - lambda(roomId) + override suspend fun invoke(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List): Result = simulateLongTask { + lambda(roomIdOrAlias, message, serverNames) } } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 9d9574a0f3..48bf792447 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -12,6 +12,7 @@ import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.anAcceptDeclineInviteState +import io.element.android.features.joinroom.impl.di.CancelKnockRoom import io.element.android.features.joinroom.impl.di.KnockRoom import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction @@ -37,6 +38,7 @@ import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom import io.element.android.libraries.matrix.ui.model.toInviteSender import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -59,6 +61,8 @@ class JoinRoomPresenterTest { assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias())) assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown) assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState()) + assertThat(state.cancelKnockAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(state.knockAction).isEqualTo(AsyncAction.Uninitialized) assertThat(state.applicationName).isEqualTo("AppName") cancelAndIgnoreRemainingEvents() } @@ -325,16 +329,20 @@ class JoinRoomPresenterTest { @Test fun `present - emit knock room event`() = runTest { - val knockRoomSuccess = lambdaRecorder { _: RoomId -> + val knockMessage = "Knock message" + val knockRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: String, _: List -> Result.success(Unit) } - val knockRoomFailure = lambdaRecorder { roomId: RoomId -> - Result.failure(RuntimeException("Failed to knock room $roomId")) + val knockRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: String, _: List -> + Result.failure(RuntimeException("Failed to knock room $roomIdOrAlias")) } val fakeKnockRoom = FakeKnockRoom(knockRoomSuccess) val presenter = createJoinRoomPresenter(knockRoom = fakeKnockRoom) presenter.test { skipItems(1) + awaitItem().also { state -> + state.eventSink(JoinRoomEvents.UpdateKnockMessage(knockMessage)) + } awaitItem().also { state -> state.eventSink(JoinRoomEvents.KnockRoom) } @@ -353,8 +361,46 @@ class JoinRoomPresenterTest { } assert(knockRoomSuccess) .isCalledOnce() - .with(value(A_ROOM_ID)) + .with(value(A_ROOM_ID.toRoomIdOrAlias()), value(knockMessage), any()) assert(knockRoomFailure) + .isCalledOnce() + .with(value(A_ROOM_ID.toRoomIdOrAlias()), value(knockMessage), any()) + } + + @Test + fun `present - emit cancel knock room event`() = runTest { + val cancelKnockRoomSuccess = lambdaRecorder { _: RoomId -> + Result.success(Unit) + } + val cancelKnockRoomFailure = lambdaRecorder { roomId: RoomId -> + Result.failure(RuntimeException("Failed to knock room $roomId")) + } + val cancelKnockRoom = FakeCancelKnockRoom(cancelKnockRoomSuccess) + val presenter = createJoinRoomPresenter(cancelKnockRoom = cancelKnockRoom) + presenter.test { + skipItems(1) + awaitItem().also { state -> + state.eventSink(JoinRoomEvents.CancelKnock(true)) + } + awaitItem().also { state -> + assertThat(state.cancelKnockAction).isEqualTo(AsyncAction.ConfirmingNoParams) + state.eventSink(JoinRoomEvents.CancelKnock(false)) + } + assertThat(awaitItem().cancelKnockAction).isEqualTo(AsyncAction.Loading) + awaitItem().also { state -> + assertThat(state.cancelKnockAction).isEqualTo(AsyncAction.Success(Unit)) + cancelKnockRoom.lambda = cancelKnockRoomFailure + state.eventSink(JoinRoomEvents.CancelKnock(false)) + } + assertThat(awaitItem().cancelKnockAction).isEqualTo(AsyncAction.Loading) + awaitItem().also { state -> + assertThat(state.cancelKnockAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + assert(cancelKnockRoomFailure) + .isCalledOnce() + .with(value(A_ROOM_ID)) + assert(cancelKnockRoomSuccess) .isCalledOnce() .with(value(A_ROOM_ID)) } @@ -474,6 +520,7 @@ class JoinRoomPresenterTest { Result.success(Unit) }, knockRoom: KnockRoom = FakeKnockRoom(), + cancelKnockRoom: CancelKnockRoom = FakeCancelKnockRoom(), buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"), acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() } ): JoinRoomPresenter { @@ -486,6 +533,7 @@ class JoinRoomPresenterTest { matrixClient = matrixClient, joinRoom = FakeJoinRoom(joinRoomLambda), knockRoom = knockRoom, + cancelKnockRoom = cancelKnockRoom, buildMeta = buildMeta, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter ) diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt index dc93bac2ea..70857c52fc 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt @@ -61,6 +61,7 @@ class JoinRoomViewTest { rule.setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), + knockMessage = "Knock knock", eventSink = eventsRecorder, ), ) @@ -82,6 +83,33 @@ class JoinRoomViewTest { eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) } + @Test + fun `clicking on cancel knock request emit the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setJoinRoomView( + aJoinRoomState( + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(R.string.screen_join_room_cancel_knock_action) + eventsRecorder.assertSingle(JoinRoomEvents.CancelKnock(true)) + } + + @Test + fun `clicking on closing Cancel Knock error emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setJoinRoomView( + aJoinRoomState( + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked), + cancelKnockAction = AsyncAction.Failure(Exception("Error")), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) + } + @Test fun `clicking on closing Join error emits the expected Event`() { val eventsRecorder = EventsRecorder() @@ -170,6 +198,7 @@ private fun AndroidComposeTestRule.setJoinR onBackClick: () -> Unit = EnsureNeverCalled(), onJoinSuccess: () -> Unit = EnsureNeverCalled(), onKnockSuccess: () -> Unit = EnsureNeverCalled(), + onCancelKnockSuccess: () -> Unit = EnsureNeverCalled(), ) { setContent { JoinRoomView( @@ -177,6 +206,7 @@ private fun AndroidComposeTestRule.setJoinR onBackClick = onBackClick, onJoinSuccess = onJoinSuccess, onKnockSuccess = onKnockSuccess, + onCancelKnockSuccess = onCancelKnockSuccess ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index adf8fa960b..6af1763e83 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -21,9 +21,9 @@ import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService -import io.element.android.libraries.matrix.api.room.PendingRoom import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo +import io.element.android.libraries.matrix.api.room.PendingRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.preview.RoomPreview @@ -65,7 +65,7 @@ interface MatrixClient : Closeable { suspend fun removeAvatar(): Result suspend fun joinRoom(roomId: RoomId): Result suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result - suspend fun knockRoom(roomId: RoomId): Result + suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List): Result fun syncService(): SyncService fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 328be9608b..b7cd2d2061 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.CurrentUserMembership -import io.element.android.libraries.matrix.api.room.PendingRoom import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.PendingRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.preview.RoomPreview @@ -394,11 +394,13 @@ class RustMatrixClient( } } - override suspend fun knockRoom(roomId: RoomId): Result = withContext(sessionDispatcher){ + override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List): Result = withContext( + sessionDispatcher + ) { runCatching { - client.knock(roomId.toRoomIdOrAlias().identifier).destroy() + client.knock(roomIdOrAlias.identifier).destroy() try { - awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds, CurrentUserMembership.KNOCKED) + awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED) } catch (e: Exception) { Timber.e(e, "Timeout waiting for the room to be available in the room list") null diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index 6b076d4b91..e06424e723 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -50,7 +50,6 @@ class RustRoomFactory( private val roomSyncSubscriber: RoomSyncSubscriber, private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, ) { - @OptIn(ExperimentalCoroutinesApi::class) private val dispatcher = dispatchers.io.limitedParallelism(1) private val mutex = Mutex() diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 69c0cc00b6..2c69048c39 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -22,8 +22,8 @@ import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService -import io.element.android.libraries.matrix.api.room.PendingRoom import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.PendingRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.preview.RoomPreview @@ -114,7 +114,7 @@ class FakeMatrixClient( var joinRoomByIdOrAliasLambda: (RoomIdOrAlias, List) -> Result = { _, _ -> Result.success(null) } - var knockRoomLambda: (RoomId) -> Result = { + var knockRoomLambda: (RoomIdOrAlias, String, List) -> Result = { _, _, _ -> Result.success(null) } var getRoomSummaryFlowLambda = { _: RoomIdOrAlias -> @@ -223,7 +223,9 @@ class FakeMatrixClient( return joinRoomByIdOrAliasLambda(roomIdOrAlias, serverNames) } - override suspend fun knockRoom(roomId: RoomId): Result = knockRoomLambda(roomId) + override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List): Result { + return knockRoomLambda(roomIdOrAlias, message, serverNames) + } override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt index f5af6ec0d4..62f57a36d4 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -35,7 +34,7 @@ fun InviteSenderView( horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier, ) { - Box(modifier = Modifier.padding(vertical = 2.dp)){ + Box(modifier = Modifier.padding(vertical = 2.dp)) { Avatar(avatarData = inviteSender.avatarData) } Text(