design (join room) : update design of join room screen
This commit is contained in:
parent
e79281a78a
commit
c44bf89ed5
12 changed files with 198 additions and 148 deletions
|
|
@ -101,7 +101,6 @@ sealed interface ContentState {
|
|||
|
||||
sealed interface JoinAuthorisationStatus {
|
||||
data object None : JoinAuthorisationStatus
|
||||
data class IsSpace(val applicationName: String) : JoinAuthorisationStatus
|
||||
data class IsInvited(val inviteData: InviteData, val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
||||
data class IsBanned(val banSender: InviteSender?, val reason: String?) : JoinAuthorisationStatus
|
||||
data object IsKnocked : JoinAuthorisationStatus
|
||||
|
|
|
|||
|
|
@ -7,21 +7,20 @@
|
|||
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
@ -38,6 +37,7 @@ 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.features.invite.api.InviteData
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||
|
|
@ -65,14 +65,21 @@ 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.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
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.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.placeholderBackground
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceInfoRow
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceMembersView
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun JoinRoomView(
|
||||
|
|
@ -92,7 +99,7 @@ fun JoinRoomView(
|
|||
containerColor = Color.Transparent,
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 32.dp
|
||||
vertical = 24.dp
|
||||
),
|
||||
topBar = {
|
||||
JoinRoomTopBar(
|
||||
|
|
@ -220,12 +227,14 @@ private fun JoinRoomFooter(
|
|||
onClick = { onDeclineInvite(joinAuthorisationStatus.inviteData, false) },
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Close())
|
||||
)
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = { onAcceptInvite(joinAuthorisationStatus.inviteData) },
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Check())
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
|
@ -278,7 +287,6 @@ private fun JoinRoomFooter(
|
|||
JoinAuthorisationStatus.Unknown -> JoinRestrictedFooter(onJoinRoom)
|
||||
JoinAuthorisationStatus.Restricted -> JoinRestrictedFooter(onJoinRoom)
|
||||
JoinAuthorisationStatus.Unauthorized -> JoinUnauthorizedFooter(onGoBack)
|
||||
is JoinAuthorisationStatus.IsSpace -> UnsupportedSpaceFooter(joinAuthorisationStatus.applicationName, onGoBack)
|
||||
JoinAuthorisationStatus.None -> Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -397,19 +405,40 @@ private fun JoinRoomContent(
|
|||
IsKnockedLoadedContent()
|
||||
}
|
||||
else -> {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
||||
if (inviteSender != null) {
|
||||
InviteSenderView(inviteSender = inviteSender, hideAvatarImage = hideAvatarsImages)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
DefaultLoadedContent(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
contentState = contentState,
|
||||
knockMessage = knockMessage,
|
||||
hideAvatarImage = hideAvatarsImages,
|
||||
onKnockMessageUpdate = onKnockMessageUpdate
|
||||
)
|
||||
when (contentState.joinAuthorisationStatus) {
|
||||
is JoinAuthorisationStatus.IsInvited -> {
|
||||
val inviteSender = contentState.joinAuthorisationStatus.inviteSender
|
||||
if (inviteSender != null) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InvitedByView(inviteSender, hideAvatarsImages)
|
||||
}
|
||||
}
|
||||
is JoinAuthorisationStatus.CanKnock -> {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
val supportingText = if (knockMessage.isNotEmpty()) {
|
||||
"${knockMessage.length}/$MAX_KNOCK_MESSAGE_LENGTH"
|
||||
} else {
|
||||
stringResource(R.string.screen_join_room_knock_message_description)
|
||||
}
|
||||
TextField(
|
||||
value = knockMessage,
|
||||
onValueChange = onKnockMessageUpdate,
|
||||
maxLines = 3,
|
||||
minLines = 3,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
supportingText = supportingText
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -422,6 +451,45 @@ private fun JoinRoomContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvitedByView(
|
||||
sender: InviteSender,
|
||||
hideAvatarImage: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Invited by",
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Avatar(
|
||||
avatarData = sender.avatarData,
|
||||
avatarType = AvatarType.User,
|
||||
hideImage = hideAvatarImage,
|
||||
forcedAvatarSize = AvatarSize.RoomPreviewInviter.dp
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = sender.displayName,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = sender.userId.value,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnknownRoomContent(
|
||||
modifier: Modifier = Modifier
|
||||
|
|
@ -429,7 +497,21 @@ private fun UnknownRoomContent(
|
|||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
Spacer(modifier = Modifier.size(AvatarSize.RoomHeader.dp))
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(AvatarSize.RoomPreviewHeader.dp)
|
||||
.background(
|
||||
color = ElementTheme.colors.placeholderBackground,
|
||||
shape = CircleShape
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
tint = ElementTheme.colors.iconPrimary,
|
||||
imageVector = CompoundIcons.VisibilityOff(),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
||||
|
|
@ -448,7 +530,7 @@ private fun IncompleteContent(
|
|||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
PlaceholderAtom(width = AvatarSize.RoomPreviewHeader.dp, height = AvatarSize.RoomPreviewHeader.dp)
|
||||
},
|
||||
title = {
|
||||
when (roomIdOrAlias) {
|
||||
|
|
@ -471,43 +553,32 @@ private fun IncompleteContent(
|
|||
|
||||
@Composable
|
||||
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.sizeIn(minHeight = maxHeight * 0.7f),
|
||||
iconStyle = BigIcon.Style.SuccessSolid,
|
||||
title = stringResource(R.string.screen_join_room_knock_sent_title),
|
||||
subTitle = stringResource(R.string.screen_join_room_knock_sent_description),
|
||||
)
|
||||
}
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = modifier.padding(horizontal = 8.dp),
|
||||
iconStyle = BigIcon.Style.SuccessSolid,
|
||||
title = stringResource(R.string.screen_join_room_knock_sent_title),
|
||||
subTitle = stringResource(R.string.screen_join_room_knock_sent_description),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DefaultLoadedContent(
|
||||
contentState: ContentState.Loaded,
|
||||
knockMessage: String,
|
||||
hideAvatarImage: Boolean,
|
||||
onKnockMessageUpdate: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
Avatar(
|
||||
contentState.avatarData(AvatarSize.RoomHeader),
|
||||
contentState.avatarData(AvatarSize.RoomPreviewHeader),
|
||||
hideImage = hideAvatarImage,
|
||||
avatarType = AvatarType.Room(),
|
||||
avatarType = if (contentState.isSpace) AvatarType.Space() else AvatarType.Room(),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
if (contentState.name != null) {
|
||||
RoomPreviewTitleAtom(
|
||||
title = contentState.name,
|
||||
)
|
||||
RoomPreviewTitleAtom(title = contentState.name)
|
||||
} else {
|
||||
RoomPreviewTitleAtom(
|
||||
title = stringResource(id = CommonStrings.common_no_room_name),
|
||||
|
|
@ -516,37 +587,32 @@ private fun DefaultLoadedContent(
|
|||
}
|
||||
},
|
||||
subtitle = {
|
||||
if (contentState.alias != null) {
|
||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||
}
|
||||
},
|
||||
description = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
||||
if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
val supportingText = if (knockMessage.isNotEmpty()) {
|
||||
"${knockMessage.length}/$MAX_KNOCK_MESSAGE_LENGTH"
|
||||
} else {
|
||||
stringResource(R.string.screen_join_room_knock_message_description)
|
||||
}
|
||||
TextField(
|
||||
value = knockMessage,
|
||||
onValueChange = onKnockMessageUpdate,
|
||||
maxLines = 3,
|
||||
minLines = 3,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
supportingText = supportingText
|
||||
when {
|
||||
contentState.isSpace -> {
|
||||
SpaceInfoRow(
|
||||
joinRule = contentState.joinRule ?: JoinRule.Public,
|
||||
numberOfRooms = contentState.childrenCount ?: 0,
|
||||
)
|
||||
}
|
||||
contentState.alias != null -> {
|
||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
description = {
|
||||
RoomPreviewDescriptionAtom(
|
||||
contentState.topic ?: "",
|
||||
maxLines = if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanJoin) Int.MAX_VALUE else 2
|
||||
)
|
||||
},
|
||||
memberCount = {
|
||||
if (contentState.showMemberCount) {
|
||||
MembersCountMolecule(memberCount = contentState.numberOfMembers?.toInt() ?: 0)
|
||||
val membersCount = contentState.numberOfMembers?.toInt() ?: 0
|
||||
if (contentState.isSpace) {
|
||||
SpaceMembersView(persistentListOf(), membersCount)
|
||||
} else {
|
||||
MembersCountMolecule(memberCount = membersCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -655,7 +655,10 @@ class JoinRoomPresenterTest {
|
|||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin,
|
||||
joinRule = JoinRule.Public,
|
||||
childrenCount = null,
|
||||
heroes = persistentListOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -724,7 +727,10 @@ class JoinRoomPresenterTest {
|
|||
),
|
||||
membershipChangeReason = null,
|
||||
),
|
||||
)
|
||||
),
|
||||
joinRule = JoinRule.Public,
|
||||
childrenCount = null,
|
||||
heroes = persistentListOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -789,7 +795,10 @@ class JoinRoomPresenterTest {
|
|||
membershipChangeReason = null,
|
||||
),
|
||||
reason = null,
|
||||
)
|
||||
),
|
||||
joinRule = JoinRule.Public,
|
||||
childrenCount = null,
|
||||
heroes = persistentListOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -842,7 +851,10 @@ class JoinRoomPresenterTest {
|
|||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked,
|
||||
joinRule = JoinRule.Public,
|
||||
childrenCount = null,
|
||||
heroes = persistentListOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -949,26 +961,6 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded as Space`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(isSpace = true))
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.UnsupportedSpace("AppName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue