From a5849ca0bc1494669d16e29036aa219d8cbf0904 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 1 Jun 2023 10:17:28 +0100 Subject: [PATCH] Selected users: if scrollable, make last one peek If there are enough selected users that they can't all be displayed, add extra padding in between the users to ensure that the last visible one is half visible to provide some scroll affordance. --- .../matrix/ui/components/SelectedUsersList.kt | 89 ++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) 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) + ) + } + } }