feature (space) : extract SpaceRoomItemView

This commit is contained in:
ganfra 2025-09-05 16:07:37 +02:00 committed by Benoit Marty
parent f56ccdc786
commit d3a9c12ac6
3 changed files with 96 additions and 55 deletions

View file

@ -15,10 +15,10 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SpaceId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
import io.element.android.libraries.matrix.ui.components.SpaceHeaderView import io.element.android.libraries.matrix.ui.components.SpaceHeaderView
import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -55,12 +55,15 @@ fun HomeSpacesView(
state.spaceRooms.forEach { state.spaceRooms.forEach {
item(it.roomId) { item(it.roomId) {
val isInvitation = it.state == CurrentUserMembership.INVITED val isInvitation = it.state == CurrentUserMembership.INVITED
HomeSpaceItemView( SpaceRoomItemView(
spaceRoom = it, spaceRoom = it,
showUnreadIndicator = isInvitation && it.roomId !in state.seenSpaceInvites, showUnreadIndicator = isInvitation && it.roomId !in state.seenSpaceInvites,
hideAvatars = isInvitation && state.hideInvitesAvatar, hideAvatars = isInvitation && state.hideInvitesAvatar,
onClick = { onClick = {
onSpaceClick(it.roomId) onSpaceClick(it.roomId)
},
onLongClick = {
} }
) )
} }

View file

@ -28,4 +28,6 @@ data class SpaceRoom(
val state: CurrentUserMembership?, val state: CurrentUserMembership?,
val topic: String?, val topic: String?,
val worldReadable: Boolean, val worldReadable: Boolean,
) ) {
val isSpace = roomType == RoomType.Space
}

View file

@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.features.home.impl.spaces package io.element.android.libraries.matrix.ui.components
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@ -22,88 +22,66 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.ripple import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.unreadIndicator import io.element.android.libraries.designsystem.theme.unreadIndicator
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonPlurals
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun HomeSpaceItemView( fun SpaceRoomItemView(
spaceRoom: SpaceRoom, spaceRoom: SpaceRoom,
showUnreadIndicator: Boolean, showUnreadIndicator: Boolean,
hideAvatars: Boolean, hideAvatars: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
SpaceScaffoldRow( SpaceRoomItemScaffold(
modifier = modifier, modifier = modifier,
spaceRoom = spaceRoom, avatarData = spaceRoom.getAvatarData(AvatarSize.SpaceListItem),
onClick = onClick, isSpace = spaceRoom.isSpace,
hideAvatars = hideAvatars, hideAvatars = hideAvatars,
onLongClick = { }, onClick = onClick,
onLongClick = onLongClick,
) { ) {
NameAndIndicatorRow( NameAndIndicatorRow(
name = spaceRoom.name, name = spaceRoom.name,
showIndicator = showUnreadIndicator, showIndicator = showUnreadIndicator
) )
Spacer(modifier = Modifier.height(1.dp)) Spacer(modifier = Modifier.height(1.dp))
if (!spaceRoom.worldReadable) { SubtitleRow(
Row( visibilityIcon = spaceRoom.visibilityIcon(),
modifier = Modifier.fillMaxWidth(), subtitle = spaceRoom.subtitle()
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier
.size(16.dp)
.padding(end = 4.dp),
imageVector = CompoundIcons.LockSolid(),
contentDescription = null,
tint = ElementTheme.colors.iconTertiary,
)
Text(
modifier = Modifier.weight(1f),
style = ElementTheme.typography.fontBodyMdRegular,
text = stringResource(CommonStrings.common_private_space),
fontStyle = FontStyle.Italic.takeIf { spaceRoom.name == null },
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Spacer(modifier = Modifier.height(1.dp))
}
val spaceSummary = stringResource(
CommonStrings.screen_space_list_details,
pluralStringResource(CommonPlurals.common_rooms, spaceRoom.childrenCount, spaceRoom.childrenCount),
pluralStringResource(CommonPlurals.common_member_count, spaceRoom.numJoinedMembers, spaceRoom.numJoinedMembers),
) )
Spacer(modifier = Modifier.height(1.dp))
Text( Text(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
style = ElementTheme.typography.fontBodyMdRegular, style = ElementTheme.typography.fontBodyMdRegular,
text = spaceSummary, text = spaceRoom.info(),
fontStyle = FontStyle.Italic.takeIf { spaceRoom.name == null }, fontStyle = FontStyle.Italic.takeIf { spaceRoom.name == null },
color = ElementTheme.colors.textSecondary, color = ElementTheme.colors.textSecondary,
maxLines = 1, maxLines = 1,
@ -119,6 +97,37 @@ internal fun HomeSpaceItemView(
} }
} }
@Composable
private fun SubtitleRow(
visibilityIcon: ImageVector?,
subtitle: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
if (visibilityIcon != null) {
Icon(
modifier = Modifier
.size(16.dp)
.padding(end = 4.dp),
imageVector = visibilityIcon,
contentDescription = null,
tint = ElementTheme.colors.iconTertiary,
)
}
Text(
modifier = Modifier.weight(1f),
style = ElementTheme.typography.fontBodyMdRegular,
text = subtitle,
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable @Composable
private fun NameAndIndicatorRow( private fun NameAndIndicatorRow(
name: String?, name: String?,
@ -148,8 +157,9 @@ private fun NameAndIndicatorRow(
} }
@Composable @Composable
private fun SpaceScaffoldRow( private fun SpaceRoomItemScaffold(
spaceRoom: SpaceRoom, avatarData: AvatarData,
isSpace: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
hideAvatars: Boolean, hideAvatars: Boolean,
@ -173,8 +183,8 @@ private fun SpaceScaffoldRow(
.height(IntrinsicSize.Min), .height(IntrinsicSize.Min),
) { ) {
Avatar( Avatar(
avatarData = spaceRoom.getAvatarData(AvatarSize.SpaceListItem), avatarData = avatarData,
avatarType = AvatarType.Space(), avatarType = if (isSpace) AvatarType.Space() else AvatarType.Room(),
hideImage = hideAvatars, hideImage = hideAvatars,
) )
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
@ -185,13 +195,39 @@ private fun SpaceScaffoldRow(
} }
} }
@PreviewsDayNight
@Composable @Composable
internal fun HomeSpaceItemViewPreview(@PreviewParameter(SpaceRoomProvider::class) spaceRoom: SpaceRoom) = ElementPreview { @ReadOnlyComposable
HomeSpaceItemView( private fun SpaceRoom.subtitle(): String {
spaceRoom = spaceRoom, return if (isSpace) {
showUnreadIndicator = false, if (joinRule == JoinRule.Public) {
hideAvatars = true, stringResource(CommonStrings.common_public_space)
onClick = {}, } else {
) stringResource(CommonStrings.common_private_space)
}
} else {
pluralStringResource(CommonPlurals.common_member_count, numJoinedMembers, numJoinedMembers)
}
}
@Composable
@ReadOnlyComposable
private fun SpaceRoom.info(): String {
return if (isSpace) {
stringResource(
CommonStrings.screen_space_list_details,
pluralStringResource(CommonPlurals.common_rooms, childrenCount, childrenCount),
pluralStringResource(CommonPlurals.common_member_count, numJoinedMembers, numJoinedMembers),
)
} else {
topic.orEmpty()
}
}
@Composable
private fun SpaceRoom.visibilityIcon(): ImageVector? {
return if (joinRule == JoinRule.Public) {
CompoundIcons.Public()
} else {
CompoundIcons.LockSolid()
}
} }