knock : improve a bit code and add tests.

This commit is contained in:
ganfra 2024-10-23 11:42:46 +02:00
parent 89ac2a6831
commit 5cce2ff572
17 changed files with 193 additions and 52 deletions

View file

@ -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

View file

@ -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<AcceptDeclineInviteState>,
private val buildMeta: BuildMeta,
) : Presenter<JoinRoomState> {
@ -76,6 +81,7 @@ class JoinRoomPresenter @AssistedInject constructor(
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val cancelKnockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
var knockMessage by rememberSaveable { mutableStateOf("") }
val contentState by produceState<ContentState>(
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<AsyncAction<Unit>>, 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)
}
}
}

View file

@ -26,6 +26,7 @@ data class JoinRoomState(
val knockAction: AsyncAction<Unit>,
val cancelKnockAction: AsyncAction<Unit>,
val applicationName: String,
val knockMessage: String,
val eventSink: (JoinRoomEvents) -> Unit
) {
val joinAuthorisationStatus = when (contentState) {

View file

@ -131,6 +131,7 @@ fun aJoinRoomState(
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
cancelKnockAction: AsyncAction<Unit> = 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
)

View file

@ -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),
@ -322,7 +318,7 @@ private fun JoinRoomContent(
}
@Composable
fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
BoxWithConstraints(
modifier = modifier
.fillMaxHeight()
@ -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),

View file

@ -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<Unit>
}
@ContributesBinding(SessionScope::class)
class DefaultCancelKnockRoom @Inject constructor(private val client: MatrixClient) : CancelKnockRoom {
override suspend fun invoke(roomId: RoomId): Result<Unit> {
return client
.getPendingRoom(roomId)
?.leave()
?: Result.failure(IllegalStateException("No pending room found"))
}
}

View file

@ -31,6 +31,7 @@ object JoinRoomModule {
client: MatrixClient,
joinRoom: JoinRoom,
knockRoom: KnockRoom,
cancelKnockRoom: CancelKnockRoom,
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
buildMeta: BuildMeta,
): JoinRoomPresenter.Factory {
@ -51,6 +52,7 @@ object JoinRoomModule {
matrixClient = client,
joinRoom = joinRoom,
knockRoom = knockRoom,
cancelKnockRoom = cancelKnockRoom,
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
buildMeta = buildMeta,
)

View file

@ -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<Unit>
suspend operator fun invoke(
roomIdOrAlias: RoomIdOrAlias,
message: String,
serverNames: List<String>,
): Result<Unit>
}
@ContributesBinding(SessionScope::class)
class DefaultKnockRoom @Inject constructor(private val client: MatrixClient) : KnockRoom {
override suspend fun invoke(roomId: RoomId): Result<Unit> {
override suspend fun invoke(
roomIdOrAlias: RoomIdOrAlias,
message: String,
serverNames: List<String>
): Result<Unit> {
return client
.knockRoom(roomId)
.knockRoom(roomIdOrAlias, message, serverNames)
.map { }
}
}