feature(room preview): Add option to forget room, improve the room preview screen for banned rooms.
Some internal refactoring was done too: - Remove RoomInfo.isPublic to only use JoinRule. - Also take into account restricted access rooms for previews.
This commit is contained in:
parent
819503b162
commit
a73bcb71d5
50 changed files with 886 additions and 357 deletions
|
|
@ -9,8 +9,10 @@ package io.element.android.features.joinroom.impl
|
|||
|
||||
sealed interface JoinRoomEvents {
|
||||
data object RetryFetchingContent : JoinRoomEvents
|
||||
data object DismissContent : JoinRoomEvents
|
||||
data object JoinRoom : JoinRoomEvents
|
||||
data object KnockRoom : JoinRoomEvents
|
||||
data object ForgetRoom : JoinRoomEvents
|
||||
data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvents
|
||||
data class UpdateKnockMessage(val message: String) : JoinRoomEvents
|
||||
data object ClearActionStates : JoinRoomEvents
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class JoinRoomNode @AssistedInject constructor(
|
|||
state = state,
|
||||
onBackClick = ::navigateUp,
|
||||
onJoinSuccess = ::navigateUp,
|
||||
onForgetSuccess = ::navigateUp,
|
||||
onCancelKnockSuccess = {},
|
||||
onKnockSuccess = {},
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -26,22 +26,27 @@ 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.ForgetRoom
|
||||
import io.element.android.features.joinroom.impl.di.KnockRoom
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
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 io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.getRoomInfoFlow
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -58,6 +63,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
private val joinRoom: JoinRoom,
|
||||
private val knockRoom: KnockRoom,
|
||||
private val cancelKnockRoom: CancelKnockRoom,
|
||||
private val forgetRoom: ForgetRoom,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : Presenter<JoinRoomState> {
|
||||
|
|
@ -79,13 +85,17 @@ 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) }
|
||||
val forgetRoomAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
var knockMessage by rememberSaveable { mutableStateOf("") }
|
||||
var isDismissingContent by remember { mutableStateOf(false) }
|
||||
val contentState by produceState<ContentState>(
|
||||
initialValue = ContentState.Loading(roomIdOrAlias),
|
||||
initialValue = ContentState.Loading,
|
||||
key1 = roomInfo,
|
||||
key2 = retryCount,
|
||||
key3 = isDismissingContent,
|
||||
) {
|
||||
when {
|
||||
isDismissingContent -> value = ContentState.Dismissing
|
||||
roomInfo.isPresent -> {
|
||||
value = roomInfo.get().toContentState()
|
||||
}
|
||||
|
|
@ -93,17 +103,17 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
value = roomDescription.get().toContentState()
|
||||
}
|
||||
else -> {
|
||||
value = ContentState.Loading(roomIdOrAlias)
|
||||
value = ContentState.Loading
|
||||
val result = matrixClient.getRoomPreviewInfo(roomIdOrAlias, serverNames)
|
||||
value = result.fold(
|
||||
onSuccess = { previewInfo ->
|
||||
previewInfo.toContentState()
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
if (throwable.message?.contains("403") == true) {
|
||||
ContentState.UnknownRoom(roomIdOrAlias)
|
||||
if (throwable is ClientException.MatrixApi && (throwable.kind == ErrorKind.NotFound || throwable.kind == ErrorKind.Forbidden)) {
|
||||
ContentState.UnknownRoom
|
||||
} else {
|
||||
ContentState.Failure(roomIdOrAlias, throwable)
|
||||
ContentState.Failure(throwable)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -136,18 +146,25 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
knockAction.value = AsyncAction.Uninitialized
|
||||
joinAction.value = AsyncAction.Uninitialized
|
||||
cancelKnockAction.value = AsyncAction.Uninitialized
|
||||
forgetRoomAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
is JoinRoomEvents.UpdateKnockMessage -> {
|
||||
knockMessage = event.message.take(MAX_KNOCK_MESSAGE_LENGTH)
|
||||
}
|
||||
JoinRoomEvents.DismissContent -> {
|
||||
isDismissingContent = true
|
||||
}
|
||||
JoinRoomEvents.ForgetRoom -> coroutineScope.forgetRoom(forgetRoomAction)
|
||||
}
|
||||
}
|
||||
|
||||
return JoinRoomState(
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
contentState = contentState,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
joinAction = joinAction.value,
|
||||
knockAction = knockAction.value,
|
||||
forgetAction = forgetRoomAction.value,
|
||||
cancelKnockAction = cancelKnockAction.value,
|
||||
applicationName = buildMeta.applicationName,
|
||||
knockMessage = knockMessage,
|
||||
|
|
@ -161,7 +178,13 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
roomIdOrAlias = roomIdOrAlias,
|
||||
serverNames = serverNames,
|
||||
trigger = trigger
|
||||
)
|
||||
).mapFailure {
|
||||
if (it is ClientException.MatrixApi && it.kind == ErrorKind.Forbidden) {
|
||||
JoinRoomFailures.UnauthorizedJoin
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +203,12 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.forgetRoom(forgetAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
forgetAction.runUpdatingState {
|
||||
forgetRoom.invoke(roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomPreviewInfo.toContentState(): ContentState {
|
||||
|
|
@ -192,12 +221,11 @@ private fun RoomPreviewInfo.toContentState(): ContentState {
|
|||
isDm = false,
|
||||
roomType = roomType,
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = when {
|
||||
// Note when isInvited, roomInfo will be used, so if this happen, it will be temporary.
|
||||
isInvited -> JoinAuthorisationStatus.IsInvited(null)
|
||||
canKnock -> JoinAuthorisationStatus.CanKnock
|
||||
isPublic -> JoinAuthorisationStatus.CanJoin
|
||||
else -> JoinAuthorisationStatus.Unknown
|
||||
joinAuthorisationStatus = when (membership) {
|
||||
CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(null)
|
||||
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(null)
|
||||
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||
else -> joinRule.toJoinAuthorisationStatus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -232,17 +260,31 @@ internal fun MatrixRoomInfo.toContentState(): ContentState {
|
|||
isDm = isDm,
|
||||
roomType = if (isSpace) RoomType.Space else RoomType.Room,
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = when {
|
||||
currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
|
||||
joinAuthorisationStatus = when (currentUserMembership) {
|
||||
CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
|
||||
inviteSender = inviter?.toInviteSender()
|
||||
)
|
||||
currentUserMembership == CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||
isPublic -> JoinAuthorisationStatus.CanJoin
|
||||
else -> JoinAuthorisationStatus.Unknown
|
||||
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(
|
||||
banSender = inviter?.toInviteSender()
|
||||
)
|
||||
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||
else -> joinRule.toJoinAuthorisationStatus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun JoinRule?.toJoinAuthorisationStatus(): JoinAuthorisationStatus {
|
||||
return when (this) {
|
||||
JoinRule.Knock,
|
||||
is JoinRule.KnockRestricted -> JoinAuthorisationStatus.CanKnock
|
||||
JoinRule.Invite,
|
||||
JoinRule.Private -> JoinAuthorisationStatus.NeedInvite
|
||||
is JoinRule.Restricted -> JoinAuthorisationStatus.Restricted
|
||||
JoinRule.Public -> JoinAuthorisationStatus.CanJoin
|
||||
else -> JoinAuthorisationStatus.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun ContentState.toInviteData(): InviteData? {
|
||||
return when (this) {
|
||||
|
|
|
|||
|
|
@ -22,30 +22,49 @@ internal const val MAX_KNOCK_MESSAGE_LENGTH = 500
|
|||
|
||||
@Immutable
|
||||
data class JoinRoomState(
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val contentState: ContentState,
|
||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val joinAction: AsyncAction<Unit>,
|
||||
val knockAction: AsyncAction<Unit>,
|
||||
val forgetAction: AsyncAction<Unit>,
|
||||
val cancelKnockAction: AsyncAction<Unit>,
|
||||
val applicationName: String,
|
||||
private val applicationName: String,
|
||||
val knockMessage: String,
|
||||
val eventSink: (JoinRoomEvents) -> Unit
|
||||
) {
|
||||
val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoomFailures.UnauthorizedJoin
|
||||
val joinAuthorisationStatus = when (contentState) {
|
||||
// Use the join authorisation status from the loaded content state
|
||||
is ContentState.Loaded -> contentState.joinAuthorisationStatus
|
||||
// Assume that if the room is unknown, the user can join it
|
||||
is ContentState.UnknownRoom -> JoinAuthorisationStatus.CanJoin
|
||||
// Otherwise assume that the user can't join the room
|
||||
else -> JoinAuthorisationStatus.Unknown
|
||||
is ContentState.Loaded -> {
|
||||
when {
|
||||
contentState.roomType == RoomType.Space -> {
|
||||
JoinAuthorisationStatus.IsSpace(applicationName)
|
||||
}
|
||||
isJoinActionUnauthorized -> {
|
||||
JoinAuthorisationStatus.Unauthorized
|
||||
}
|
||||
else -> {
|
||||
contentState.joinAuthorisationStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
is ContentState.UnknownRoom -> {
|
||||
if (isJoinActionUnauthorized) {
|
||||
JoinAuthorisationStatus.Unauthorized
|
||||
} else {
|
||||
JoinAuthorisationStatus.Unknown
|
||||
}
|
||||
}
|
||||
else -> JoinAuthorisationStatus.None
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface ContentState {
|
||||
data class Loading(val roomIdOrAlias: RoomIdOrAlias) : ContentState
|
||||
data class Failure(val roomIdOrAlias: RoomIdOrAlias, val error: Throwable) : ContentState
|
||||
data class UnknownRoom(val roomIdOrAlias: RoomIdOrAlias) : ContentState
|
||||
data object Dismissing : ContentState
|
||||
data object Loading : ContentState
|
||||
data class Failure(val error: Throwable) : ContentState
|
||||
data object UnknownRoom : ContentState
|
||||
data class Loaded(
|
||||
val roomId: RoomId,
|
||||
val name: String?,
|
||||
|
|
@ -71,9 +90,19 @@ sealed interface ContentState {
|
|||
}
|
||||
|
||||
sealed interface JoinAuthorisationStatus {
|
||||
data object None : JoinAuthorisationStatus
|
||||
data class IsSpace(val applicationName: String) : JoinAuthorisationStatus
|
||||
data class IsInvited(val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
||||
data class IsBanned(val banSender: InviteSender?) : JoinAuthorisationStatus
|
||||
data object IsKnocked : JoinAuthorisationStatus
|
||||
data object CanKnock : JoinAuthorisationStatus
|
||||
data object CanJoin : JoinAuthorisationStatus
|
||||
data object NeedInvite : JoinAuthorisationStatus
|
||||
data object Restricted : JoinAuthorisationStatus
|
||||
data object Unknown : JoinAuthorisationStatus
|
||||
data object Unauthorized : JoinAuthorisationStatus
|
||||
}
|
||||
|
||||
sealed class JoinRoomFailures : Exception() {
|
||||
data object UnauthorizedJoin : JoinRoomFailures()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
|
||||
|
|
@ -25,10 +26,10 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
|||
override val values: Sequence<JoinRoomState>
|
||||
get() = sequenceOf(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadingContentState()
|
||||
contentState = ContentState.Loading
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = anUnknownContentState()
|
||||
contentState = ContentState.UnknownRoom
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(
|
||||
|
|
@ -40,6 +41,14 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
|||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
|
||||
joinAction = AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
|
||||
joinAction = AsyncAction.Failure(ClientException.Generic("Something went wrong"))
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null))
|
||||
),
|
||||
|
|
@ -52,9 +61,6 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
|||
aJoinRoomState(
|
||||
contentState = aFailureContentState()
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aFailureContentState(roomIdOrAlias = A_ROOM_ALIAS.toRoomIdOrAlias())
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(
|
||||
roomId = RoomId("!aSpaceId:domain"),
|
||||
|
|
@ -98,23 +104,41 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
|||
name = "A knocked Room",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked
|
||||
)
|
||||
)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(
|
||||
name = "A private room",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.NeedInvite
|
||||
)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(
|
||||
name = "A banned room",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(
|
||||
InviteSender(
|
||||
userId = UserId("@alice:domain"),
|
||||
displayName = "Alice",
|
||||
avatarData = AvatarData("alice", "Alice", size = AvatarSize.InviteSender),
|
||||
membershipChangeReason = "spamming"
|
||||
)
|
||||
),
|
||||
)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(
|
||||
name = "A restricted room",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.Restricted,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aFailureContentState(
|
||||
roomIdOrAlias: RoomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias()
|
||||
): ContentState {
|
||||
fun aFailureContentState(): ContentState {
|
||||
return ContentState.Failure(
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
error = Exception("Error"),
|
||||
)
|
||||
}
|
||||
|
||||
fun anUnknownContentState(roomId: RoomId = A_ROOM_ID) = ContentState.UnknownRoom(roomId.toRoomIdOrAlias())
|
||||
|
||||
fun aLoadingContentState(roomId: RoomId = A_ROOM_ID) = ContentState.Loading(roomId.toRoomIdOrAlias())
|
||||
|
||||
fun aLoadedContentState(
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String? = "Element X android",
|
||||
|
|
@ -138,19 +162,23 @@ fun aLoadedContentState(
|
|||
)
|
||||
|
||||
fun aJoinRoomState(
|
||||
roomIdOrAlias: RoomIdOrAlias = A_ROOM_ALIAS.toRoomIdOrAlias(),
|
||||
contentState: ContentState = aLoadedContentState(),
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
forgetAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
cancelKnockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
knockMessage: String = "",
|
||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||
) = JoinRoomState(
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
contentState = contentState,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
joinAction = joinAction,
|
||||
knockAction = knockAction,
|
||||
cancelKnockAction = cancelKnockAction,
|
||||
forgetAction = forgetAction,
|
||||
applicationName = "AppName",
|
||||
knockMessage = knockMessage,
|
||||
eventSink = eventSink
|
||||
|
|
@ -160,10 +188,12 @@ internal fun anInviteSender(
|
|||
userId: UserId = UserId("@bob:domain"),
|
||||
displayName: String = "Bob",
|
||||
avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender),
|
||||
membershipChangeReason: String? = null,
|
||||
) = InviteSender(
|
||||
userId = userId,
|
||||
displayName = displayName,
|
||||
avatarData = avatarData,
|
||||
membershipChangeReason = membershipChangeReason,
|
||||
)
|
||||
|
||||
private val A_ROOM_ID = RoomId("!exa:matrix.org")
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -31,7 +32,6 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
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.unit.dp
|
||||
|
|
@ -46,7 +46,8 @@ import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubti
|
|||
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.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.LightGradientBackground
|
||||
import io.element.android.libraries.designsystem.components.Announcement
|
||||
import io.element.android.libraries.designsystem.components.AnnouncementType
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
|
|
@ -54,16 +55,17 @@ 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.SuperButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
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.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
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.room.RoomType
|
||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
|
|
@ -73,30 +75,33 @@ fun JoinRoomView(
|
|||
onBackClick: () -> Unit,
|
||||
onJoinSuccess: () -> Unit,
|
||||
onKnockSuccess: () -> Unit,
|
||||
onForgetSuccess: () -> Unit,
|
||||
onCancelKnockSuccess: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
LightGradientBackground()
|
||||
HeaderFooterPage(
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
paddingValues = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 32.dp
|
||||
),
|
||||
topBar = {
|
||||
JoinRoomTopBar(contentState = state.contentState, onBackClick = onBackClick)
|
||||
},
|
||||
content = {
|
||||
JoinRoomContent(
|
||||
roomIdOrAlias = state.roomIdOrAlias,
|
||||
contentState = state.contentState,
|
||||
applicationName = state.applicationName,
|
||||
knockMessage = state.knockMessage,
|
||||
onKnockMessageUpdate = { state.eventSink(JoinRoomEvents.UpdateKnockMessage(it)) },
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
JoinRoomFooter(
|
||||
state = state,
|
||||
joinAuthorisationStatus = state.joinAuthorisationStatus,
|
||||
onAcceptInvite = {
|
||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||
},
|
||||
|
|
@ -112,31 +117,55 @@ fun JoinRoomView(
|
|||
onCancelKnock = {
|
||||
state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = true))
|
||||
},
|
||||
onRetry = {
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
onForgetRoom = {
|
||||
state.eventSink(JoinRoomEvents.ForgetRoom)
|
||||
},
|
||||
onGoBack = onBackClick,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (state.contentState is ContentState.Failure) {
|
||||
RetryDialog(
|
||||
title = stringResource(R.string.screen_join_room_loading_alert_title),
|
||||
content = stringResource(CommonStrings.error_network_or_server_issue),
|
||||
onRetry = { state.eventSink(JoinRoomEvents.RetryFetchingContent) },
|
||||
onDismiss = {
|
||||
state.eventSink(JoinRoomEvents.DismissContent)
|
||||
onBackClick()
|
||||
}
|
||||
)
|
||||
}
|
||||
// This particular error is shown directly in the footer
|
||||
if (!state.isJoinActionUnauthorized) {
|
||||
AsyncActionView(
|
||||
async = state.joinAction,
|
||||
errorTitle = { stringResource(CommonStrings.common_something_went_wrong) },
|
||||
errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) },
|
||||
onSuccess = { onJoinSuccess() },
|
||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||
)
|
||||
}
|
||||
AsyncActionView(
|
||||
async = state.joinAction,
|
||||
onSuccess = { onJoinSuccess() },
|
||||
async = state.knockAction,
|
||||
errorTitle = { stringResource(CommonStrings.common_something_went_wrong) },
|
||||
errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) },
|
||||
onSuccess = { onKnockSuccess() },
|
||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||
)
|
||||
AsyncActionView(
|
||||
async = state.knockAction,
|
||||
onSuccess = { onKnockSuccess() },
|
||||
async = state.forgetAction,
|
||||
errorTitle = { stringResource(CommonStrings.common_something_went_wrong) },
|
||||
errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) },
|
||||
onSuccess = { onForgetSuccess() },
|
||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||
)
|
||||
AsyncActionView(
|
||||
async = state.cancelKnockAction,
|
||||
onSuccess = { onCancelKnockSuccess() },
|
||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) },
|
||||
errorMessage = {
|
||||
stringResource(CommonStrings.error_unknown)
|
||||
},
|
||||
errorTitle = { stringResource(CommonStrings.common_something_went_wrong) },
|
||||
errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) },
|
||||
confirmationDialog = {
|
||||
ConfirmationDialog(
|
||||
content = stringResource(R.string.screen_join_room_cancel_knock_alert_description),
|
||||
|
|
@ -152,13 +181,13 @@ fun JoinRoomView(
|
|||
|
||||
@Composable
|
||||
private fun JoinRoomFooter(
|
||||
state: JoinRoomState,
|
||||
joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||
onAcceptInvite: () -> Unit,
|
||||
onDeclineInvite: () -> Unit,
|
||||
onJoinRoom: () -> Unit,
|
||||
onKnockRoom: () -> Unit,
|
||||
onCancelKnock: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
onForgetRoom: () -> Unit,
|
||||
onGoBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -167,79 +196,170 @@ private fun JoinRoomFooter(
|
|||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
if (state.contentState is ContentState.Failure) {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_retry),
|
||||
onClick = onRetry,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
} else if (state.contentState is ContentState.Loaded && state.contentState.roomType == RoomType.Space) {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_go_back),
|
||||
onClick = onGoBack,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
} else {
|
||||
val joinAuthorisationStatus = state.joinAuthorisationStatus
|
||||
when (joinAuthorisationStatus) {
|
||||
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 -> {
|
||||
when (joinAuthorisationStatus) {
|
||||
is JoinAuthorisationStatus.IsInvited -> {
|
||||
ButtonRowMolecule(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||
OutlinedButton(
|
||||
text = stringResource(R.string.screen_join_room_cancel_knock_action),
|
||||
onClick = onCancelKnock,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
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.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 -> {
|
||||
SuperButton(
|
||||
onClick = onKnockRoom,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
buttonSize = ButtonSize.Large,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_join_room_knock_action),
|
||||
)
|
||||
}
|
||||
}
|
||||
JoinAuthorisationStatus.IsKnocked -> {
|
||||
OutlinedButton(
|
||||
text = stringResource(R.string.screen_join_room_cancel_knock_action),
|
||||
onClick = onCancelKnock,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
JoinAuthorisationStatus.NeedInvite -> {
|
||||
Announcement(
|
||||
title = stringResource(R.string.screen_join_room_invite_required_message),
|
||||
description = null,
|
||||
type = AnnouncementType.Informative(isCritical = false),
|
||||
)
|
||||
}
|
||||
is JoinAuthorisationStatus.IsBanned -> JoinBannedFooter(joinAuthorisationStatus, onForgetRoom)
|
||||
JoinAuthorisationStatus.Unknown -> JoinRestrictedFooter(onJoinRoom)
|
||||
JoinAuthorisationStatus.Restricted -> JoinRestrictedFooter(onJoinRoom)
|
||||
JoinAuthorisationStatus.Unauthorized -> JoinUnauthorizedFooter(onGoBack)
|
||||
is JoinAuthorisationStatus.IsSpace -> UnsupportedSpaceFooter(joinAuthorisationStatus.applicationName, onGoBack)
|
||||
JoinAuthorisationStatus.None -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinRoomContent(
|
||||
contentState: ContentState,
|
||||
private fun JoinUnauthorizedFooter(
|
||||
onOkClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Announcement(
|
||||
title = stringResource(R.string.screen_join_room_fail_message),
|
||||
description = stringResource(R.string.screen_join_room_fail_reason),
|
||||
type = AnnouncementType.Informative(isCritical = true),
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_ok),
|
||||
onClick = onOkClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinBannedFooter(
|
||||
status: JoinAuthorisationStatus.IsBanned,
|
||||
onForgetRoom: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
val banReason = status.banSender?.membershipChangeReason?.let {
|
||||
stringResource(R.string.screen_join_room_ban_reason, it)
|
||||
}
|
||||
val title = if (status.banSender != null) {
|
||||
stringResource(R.string.screen_join_room_ban_by_message, status.banSender.displayName)
|
||||
} else {
|
||||
stringResource(R.string.screen_join_room_ban_message)
|
||||
}
|
||||
Announcement(
|
||||
title = title,
|
||||
description = banReason,
|
||||
type = AnnouncementType.Informative(isCritical = true),
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Button(
|
||||
text = stringResource(R.string.screen_join_room_forget_action),
|
||||
onClick = onForgetRoom,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinRestrictedFooter(
|
||||
onJoinRoom: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Announcement(
|
||||
title = stringResource(R.string.screen_join_room_join_restricted_message),
|
||||
description = null,
|
||||
type = AnnouncementType.Informative(),
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
SuperButton(
|
||||
onClick = onJoinRoom,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
buttonSize = ButtonSize.Large,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_join_room_join_action),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnsupportedSpaceFooter(
|
||||
applicationName: String,
|
||||
onGoBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Announcement(
|
||||
title = stringResource(R.string.screen_join_room_space_not_supported_title),
|
||||
description = stringResource(R.string.screen_join_room_space_not_supported_description, applicationName),
|
||||
type = AnnouncementType.Informative(),
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_ok),
|
||||
onClick = onGoBack,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinRoomContent(
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
contentState: ContentState,
|
||||
knockMessage: String,
|
||||
onKnockMessageUpdate: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -255,67 +375,67 @@ private fun JoinRoomContent(
|
|||
DefaultLoadedContent(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
contentState = contentState,
|
||||
applicationName = applicationName,
|
||||
knockMessage = knockMessage,
|
||||
onKnockMessageUpdate = onKnockMessageUpdate
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ContentState.UnknownRoom -> {
|
||||
RoomPreviewOrganism(
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
||||
},
|
||||
subtitle = {
|
||||
RoomPreviewSubtitleAtom(stringResource(R.string.screen_join_room_subtitle_no_preview))
|
||||
},
|
||||
)
|
||||
}
|
||||
is ContentState.Loading -> {
|
||||
RoomPreviewOrganism(
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
||||
},
|
||||
subtitle = {
|
||||
PlaceholderAtom(width = 140.dp, height = 20.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
is ContentState.Failure -> {
|
||||
RoomPreviewOrganism(
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
when (contentState.roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
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,
|
||||
color = ElementTheme.colors.textCriticalPrimary,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
is ContentState.UnknownRoom -> UnknownRoomContent()
|
||||
is ContentState.Loading -> IncompleteContent(roomIdOrAlias, isLoading = true)
|
||||
is ContentState.Dismissing -> IncompleteContent(roomIdOrAlias, isLoading = false)
|
||||
is ContentState.Failure -> IncompleteContent(roomIdOrAlias, isLoading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnknownRoomContent(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
Spacer(modifier = Modifier.size(AvatarSize.RoomHeader.dp))
|
||||
},
|
||||
title = {
|
||||
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
||||
},
|
||||
subtitle = {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IncompleteContent(
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
isLoading: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
RoomPreviewSubtitleAtom(roomIdOrAlias.identifier)
|
||||
}
|
||||
is RoomIdOrAlias.Id -> {
|
||||
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
||||
}
|
||||
}
|
||||
},
|
||||
subtitle = {
|
||||
if (isLoading) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
||||
BoxWithConstraints(
|
||||
|
|
@ -336,7 +456,6 @@ private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun DefaultLoadedContent(
|
||||
contentState: ContentState.Loaded,
|
||||
applicationName: String,
|
||||
knockMessage: String,
|
||||
onKnockMessageUpdate: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -373,21 +492,7 @@ private fun DefaultLoadedContent(
|
|||
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 = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.screen_join_room_space_not_supported_description, applicationName),
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
} else if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||
if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
val supportingText = if (knockMessage.isNotEmpty()) {
|
||||
"${knockMessage.length}/$MAX_KNOCK_MESSAGE_LENGTH"
|
||||
|
|
@ -461,6 +566,7 @@ internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class)
|
|||
onBackClick = { },
|
||||
onJoinSuccess = { },
|
||||
onKnockSuccess = { },
|
||||
onForgetSuccess = { },
|
||||
onCancelKnockSuccess = { },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector 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.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 ForgetRoom {
|
||||
suspend operator fun invoke(roomId: RoomId): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultForgetRoom @Inject constructor(private val client: MatrixClient) : ForgetRoom {
|
||||
override suspend fun invoke(roomId: RoomId): Result<Unit> {
|
||||
return client
|
||||
.getPendingRoom(roomId)
|
||||
?.forget()
|
||||
?: Result.failure(IllegalStateException("No pending room found"))
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ object JoinRoomModule {
|
|||
joinRoom: JoinRoom,
|
||||
knockRoom: KnockRoom,
|
||||
cancelKnockRoom: CancelKnockRoom,
|
||||
forgetRoom: ForgetRoom,
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
buildMeta: BuildMeta,
|
||||
): JoinRoomPresenter.Factory {
|
||||
|
|
@ -52,6 +53,7 @@ object JoinRoomModule {
|
|||
matrixClient = client,
|
||||
joinRoom = joinRoom,
|
||||
knockRoom = knockRoom,
|
||||
forgetRoom = forgetRoom,
|
||||
cancelKnockRoom = cancelKnockRoom,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
buildMeta = buildMeta,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
<string name="screen_join_room_knock_message_description">"Message (optional)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"You will receive an invite to join the room if your request is accepted."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Request to join sent"</string>
|
||||
<string name="screen_join_room_loading_alert_message">"We could not display the room preview. This may be due to network or server issues."</string>
|
||||
<string name="screen_join_room_loading_alert_title">"We couldn’t display this room preview"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s does not support spaces yet. You can access spaces on web."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Spaces are not supported yet"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved."</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector 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.joinroom.impl
|
||||
|
||||
import io.element.android.features.joinroom.impl.di.ForgetRoom
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeForgetRoom(
|
||||
var lambda: (RoomId) -> Result<Unit> = { Result.success(Unit) }
|
||||
) : ForgetRoom {
|
||||
override suspend fun invoke(roomId: RoomId) = simulateLongTask {
|
||||
lambda(roomId)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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.ForgetRoom
|
||||
import io.element.android.features.joinroom.impl.di.KnockRoom
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
|
@ -24,9 +25,11 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
|
|
@ -34,6 +37,7 @@ import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
|||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.aRoomPreviewInfo
|
||||
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
|
||||
|
|
@ -49,6 +53,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import java.util.Optional
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class JoinRoomPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
|
@ -58,12 +63,10 @@ class JoinRoomPresenterTest {
|
|||
val presenter = createJoinRoomPresenter()
|
||||
presenter.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias()))
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown)
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading)
|
||||
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
|
||||
assertThat(state.cancelKnockAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
assertThat(state.knockAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
assertThat(state.applicationName).isEqualTo("AppName")
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
|
@ -226,9 +229,52 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is joined with unauthorized error, then the authorisation status is unauthorized`() = runTest {
|
||||
val roomDescription = aRoomDescription()
|
||||
val presenter = createJoinRoomPresenter(
|
||||
roomDescription = Optional.of(roomDescription),
|
||||
joinRoomLambda = { _, _, _ ->
|
||||
Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden"))
|
||||
},
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin))
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is banned, then join authorization is equal to IsBanned`() = runTest {
|
||||
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.BANNED, joinRule = JoinRule.Public)
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
getRoomSummaryFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomSummary))
|
||||
}
|
||||
}
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isInstanceOf(JoinAuthorisationStatus.IsBanned::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
|
||||
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
|
||||
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.LEFT, joinRule = JoinRule.Public)
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
getRoomSummaryFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomSummary))
|
||||
|
|
@ -246,8 +292,8 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is left and not public then join authorization is equal to unknown`() = runTest {
|
||||
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.LEFT, isPublic = false)
|
||||
fun `present - when room is left and join rule null then join authorization is equal to Unknown`() = runTest {
|
||||
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.LEFT, joinRule = null)
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
getRoomSummaryFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomSummary))
|
||||
|
|
@ -327,6 +373,20 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room preview join rule is Private then join authorization is equal to NeedInvite`() = runTest {
|
||||
val roomDescription = aRoomDescription(joinRule = RoomDescription.JoinRule.UNKNOWN)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
roomDescription = Optional.of(roomDescription)
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - emit knock room event`() = runTest {
|
||||
val knockMessage = "Knock message"
|
||||
|
|
@ -405,24 +465,58 @@ class JoinRoomPresenterTest {
|
|||
.with(value(A_ROOM_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - emit forget room event`() = runTest {
|
||||
val forgetRoomSuccess = lambdaRecorder { _: RoomId ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val forgetRoomFailure = lambdaRecorder { _: RoomId ->
|
||||
Result.failure<Unit>(RuntimeException("Failed to forget room"))
|
||||
}
|
||||
val fakeForgetRoom = FakeForgetRoom(forgetRoomSuccess)
|
||||
val presenter = createJoinRoomPresenter(forgetRoom = fakeForgetRoom)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(JoinRoomEvents.ForgetRoom)
|
||||
}
|
||||
|
||||
assertThat(awaitItem().forgetAction).isEqualTo(AsyncAction.Loading)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.forgetAction).isEqualTo(AsyncAction.Success(Unit))
|
||||
fakeForgetRoom.lambda = forgetRoomFailure
|
||||
state.eventSink(JoinRoomEvents.ForgetRoom)
|
||||
}
|
||||
|
||||
assertThat(awaitItem().forgetAction).isEqualTo(AsyncAction.Loading)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.forgetAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
}
|
||||
}
|
||||
assert(forgetRoomFailure)
|
||||
.isCalledOnce()
|
||||
.with(value(A_ROOM_ID))
|
||||
assert(forgetRoomSuccess)
|
||||
.isCalledOnce()
|
||||
.with(value(A_ROOM_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.success(
|
||||
RoomPreviewInfo(
|
||||
aRoomPreviewInfo(
|
||||
roomId = A_ROOM_ID,
|
||||
canonicalAlias = RoomAlias("#alias:matrix.org"),
|
||||
name = "Room name",
|
||||
topic = "Room topic",
|
||||
avatarUrl = "avatarUrl",
|
||||
numberOfJoinedMembers = 2,
|
||||
roomType = RoomType.Room,
|
||||
isSpace = false,
|
||||
isHistoryWorldReadable = false,
|
||||
isJoined = false,
|
||||
isInvited = false,
|
||||
isPublic = true,
|
||||
canKnock = false,
|
||||
joinRule = JoinRule.Public,
|
||||
currentUserMembership = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -450,6 +544,86 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded as Private `() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreviewInfo(joinRule = JoinRule.Private)
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.NeedInvite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded as KnockRestricted `() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreviewInfo(joinRule = JoinRule.KnockRestricted(emptyList()))
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.CanKnock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded as Restricted `() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreviewInfo(joinRule = JoinRule.Restricted(emptyList()))
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Restricted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded as Space `() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreviewInfo(isSpace = true)
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsSpace("AppName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
|
|
@ -464,35 +638,27 @@ class JoinRoomPresenterTest {
|
|||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(
|
||||
roomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias(),
|
||||
error = AN_EXCEPTION
|
||||
)
|
||||
ContentState.Failure(error = AN_EXCEPTION)
|
||||
)
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias())
|
||||
)
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(
|
||||
roomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias(),
|
||||
error = AN_EXCEPTION
|
||||
)
|
||||
ContentState.Failure(error = AN_EXCEPTION)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||
fun `present - when room is not known RoomPreview is loaded with error Forbidden`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.failure(Exception("403"))
|
||||
Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden"))
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
|
|
@ -501,11 +667,7 @@ class JoinRoomPresenterTest {
|
|||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.UnknownRoom(
|
||||
roomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias(),
|
||||
)
|
||||
)
|
||||
assertThat(state.contentState).isEqualTo(ContentState.UnknownRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -521,6 +683,7 @@ class JoinRoomPresenterTest {
|
|||
},
|
||||
knockRoom: KnockRoom = FakeKnockRoom(),
|
||||
cancelKnockRoom: CancelKnockRoom = FakeCancelKnockRoom(),
|
||||
forgetRoom: ForgetRoom = FakeForgetRoom(),
|
||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
|
||||
): JoinRoomPresenter {
|
||||
|
|
@ -534,6 +697,7 @@ class JoinRoomPresenterTest {
|
|||
joinRoom = FakeJoinRoom(joinRoomLambda),
|
||||
knockRoom = knockRoom,
|
||||
cancelKnockRoom = cancelKnockRoom,
|
||||
forgetRoom = forgetRoom,
|
||||
buildMeta = buildMeta,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
||||
)
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class JoinRoomViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Go back when a space is displayed invokes the expected callback`() {
|
||||
fun `clicking on ok when a space is displayed invokes the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setJoinRoomView(
|
||||
|
|
@ -188,9 +188,38 @@ class JoinRoomViewTest {
|
|||
),
|
||||
onBackClick = it
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_go_back)
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on ok when user is unauthorized the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(),
|
||||
joinAction = AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackClick = it
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on forget when user is banned invokes the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(null)),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_join_room_forget_action)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.ForgetRoom)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
|
||||
|
|
@ -199,6 +228,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinR
|
|||
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
onKnockSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
onCancelKnockSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
onForgetSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
JoinRoomView(
|
||||
|
|
@ -206,7 +236,8 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinR
|
|||
onBackClick = onBackClick,
|
||||
onJoinSuccess = onJoinSuccess,
|
||||
onKnockSuccess = onKnockSuccess,
|
||||
onCancelKnockSuccess = onCancelKnockSuccess
|
||||
onForgetSuccess = onForgetSuccess,
|
||||
onCancelKnockSuccess = onCancelKnockSuccess,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue