diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt index 33bbe68c63..48d30eb763 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt @@ -10,8 +10,9 @@ package io.element.android.features.joinroom.impl sealed interface JoinRoomEvents { data object RetryFetchingContent : JoinRoomEvents data object JoinRoom : JoinRoomEvents - data object KnockRoom : JoinRoomEvents - data object ClearError : JoinRoomEvents + data class KnockRoom(val message: String) : JoinRoomEvents + data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvents + data object ClearActionStates : JoinRoomEvents data object AcceptInvite : JoinRoomEvents data object DeclineInvite : JoinRoomEvents } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomNode.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomNode.kt index c0835bfca2..c6ebb1b8b5 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomNode.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomNode.kt @@ -43,7 +43,7 @@ class JoinRoomNode @AssistedInject constructor( state = state, onBackClick = ::navigateUp, onJoinSuccess = ::navigateUp, - onKnockSuccess = ::navigateUp, + onKnockSuccess = { }, modifier = modifier ) acceptDeclineInviteView.Render( diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 8e0fa9193e..47821ef12e 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -75,6 +75,7 @@ class JoinRoomPresenter @AssistedInject constructor( val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty()) val joinAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val knockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + val cancelKnockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val contentState by produceState( initialValue = ContentState.Loading(roomIdOrAlias), key1 = roomInfo, @@ -110,7 +111,7 @@ class JoinRoomPresenter @AssistedInject constructor( fun handleEvents(event: JoinRoomEvents) { when (event) { JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction) - JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction) + is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, event.message) JoinRoomEvents.AcceptInvite -> { val inviteData = contentState.toInviteData() ?: return acceptDeclineInviteState.eventSink( @@ -123,12 +124,14 @@ class JoinRoomPresenter @AssistedInject constructor( AcceptDeclineInviteEvents.DeclineInvite(inviteData) ) } + is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction) JoinRoomEvents.RetryFetchingContent -> { retryCount++ } - JoinRoomEvents.ClearError -> { + JoinRoomEvents.ClearActionStates -> { knockAction.value = AsyncAction.Uninitialized joinAction.value = AsyncAction.Uninitialized + cancelKnockAction.value = AsyncAction.Uninitialized } } } @@ -138,6 +141,7 @@ class JoinRoomPresenter @AssistedInject constructor( acceptDeclineInviteState = acceptDeclineInviteState, joinAction = joinAction.value, knockAction = knockAction.value, + cancelKnockAction = cancelKnockAction.value, applicationName = buildMeta.applicationName, eventSink = ::handleEvents ) @@ -153,11 +157,23 @@ class JoinRoomPresenter @AssistedInject constructor( } } - private fun CoroutineScope.knockRoom(knockAction: MutableState>) = launch { + private fun CoroutineScope.knockRoom(knockAction: MutableState>, message: String) = launch { knockAction.runUpdatingState { knockRoom(roomId) } } + + private fun CoroutineScope.cancelKnockRoom(requiresConfirmation: Boolean, cancelKnockAction: MutableState>) = launch { + if (requiresConfirmation) { + cancelKnockAction.value = AsyncAction.ConfirmingNoParams + } else { + matrixClient.getPendingRoom(roomId)?.use { room -> + cancelKnockAction.runUpdatingState { + room.leave() + } + } + } + } } private fun RoomPreview.toContentState(): ContentState { @@ -206,7 +222,7 @@ internal fun MatrixRoomInfo.toContentState(): ContentState { name = name, topic = topic, alias = canonicalAlias, - numberOfMembers = activeMembersCount.toLong(), + numberOfMembers = activeMembersCount, isDm = isDm, roomType = if (isSpace) RoomType.Space else RoomType.Room, roomAvatarUrl = avatarUrl, @@ -214,6 +230,7 @@ internal fun MatrixRoomInfo.toContentState(): ContentState { currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited( inviteSender = inviter?.toInviteSender() ) + currentUserMembership == CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked isPublic -> JoinAuthorisationStatus.CanJoin else -> JoinAuthorisationStatus.Unknown } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index c37867fc46..cdf93484a9 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -24,6 +24,7 @@ data class JoinRoomState( val acceptDeclineInviteState: AcceptDeclineInviteState, val joinAction: AsyncAction, val knockAction: AsyncAction, + val cancelKnockAction: AsyncAction, val applicationName: String, val eventSink: (JoinRoomEvents) -> Unit ) { @@ -68,6 +69,7 @@ sealed interface ContentState { sealed interface JoinAuthorisationStatus { data class IsInvited(val inviteSender: InviteSender?) : JoinAuthorisationStatus + data object IsKnocked : JoinAuthorisationStatus data object CanKnock : JoinAuthorisationStatus data object CanJoin : JoinAuthorisationStatus data object Unknown : JoinAuthorisationStatus diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt index a560b5fea8..ab59b7ae2f 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt @@ -81,6 +81,12 @@ open class JoinRoomStateProvider : PreviewParameterProvider { isDm = true, ) ), + aJoinRoomState( + contentState = aLoadedContentState( + name = "A knocked Room", + joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked + ) + ) ) } @@ -124,12 +130,14 @@ fun aJoinRoomState( acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), joinAction: AsyncAction = AsyncAction.Uninitialized, knockAction: AsyncAction = AsyncAction.Uninitialized, + cancelKnockAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (JoinRoomEvents) -> Unit = {} ) = JoinRoomState( contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, joinAction = joinAction, knockAction = knockAction, + cancelKnockAction = cancelKnockAction, applicationName = "AppName", eventSink = eventSink ) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index 6d86227e07..8acfc17b4a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -7,31 +7,50 @@ package io.element.android.features.joinroom.impl +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip 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 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.RoomPreviewDescriptionAtom 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.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.organisms.RoomPreviewOrganism 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.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.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.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.TopAppBar import io.element.android.libraries.matrix.api.core.RoomIdOrAlias @@ -64,17 +85,20 @@ fun JoinRoomView( Box( modifier = modifier.fillMaxSize(), ) { + var knockMessage by rememberSaveable { mutableStateOf("") } LightGradientBackground() HeaderFooterPage( containerColor = Color.Transparent, paddingValues = PaddingValues(16.dp), topBar = { - JoinRoomTopBar(onBackClick = onBackClick) + JoinRoomTopBar(contentState = state.contentState, onBackClick = onBackClick) }, content = { JoinRoomContent( contentState = state.contentState, applicationName = state.applicationName, + knockMessage = knockMessage, + onKnockMessageUpdate = { knockMessage = it }, ) }, footer = { @@ -90,7 +114,10 @@ fun JoinRoomView( state.eventSink(JoinRoomEvents.JoinRoom) }, onKnockRoom = { - state.eventSink(JoinRoomEvents.KnockRoom) + state.eventSink(JoinRoomEvents.KnockRoom(knockMessage)) + }, + onCancelKnock = { + state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = true)) }, onRetry = { state.eventSink(JoinRoomEvents.RetryFetchingContent) @@ -103,12 +130,27 @@ fun JoinRoomView( AsyncActionView( async = state.joinAction, onSuccess = { onJoinSuccess() }, - onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) }, + onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) }, ) AsyncActionView( async = state.knockAction, 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, onJoinRoom: () -> Unit, onKnockRoom: () -> Unit, + onCancelKnock: () -> Unit, onRetry: () -> Unit, onGoBack: () -> Unit, modifier: Modifier = Modifier, ) { - 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(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) { + Box(modifier = modifier.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 -> { 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, + text = stringResource(R.string.screen_join_room_cancel_knock_action), + onClick = onCancelKnock, + modifier = Modifier.fillMaxWidth(), + size = ButtonSize.Large, ) } + 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( contentState: ContentState, applicationName: String, + knockMessage: String, + onKnockMessageUpdate: (String) -> Unit, modifier: Modifier = Modifier, ) { - when (contentState) { - is ContentState.Loaded -> { - RoomPreviewOrganism( - modifier = modifier, - avatar = { - Avatar(contentState.avatarData(AvatarSize.RoomHeader)) - }, - title = { - if (contentState.name != null) { - RoomPreviewTitleAtom( - title = contentState.name, + Box(modifier = modifier) { + when (contentState) { + is ContentState.Loaded -> { + when (contentState.joinAuthorisationStatus) { + is JoinAuthorisationStatus.IsKnocked -> { + IsKnockedLoadedContent() + } + else -> { + DefaultLoadedContent( + modifier = Modifier.verticalScroll(rememberScrollState()), + 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 -> { - RoomPreviewOrganism( - modifier = modifier, - 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( - modifier = modifier, - 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( - modifier = modifier, - avatar = { - PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp) - }, - title = { - when (contentState.roomIdOrAlias) { - is RoomIdOrAlias.Alias -> { - RoomPreviewTitleAtom(contentState.roomIdOrAlias.identifier) + } + 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) + } } - is RoomIdOrAlias.Id -> { - PlaceholderAtom(width = 200.dp, height = 22.dp) - } - } - }, - subtitle = { - Text( - text = stringResource(id = CommonStrings.error_unknown), - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.error, - ) - }, - ) + }, + subtitle = { + 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) @Composable private fun JoinRoomTopBar( + contentState: ContentState, onBackClick: () -> Unit, ) { TopAppBar( navigationIcon = { 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 + ) + } + } + }, ) } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt index b2fc94cdd6..9056687b37 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt @@ -19,5 +19,9 @@ interface KnockRoom { @ContributesBinding(SessionScope::class) 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 { + return client + .knockRoom(roomId) + .map { } + } } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 0a323122ac..9d9574a0f3 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -214,7 +214,7 @@ class JoinRoomPresenterTest { } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - state.eventSink(JoinRoomEvents.ClearError) + state.eventSink(JoinRoomEvents.ClearActionStates) } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized) diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt index a120b74e9e..dc93bac2ea 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt @@ -79,7 +79,7 @@ class JoinRoomViewTest { ), ) rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(JoinRoomEvents.ClearError) + eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) } @Test @@ -93,7 +93,7 @@ class JoinRoomViewTest { ), ) rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(JoinRoomEvents.ClearError) + eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) } @Test