diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt index c9f819a3d5..18d611e6b1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt @@ -16,18 +16,26 @@ package io.element.android.libraries.matrix.ui.components +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -35,6 +43,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import kotlin.math.floor @Composable fun SelectedUsersList( @@ -56,16 +65,60 @@ fun SelectedUsersList( } } + val rowWidth by remember { + derivedStateOf { + lazyListState.layoutInfo.viewportSize.width - lazyListState.layoutInfo.beforeContentPadding + } + } + LazyRow( state = lazyListState, - modifier = modifier, + modifier = modifier + .fillMaxWidth(), contentPadding = contentPadding, - horizontalArrangement = Arrangement.spacedBy(24.dp), ) { - items(selectedUsers.toList()) { matrixUser -> - SelectedUser( - matrixUser = matrixUser, - onUserRemoved = onUserRemoved, + itemsIndexed(selectedUsers.toList()) { index, matrixUser -> + Layout( + content = { + SelectedUser( + matrixUser = matrixUser, + onUserRemoved = onUserRemoved, + ) + }, + measurePolicy = { measurables, constraints -> + // Measures out extra space between each user to ensure that the last visible user is exactly half visible, giving an affordance that + // the user can scroll to see more. + + val placeable = measurables.first().measure(constraints) + val isLastItem = index == selectedUsers.lastIndex + val width = if (isLastItem) { + // Don't add any padding on the final item + placeable.width + } else { + val minimumSpacing = 24.dp.toPx() + val userWidth = placeable.width + minimumSpacing + val maxVisibleUsers = rowWidth / userWidth + if (maxVisibleUsers >= selectedUsers.size) { + // If we can fit all the users in, don't do anything fancy + (placeable.width + minimumSpacing).toInt() + } else { + // Round down the number of visible users to end with a state where one is half visible + val targetFraction = (placeable.width / 2) / userWidth + val targetUsers = floor(maxVisibleUsers - targetFraction) + targetFraction + + // Work out how much extra spacing we need to reduce the number of users that much, then split it evenly amongst the visible users + val extraSpacing = (maxVisibleUsers - targetUsers) * userWidth + val extraSpacingPerUser = (extraSpacing) / floor(targetUsers) + + (placeable.width + minimumSpacing + extraSpacingPerUser).toInt() + } + } + + + layout(width, placeable.height) { + placeable.place(0, 0) + } + } ) } } @@ -81,7 +134,23 @@ internal fun SelectedUsersListDarkPreview() = ElementPreviewDark { ContentToPrev @Composable private fun ContentToPreview() { - SelectedUsersList( - selectedUsers = aMatrixUserList().take(6).toImmutableList(), - ) + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + // Two users that will be visible with no scrolling + SelectedUsersList( + selectedUsers = aMatrixUserList().take(2).toImmutableList(), + modifier = Modifier + .width(200.dp) + .border(1.dp, Color.Red) + ) + + // Multiple users that don't fit, so will be spaced out per the measure policy + for (i in 0..5) { + SelectedUsersList( + selectedUsers = aMatrixUserList().take(6).toImmutableList(), + modifier = Modifier + .width((200 + (i * 20)).dp) + .border(1.dp, Color.Red) + ) + } + } }