knock : start adding ui to the JoinRoomView
This commit is contained in:
parent
1424296811
commit
928b1af702
9 changed files with 346 additions and 171 deletions
|
|
@ -10,8 +10,9 @@ package io.element.android.features.joinroom.impl
|
||||||
sealed interface JoinRoomEvents {
|
sealed interface JoinRoomEvents {
|
||||||
data object RetryFetchingContent : JoinRoomEvents
|
data object RetryFetchingContent : JoinRoomEvents
|
||||||
data object JoinRoom : JoinRoomEvents
|
data object JoinRoom : JoinRoomEvents
|
||||||
data object KnockRoom : JoinRoomEvents
|
data class KnockRoom(val message: String) : JoinRoomEvents
|
||||||
data object ClearError : JoinRoomEvents
|
data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvents
|
||||||
|
data object ClearActionStates : JoinRoomEvents
|
||||||
data object AcceptInvite : JoinRoomEvents
|
data object AcceptInvite : JoinRoomEvents
|
||||||
data object DeclineInvite : JoinRoomEvents
|
data object DeclineInvite : JoinRoomEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class JoinRoomNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = ::navigateUp,
|
onBackClick = ::navigateUp,
|
||||||
onJoinSuccess = ::navigateUp,
|
onJoinSuccess = ::navigateUp,
|
||||||
onKnockSuccess = ::navigateUp,
|
onKnockSuccess = { },
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
acceptDeclineInviteView.Render(
|
acceptDeclineInviteView.Render(
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty())
|
val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty())
|
||||||
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val knockAction: 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) }
|
||||||
val contentState by produceState<ContentState>(
|
val contentState by produceState<ContentState>(
|
||||||
initialValue = ContentState.Loading(roomIdOrAlias),
|
initialValue = ContentState.Loading(roomIdOrAlias),
|
||||||
key1 = roomInfo,
|
key1 = roomInfo,
|
||||||
|
|
@ -110,7 +111,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
fun handleEvents(event: JoinRoomEvents) {
|
fun handleEvents(event: JoinRoomEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
||||||
JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction)
|
is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, event.message)
|
||||||
JoinRoomEvents.AcceptInvite -> {
|
JoinRoomEvents.AcceptInvite -> {
|
||||||
val inviteData = contentState.toInviteData() ?: return
|
val inviteData = contentState.toInviteData() ?: return
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
|
|
@ -123,12 +124,14 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
AcceptDeclineInviteEvents.DeclineInvite(inviteData)
|
AcceptDeclineInviteEvents.DeclineInvite(inviteData)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction)
|
||||||
JoinRoomEvents.RetryFetchingContent -> {
|
JoinRoomEvents.RetryFetchingContent -> {
|
||||||
retryCount++
|
retryCount++
|
||||||
}
|
}
|
||||||
JoinRoomEvents.ClearError -> {
|
JoinRoomEvents.ClearActionStates -> {
|
||||||
knockAction.value = AsyncAction.Uninitialized
|
knockAction.value = AsyncAction.Uninitialized
|
||||||
joinAction.value = AsyncAction.Uninitialized
|
joinAction.value = AsyncAction.Uninitialized
|
||||||
|
cancelKnockAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +141,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
joinAction = joinAction.value,
|
joinAction = joinAction.value,
|
||||||
knockAction = knockAction.value,
|
knockAction = knockAction.value,
|
||||||
|
cancelKnockAction = cancelKnockAction.value,
|
||||||
applicationName = buildMeta.applicationName,
|
applicationName = buildMeta.applicationName,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
|
|
@ -153,11 +157,23 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>) = launch {
|
private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>, message: String) = launch {
|
||||||
knockAction.runUpdatingState {
|
knockAction.runUpdatingState {
|
||||||
knockRoom(roomId)
|
knockRoom(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.cancelKnockRoom(requiresConfirmation: Boolean, cancelKnockAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
|
if (requiresConfirmation) {
|
||||||
|
cancelKnockAction.value = AsyncAction.ConfirmingNoParams
|
||||||
|
} else {
|
||||||
|
matrixClient.getPendingRoom(roomId)?.use { room ->
|
||||||
|
cancelKnockAction.runUpdatingState {
|
||||||
|
room.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomPreview.toContentState(): ContentState {
|
private fun RoomPreview.toContentState(): ContentState {
|
||||||
|
|
@ -206,7 +222,7 @@ internal fun MatrixRoomInfo.toContentState(): ContentState {
|
||||||
name = name,
|
name = name,
|
||||||
topic = topic,
|
topic = topic,
|
||||||
alias = canonicalAlias,
|
alias = canonicalAlias,
|
||||||
numberOfMembers = activeMembersCount.toLong(),
|
numberOfMembers = activeMembersCount,
|
||||||
isDm = isDm,
|
isDm = isDm,
|
||||||
roomType = if (isSpace) RoomType.Space else RoomType.Room,
|
roomType = if (isSpace) RoomType.Space else RoomType.Room,
|
||||||
roomAvatarUrl = avatarUrl,
|
roomAvatarUrl = avatarUrl,
|
||||||
|
|
@ -214,6 +230,7 @@ internal fun MatrixRoomInfo.toContentState(): ContentState {
|
||||||
currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
|
currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
|
||||||
inviteSender = inviter?.toInviteSender()
|
inviteSender = inviter?.toInviteSender()
|
||||||
)
|
)
|
||||||
|
currentUserMembership == CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||||
isPublic -> JoinAuthorisationStatus.CanJoin
|
isPublic -> JoinAuthorisationStatus.CanJoin
|
||||||
else -> JoinAuthorisationStatus.Unknown
|
else -> JoinAuthorisationStatus.Unknown
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ data class JoinRoomState(
|
||||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||||
val joinAction: AsyncAction<Unit>,
|
val joinAction: AsyncAction<Unit>,
|
||||||
val knockAction: AsyncAction<Unit>,
|
val knockAction: AsyncAction<Unit>,
|
||||||
|
val cancelKnockAction: AsyncAction<Unit>,
|
||||||
val applicationName: String,
|
val applicationName: String,
|
||||||
val eventSink: (JoinRoomEvents) -> Unit
|
val eventSink: (JoinRoomEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -68,6 +69,7 @@ sealed interface ContentState {
|
||||||
|
|
||||||
sealed interface JoinAuthorisationStatus {
|
sealed interface JoinAuthorisationStatus {
|
||||||
data class IsInvited(val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
data class IsInvited(val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
||||||
|
data object IsKnocked : JoinAuthorisationStatus
|
||||||
data object CanKnock : JoinAuthorisationStatus
|
data object CanKnock : JoinAuthorisationStatus
|
||||||
data object CanJoin : JoinAuthorisationStatus
|
data object CanJoin : JoinAuthorisationStatus
|
||||||
data object Unknown : JoinAuthorisationStatus
|
data object Unknown : JoinAuthorisationStatus
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,12 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||||
isDm = true,
|
isDm = true,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
aJoinRoomState(
|
||||||
|
contentState = aLoadedContentState(
|
||||||
|
name = "A knocked Room",
|
||||||
|
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,12 +130,14 @@ fun aJoinRoomState(
|
||||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||||
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
|
cancelKnockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||||
) = JoinRoomState(
|
) = JoinRoomState(
|
||||||
contentState = contentState,
|
contentState = contentState,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
joinAction = joinAction,
|
joinAction = joinAction,
|
||||||
knockAction = knockAction,
|
knockAction = knockAction,
|
||||||
|
cancelKnockAction = cancelKnockAction,
|
||||||
applicationName = "AppName",
|
applicationName = "AppName",
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,31 +7,50 @@
|
||||||
|
|
||||||
package io.element.android.features.joinroom.impl
|
package io.element.android.features.joinroom.impl
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewSubtitleAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewSubtitleAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.RoomPreviewMembersCountMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.RoomPreviewMembersCountMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
|
@ -41,11 +60,13 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.button.SuperButton
|
import io.element.android.libraries.designsystem.components.button.SuperButton
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
|
|
@ -64,17 +85,20 @@ fun JoinRoomView(
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
|
var knockMessage by rememberSaveable { mutableStateOf("") }
|
||||||
LightGradientBackground()
|
LightGradientBackground()
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
paddingValues = PaddingValues(16.dp),
|
paddingValues = PaddingValues(16.dp),
|
||||||
topBar = {
|
topBar = {
|
||||||
JoinRoomTopBar(onBackClick = onBackClick)
|
JoinRoomTopBar(contentState = state.contentState, onBackClick = onBackClick)
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
JoinRoomContent(
|
JoinRoomContent(
|
||||||
contentState = state.contentState,
|
contentState = state.contentState,
|
||||||
applicationName = state.applicationName,
|
applicationName = state.applicationName,
|
||||||
|
knockMessage = knockMessage,
|
||||||
|
onKnockMessageUpdate = { knockMessage = it },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
|
|
@ -90,7 +114,10 @@ fun JoinRoomView(
|
||||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||||
},
|
},
|
||||||
onKnockRoom = {
|
onKnockRoom = {
|
||||||
state.eventSink(JoinRoomEvents.KnockRoom)
|
state.eventSink(JoinRoomEvents.KnockRoom(knockMessage))
|
||||||
|
},
|
||||||
|
onCancelKnock = {
|
||||||
|
state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = true))
|
||||||
},
|
},
|
||||||
onRetry = {
|
onRetry = {
|
||||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||||
|
|
@ -103,12 +130,27 @@ fun JoinRoomView(
|
||||||
AsyncActionView(
|
AsyncActionView(
|
||||||
async = state.joinAction,
|
async = state.joinAction,
|
||||||
onSuccess = { onJoinSuccess() },
|
onSuccess = { onJoinSuccess() },
|
||||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) },
|
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||||
)
|
)
|
||||||
AsyncActionView(
|
AsyncActionView(
|
||||||
async = state.knockAction,
|
async = state.knockAction,
|
||||||
onSuccess = { onKnockSuccess() },
|
onSuccess = { onKnockSuccess() },
|
||||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) },
|
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||||
|
)
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.cancelKnockAction,
|
||||||
|
onSuccess = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||||
|
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||||
|
confirmationDialog = {
|
||||||
|
ConfirmationDialog(
|
||||||
|
content = stringResource(R.string.screen_join_room_cancel_knock_alert_description),
|
||||||
|
title = stringResource(R.string.screen_join_room_cancel_knock_alert_title),
|
||||||
|
submitText = stringResource(R.string.screen_join_room_cancel_knock_alert_confirmation),
|
||||||
|
cancelText = stringResource(CommonStrings.action_no),
|
||||||
|
onSubmitClick = { state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = false)) },
|
||||||
|
onDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,63 +161,77 @@ private fun JoinRoomFooter(
|
||||||
onDeclineInvite: () -> Unit,
|
onDeclineInvite: () -> Unit,
|
||||||
onJoinRoom: () -> Unit,
|
onJoinRoom: () -> Unit,
|
||||||
onKnockRoom: () -> Unit,
|
onKnockRoom: () -> Unit,
|
||||||
|
onCancelKnock: () -> Unit,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
onGoBack: () -> Unit,
|
onGoBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (state.contentState is ContentState.Failure) {
|
Box(modifier = modifier.fillMaxWidth().padding(top = 8.dp)) {
|
||||||
Button(
|
if (state.contentState is ContentState.Failure) {
|
||||||
text = stringResource(CommonStrings.action_retry),
|
Button(
|
||||||
onClick = onRetry,
|
text = stringResource(CommonStrings.action_retry),
|
||||||
modifier = modifier.fillMaxWidth(),
|
onClick = onRetry,
|
||||||
size = ButtonSize.Large,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
size = ButtonSize.Large,
|
||||||
} else if (state.contentState is ContentState.Loaded && state.contentState.roomType == RoomType.Space) {
|
)
|
||||||
Button(
|
} else if (state.contentState is ContentState.Loaded && state.contentState.roomType == RoomType.Space) {
|
||||||
text = stringResource(CommonStrings.action_go_back),
|
Button(
|
||||||
onClick = onGoBack,
|
text = stringResource(CommonStrings.action_go_back),
|
||||||
modifier = modifier.fillMaxWidth(),
|
onClick = onGoBack,
|
||||||
size = ButtonSize.Large,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
size = ButtonSize.Large,
|
||||||
} else {
|
)
|
||||||
val joinAuthorisationStatus = state.joinAuthorisationStatus
|
} else {
|
||||||
when (joinAuthorisationStatus) {
|
val joinAuthorisationStatus = state.joinAuthorisationStatus
|
||||||
is JoinAuthorisationStatus.IsInvited -> {
|
when (joinAuthorisationStatus) {
|
||||||
ButtonRowMolecule(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
is JoinAuthorisationStatus.IsInvited -> {
|
||||||
|
ButtonRowMolecule(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||||
|
OutlinedButton(
|
||||||
|
text = stringResource(CommonStrings.action_decline),
|
||||||
|
onClick = onDeclineInvite,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
size = ButtonSize.LargeLowPadding,
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
text = stringResource(CommonStrings.action_accept),
|
||||||
|
onClick = onAcceptInvite,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
size = ButtonSize.LargeLowPadding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JoinAuthorisationStatus.CanJoin -> {
|
||||||
|
SuperButton(
|
||||||
|
onClick = onJoinRoom,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
buttonSize = ButtonSize.Large,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_join_room_join_action),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JoinAuthorisationStatus.CanKnock -> {
|
||||||
|
SuperButton(
|
||||||
|
onClick = onKnockRoom,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
buttonSize = ButtonSize.Large,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_join_room_knock_action),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JoinAuthorisationStatus.IsKnocked -> {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
text = stringResource(CommonStrings.action_decline),
|
text = stringResource(R.string.screen_join_room_cancel_knock_action),
|
||||||
onClick = onDeclineInvite,
|
onClick = onCancelKnock,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
size = ButtonSize.LargeLowPadding,
|
size = ButtonSize.Large,
|
||||||
)
|
|
||||||
Button(
|
|
||||||
text = stringResource(CommonStrings.action_accept),
|
|
||||||
onClick = onAcceptInvite,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
size = ButtonSize.LargeLowPadding,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
JoinAuthorisationStatus.Unknown -> Unit
|
||||||
}
|
}
|
||||||
JoinAuthorisationStatus.CanJoin -> {
|
|
||||||
SuperButton(
|
|
||||||
onClick = onJoinRoom,
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
buttonSize = ButtonSize.Large,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.screen_join_room_join_action),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JoinAuthorisationStatus.CanKnock -> {
|
|
||||||
Button(
|
|
||||||
text = stringResource(R.string.screen_join_room_knock_action),
|
|
||||||
onClick = onKnockRoom,
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
size = ButtonSize.Large,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
JoinAuthorisationStatus.Unknown -> Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,132 +240,219 @@ private fun JoinRoomFooter(
|
||||||
private fun JoinRoomContent(
|
private fun JoinRoomContent(
|
||||||
contentState: ContentState,
|
contentState: ContentState,
|
||||||
applicationName: String,
|
applicationName: String,
|
||||||
|
knockMessage: String,
|
||||||
|
onKnockMessageUpdate: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (contentState) {
|
Box(modifier = modifier) {
|
||||||
is ContentState.Loaded -> {
|
when (contentState) {
|
||||||
RoomPreviewOrganism(
|
is ContentState.Loaded -> {
|
||||||
modifier = modifier,
|
when (contentState.joinAuthorisationStatus) {
|
||||||
avatar = {
|
is JoinAuthorisationStatus.IsKnocked -> {
|
||||||
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
|
IsKnockedLoadedContent()
|
||||||
},
|
}
|
||||||
title = {
|
else -> {
|
||||||
if (contentState.name != null) {
|
DefaultLoadedContent(
|
||||||
RoomPreviewTitleAtom(
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
title = contentState.name,
|
contentState = contentState,
|
||||||
|
applicationName = applicationName,
|
||||||
|
knockMessage = knockMessage,
|
||||||
|
onKnockMessageUpdate = onKnockMessageUpdate
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
RoomPreviewTitleAtom(
|
|
||||||
title = stringResource(id = CommonStrings.common_no_room_name),
|
|
||||||
fontStyle = FontStyle.Italic
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
subtitle = {
|
|
||||||
if (contentState.alias != null) {
|
|
||||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
description = {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
|
||||||
if (inviteSender != null) {
|
|
||||||
InviteSenderView(inviteSender = inviteSender)
|
|
||||||
}
|
|
||||||
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
|
||||||
if (contentState.roomType == RoomType.Space) {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.screen_join_room_space_not_supported_title),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.screen_join_room_space_not_supported_description, applicationName),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
memberCount = {
|
|
||||||
if (contentState.showMemberCount) {
|
|
||||||
RoomPreviewMembersCountMolecule(memberCount = contentState.numberOfMembers ?: 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
is ContentState.UnknownRoom -> {
|
||||||
is ContentState.UnknownRoom -> {
|
RoomPreviewOrganism(
|
||||||
RoomPreviewOrganism(
|
avatar = {
|
||||||
modifier = modifier,
|
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||||
avatar = {
|
},
|
||||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
title = {
|
||||||
},
|
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
||||||
title = {
|
},
|
||||||
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
subtitle = {
|
||||||
},
|
RoomPreviewSubtitleAtom(stringResource(R.string.screen_join_room_subtitle_no_preview))
|
||||||
subtitle = {
|
},
|
||||||
RoomPreviewSubtitleAtom(stringResource(R.string.screen_join_room_subtitle_no_preview))
|
)
|
||||||
},
|
}
|
||||||
)
|
is ContentState.Loading -> {
|
||||||
}
|
RoomPreviewOrganism(
|
||||||
is ContentState.Loading -> {
|
avatar = {
|
||||||
RoomPreviewOrganism(
|
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||||
modifier = modifier,
|
},
|
||||||
avatar = {
|
title = {
|
||||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
||||||
},
|
},
|
||||||
title = {
|
subtitle = {
|
||||||
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
PlaceholderAtom(width = 140.dp, height = 20.dp)
|
||||||
},
|
},
|
||||||
subtitle = {
|
)
|
||||||
PlaceholderAtom(width = 140.dp, height = 20.dp)
|
}
|
||||||
},
|
is ContentState.Failure -> {
|
||||||
)
|
RoomPreviewOrganism(
|
||||||
}
|
avatar = {
|
||||||
is ContentState.Failure -> {
|
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||||
RoomPreviewOrganism(
|
},
|
||||||
modifier = modifier,
|
title = {
|
||||||
avatar = {
|
when (contentState.roomIdOrAlias) {
|
||||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
is RoomIdOrAlias.Alias -> {
|
||||||
},
|
RoomPreviewTitleAtom(contentState.roomIdOrAlias.identifier)
|
||||||
title = {
|
}
|
||||||
when (contentState.roomIdOrAlias) {
|
is RoomIdOrAlias.Id -> {
|
||||||
is RoomIdOrAlias.Alias -> {
|
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
||||||
RoomPreviewTitleAtom(contentState.roomIdOrAlias.identifier)
|
}
|
||||||
}
|
}
|
||||||
is RoomIdOrAlias.Id -> {
|
},
|
||||||
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
subtitle = {
|
||||||
}
|
Text(
|
||||||
}
|
text = stringResource(id = CommonStrings.error_unknown),
|
||||||
},
|
textAlign = TextAlign.Center,
|
||||||
subtitle = {
|
color = MaterialTheme.colorScheme.error,
|
||||||
Text(
|
)
|
||||||
text = stringResource(id = CommonStrings.error_unknown),
|
},
|
||||||
textAlign = TextAlign.Center,
|
)
|
||||||
color = MaterialTheme.colorScheme.error,
|
}
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
||||||
|
BoxWithConstraints(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
IconTitleSubtitleMolecule(
|
||||||
|
modifier = Modifier.sizeIn(minHeight = maxHeight*0.7f),
|
||||||
|
iconImageVector = CompoundIcons.CheckCircleSolid(),
|
||||||
|
title = stringResource(R.string.screen_join_room_knock_sent_title),
|
||||||
|
subTitle = stringResource(R.string.screen_join_room_knock_sent_description),
|
||||||
|
iconTint = ElementTheme.colors.iconSuccessPrimary,
|
||||||
|
iconBackgroundTint = ElementTheme.colors.bgSuccessSubtle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DefaultLoadedContent(
|
||||||
|
contentState: ContentState.Loaded,
|
||||||
|
applicationName: String,
|
||||||
|
knockMessage: String,
|
||||||
|
onKnockMessageUpdate: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
RoomPreviewOrganism(
|
||||||
|
modifier = modifier,
|
||||||
|
avatar = {
|
||||||
|
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
if (contentState.name != null) {
|
||||||
|
RoomPreviewTitleAtom(
|
||||||
|
title = contentState.name,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RoomPreviewTitleAtom(
|
||||||
|
title = stringResource(id = CommonStrings.common_no_room_name),
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subtitle = {
|
||||||
|
if (contentState.alias != null) {
|
||||||
|
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
||||||
|
if (inviteSender != null) {
|
||||||
|
InviteSenderView(inviteSender = inviteSender)
|
||||||
|
}
|
||||||
|
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
||||||
|
if (contentState.roomType == RoomType.Space) {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_join_room_space_not_supported_title),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_join_room_space_not_supported_description, applicationName),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
} else if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = knockMessage,
|
||||||
|
onValueChange = onKnockMessageUpdate,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 90.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_join_room_knock_message_description),
|
||||||
|
style = ElementTheme.typography.fontBodySmRegular,
|
||||||
|
color = ElementTheme.colors.textPlaceholder,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
memberCount = {
|
||||||
|
if (contentState.showMemberCount) {
|
||||||
|
RoomPreviewMembersCountMolecule(memberCount = contentState.numberOfMembers ?: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun JoinRoomTopBar(
|
private fun JoinRoomTopBar(
|
||||||
|
contentState: ContentState,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
BackButton(onClick = onBackClick)
|
BackButton(onClick = onBackClick)
|
||||||
},
|
},
|
||||||
title = {},
|
title = {
|
||||||
|
if (contentState is ContentState.Loaded && contentState.joinAuthorisationStatus is JoinAuthorisationStatus.IsKnocked) {
|
||||||
|
val roundedCornerShape = RoundedCornerShape(8.dp)
|
||||||
|
val titleModifier = Modifier
|
||||||
|
.clip(roundedCornerShape)
|
||||||
|
if (contentState.name != null) {
|
||||||
|
Row(
|
||||||
|
modifier = titleModifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Avatar(avatarData = contentState.avatarData(AvatarSize.TimelineRoom))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
text = contentState.name,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IconTitlePlaceholdersRowMolecule(
|
||||||
|
iconSize = AvatarSize.TimelineRoom.dp,
|
||||||
|
modifier = titleModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,9 @@ interface KnockRoom {
|
||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
class DefaultKnockRoom @Inject constructor(private val client: MatrixClient) : KnockRoom {
|
class DefaultKnockRoom @Inject constructor(private val client: MatrixClient) : KnockRoom {
|
||||||
override suspend fun invoke(roomId: RoomId) = client.knockRoom(roomId)
|
override suspend fun invoke(roomId: RoomId): Result<Unit> {
|
||||||
|
return client
|
||||||
|
.knockRoom(roomId)
|
||||||
|
.map { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ class JoinRoomPresenterTest {
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
|
||||||
state.eventSink(JoinRoomEvents.ClearError)
|
state.eventSink(JoinRoomEvents.ClearActionStates)
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized)
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class JoinRoomViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -93,7 +93,7 @@ class JoinRoomViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue